From ea6292168a6acd4c263913f0ccd7dd64daf4f5cf Mon Sep 17 00:00:00 2001 From: Sandro Jäckel Date: Fri, 23 Feb 2018 01:45:58 +0100 Subject: Revert "Applied clang-format on source" This reverts commit 5e5c9c07291f6b09623d31c92b1fb61c4ede576e. --- .clang-format | 11 - src/aboutdialog.cpp | 95 +- src/aboutdialog.h | 44 +- src/activatemodsdialog.cpp | 99 +- src/activatemodsdialog.h | 58 +- src/bbcode.cpp | 415 +- src/bbcode.h | 7 +- src/browserdialog.cpp | 370 +- src/browserdialog.h | 110 +- src/browserview.cpp | 77 +- src/browserview.h | 67 +- src/categories.cpp | 516 +-- src/categories.h | 306 +- src/categoriesdialog.cpp | 320 +- src/categoriesdialog.h | 46 +- src/credentialsdialog.cpp | 32 +- src/credentialsdialog.h | 21 +- src/csvbuilder.cpp | 345 +- src/csvbuilder.h | 99 +- src/descriptionpage.h | 14 +- src/directoryrefresher.cpp | 203 +- src/directoryrefresher.h | 200 +- src/downloadlist.cpp | 123 +- src/downloadlist.h | 96 +- src/downloadlistsortproxy.cpp | 71 +- src/downloadlistsortproxy.h | 27 +- src/downloadlistwidget.cpp | 482 ++- src/downloadlistwidget.h | 103 +- src/downloadlistwidgetcompact.cpp | 455 ++- src/downloadlistwidgetcompact.h | 114 +- src/downloadmanager.cpp | 2325 ++++++----- src/downloadmanager.h | 837 ++-- src/editexecutablesdialog.cpp | 459 ++- src/editexecutablesdialog.h | 91 +- src/eventfilter.cpp | 14 +- src/eventfilter.h | 16 +- src/executableslist.cpp | 253 +- src/executableslist.h | 273 +- src/filedialogmemory.cpp | 106 +- src/filedialogmemory.h | 37 +- src/gameinfoimpl.cpp | 49 +- src/genericicondelegate.cpp | 44 +- src/genericicondelegate.h | 45 +- src/helper.cpp | 90 +- src/helper.h | 13 +- src/icondelegate.cpp | 71 +- src/icondelegate.h | 22 +- src/ilockedwaitingforprocess.h | 7 +- src/installationmanager.cpp | 1334 +++--- src/installationmanager.h | 303 +- src/instancemanager.cpp | 410 +- src/instancemanager.h | 46 +- src/iuserinterface.h | 35 +- src/json.cpp | 823 ++-- src/json.h | 105 +- src/loadmechanism.cpp | 454 ++- src/loadmechanism.h | 119 +- src/lockeddialog.cpp | 64 +- src/lockeddialog.h | 21 +- src/lockeddialogbase.cpp | 65 +- src/lockeddialogbase.h | 32 +- src/logbuffer.cpp | 337 +- src/logbuffer.h | 78 +- src/loghighlighter.cpp | 47 +- src/loghighlighter.h | 9 +- src/main.cpp | 1052 ++--- src/mainwindow.cpp | 8158 +++++++++++++++++++------------------ src/mainwindow.h | 691 ++-- src/messagedialog.cpp | 107 +- src/messagedialog.h | 46 +- src/moapplication.cpp | 175 +- src/moapplication.h | 19 +- src/modeltest.cpp | 496 +-- src/modeltest.h | 62 +- src/modflagicondelegate.cpp | 142 +- src/modflagicondelegate.h | 17 +- src/modinfo.cpp | 512 +-- src/modinfo.h | 1111 ++--- src/modinfobackup.cpp | 22 +- src/modinfobackup.h | 49 +- src/modinfodialog.cpp | 1832 +++++---- src/modinfodialog.h | 313 +- src/modinfoforeign.cpp | 76 +- src/modinfoforeign.h | 100 +- src/modinfooverwrite.cpp | 75 +- src/modinfooverwrite.h | 82 +- src/modinforegular.cpp | 878 ++-- src/modinforegular.h | 622 +-- src/modinfowithconflictinfo.cpp | 184 +- src/modinfowithconflictinfo.h | 62 +- src/modlist.cpp | 1891 ++++----- src/modlist.h | 432 +- src/modlistsortproxy.cpp | 646 +-- src/modlistsortproxy.h | 134 +- src/modlistview.cpp | 67 +- src/modlistview.h | 22 +- src/moshortcut.cpp | 26 +- src/moshortcut.h | 23 +- src/motddialog.cpp | 27 +- src/motddialog.h | 19 +- src/nexusinterface.cpp | 943 +++-- src/nexusinterface.h | 700 ++-- src/noeditdelegate.cpp | 9 +- src/noeditdelegate.h | 6 +- src/nxmaccessmanager.cpp | 478 ++- src/nxmaccessmanager.h | 131 +- src/organizercore.cpp | 3491 ++++++++-------- src/organizercore.h | 408 +- src/organizerproxy.cpp | 187 +- src/organizerproxy.h | 93 +- src/overwriteinfodialog.cpp | 340 +- src/overwriteinfodialog.h | 53 +- src/persistentcookiejar.cpp | 96 +- src/persistentcookiejar.h | 19 +- src/plugincontainer.cpp | 502 +-- src/plugincontainer.h | 115 +- src/pluginlist.cpp | 1866 +++++---- src/pluginlist.h | 501 +-- src/pluginlistsortproxy.cpp | 137 +- src/pluginlistsortproxy.h | 44 +- src/pluginlistview.cpp | 66 +- src/pluginlistview.h | 22 +- src/previewdialog.cpp | 66 +- src/previewdialog.h | 24 +- src/previewgenerator.cpp | 40 +- src/previewgenerator.h | 24 +- src/problemsdialog.cpp | 104 +- src/problemsdialog.h | 30 +- src/profile.cpp | 1180 +++--- src/profile.h | 569 +-- src/profileinputdialog.cpp | 28 +- src/profileinputdialog.h | 19 +- src/profilesdialog.cpp | 460 ++- src/profilesdialog.h | 81 +- src/qtgroupingproxy.cpp | 1651 ++++---- src/qtgroupingproxy.h | 235 +- src/queryoverwritedialog.cpp | 51 +- src/queryoverwritedialog.h | 39 +- src/savetextasdialog.cpp | 54 +- src/savetextasdialog.h | 21 +- src/selectiondialog.cpp | 107 +- src/selectiondialog.h | 54 +- src/selfupdater.cpp | 412 +- src/selfupdater.h | 142 +- src/serverinfo.h | 13 +- src/settings.cpp | 1491 +++---- src/settings.h | 846 ++-- src/settingsdialog.cpp | 319 +- src/settingsdialog.h | 53 +- src/shared/appconfig.cpp | 4 +- src/shared/appconfig.h | 4 +- src/shared/directoryentry.cpp | 1332 +++--- src/shared/directoryentry.h | 405 +- src/shared/error_report.cpp | 98 +- src/shared/leaktrace.cpp | 70 +- src/shared/leaktrace.h | 7 +- src/shared/stackdata.cpp | 219 +- src/shared/stackdata.h | 43 +- src/shared/util.cpp | 209 +- src/shared/util.h | 23 +- src/shared/windows_error.cpp | 43 +- src/shared/windows_error.h | 14 +- src/singleinstance.cpp | 125 +- src/singleinstance.h | 79 +- src/spawn.cpp | 244 +- src/spawn.h | 12 +- src/syncoverwritedialog.cpp | 215 +- src/syncoverwritedialog.h | 31 +- src/transfersavesdialog.cpp | 464 ++- src/transfersavesdialog.h | 81 +- src/usvfsconnector.cpp | 251 +- src/usvfsconnector.h | 50 +- src/viewmarkingscrollbar.cpp | 54 +- src/viewmarkingscrollbar.h | 20 +- src/waitingonclosedialog.cpp | 61 +- src/waitingonclosedialog.h | 23 +- 176 files changed, 30169 insertions(+), 26462 deletions(-) delete mode 100644 .clang-format diff --git a/.clang-format b/.clang-format deleted file mode 100644 index 6e34f96b..00000000 --- a/.clang-format +++ /dev/null @@ -1,11 +0,0 @@ ---- -Language: Cpp -BasedOnStyle: LLVM - -AccessModifierOffset: -4 -AlwaysBreakTemplateDeclarations: true -ColumnLimit: 120 -DerivePointerAlignment: false -IndentWidth: 4 -PointerAlignment: Left -SortUsingDeclarations: true \ No newline at end of file diff --git a/src/aboutdialog.cpp b/src/aboutdialog.cpp index e79109ad..ed57a217 100644 --- a/src/aboutdialog.cpp +++ b/src/aboutdialog.cpp @@ -17,6 +17,7 @@ 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 @@ -29,55 +30,65 @@ along with Mod Organizer. If not, see . #include #include -AboutDialog::AboutDialog(const QString& version, QWidget* parent) : QDialog(parent), ui(new Ui::AboutDialog) { - ui->setupUi(this); - - m_LicenseFiles[LICENSE_LGPL3] = "lgpl-3.0.txt"; - m_LicenseFiles[LICENSE_GPL3] = "gpl-3.0.txt"; - m_LicenseFiles[LICENSE_BSD3] = "bsd3.txt"; - m_LicenseFiles[LICENSE_BOOST] = "boost.txt"; - m_LicenseFiles[LICENSE_CCBY3] = "by-sa3.txt"; - m_LicenseFiles[LICENSE_ZLIB] = "zlib.txt"; - m_LicenseFiles[LICENSE_APACHE2] = "apache-license-2.0.txt"; - - addLicense("Qt", LICENSE_LGPL3); - addLicense("Qt Json", LICENSE_GPL3); - addLicense("Boost Library", LICENSE_BOOST); - addLicense("7-zip", LICENSE_LGPL3); - addLicense("ZLib", LICENSE_ZLIB); - 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_APACHE2); - addLicense("LOOT", LICENSE_GPL3); - - ui->nameLabel->setText(QString("%1 %2") - .arg(ui->nameLabel->text()) - .arg(version)); + +AboutDialog::AboutDialog(const QString &version, QWidget *parent) + : QDialog(parent) + , ui(new Ui::AboutDialog) +{ + ui->setupUi(this); + + m_LicenseFiles[LICENSE_LGPL3] = "lgpl-3.0.txt"; + m_LicenseFiles[LICENSE_GPL3] = "gpl-3.0.txt"; + m_LicenseFiles[LICENSE_BSD3] = "bsd3.txt"; + m_LicenseFiles[LICENSE_BOOST] = "boost.txt"; + m_LicenseFiles[LICENSE_CCBY3] = "by-sa3.txt"; + m_LicenseFiles[LICENSE_ZLIB] = "zlib.txt"; + m_LicenseFiles[LICENSE_APACHE2] = "apache-license-2.0.txt"; + + addLicense("Qt", LICENSE_LGPL3); + addLicense("Qt Json", LICENSE_GPL3); + addLicense("Boost Library", LICENSE_BOOST); + addLicense("7-zip", LICENSE_LGPL3); + addLicense("ZLib", LICENSE_ZLIB); + 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_APACHE2); + addLicense("LOOT", LICENSE_GPL3); + + ui->nameLabel->setText(QString("%1 %2").arg(ui->nameLabel->text()).arg(version)); #if defined(HGID) - ui->revisionLabel->setText(ui->revisionLabel->text() + " " + HGID); + ui->revisionLabel->setText(ui->revisionLabel->text() + " " + HGID); #elif defined(GITID) - ui->revisionLabel->setText(ui->revisionLabel->text() + " " + GITID); + ui->revisionLabel->setText(ui->revisionLabel->text() + " " + GITID); #else - ui->revisionLabel->setText(ui->revisionLabel->text() + " unknown"); + ui->revisionLabel->setText(ui->revisionLabel->text() + " unknown"); #endif } -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); +AboutDialog::~AboutDialog() +{ + delete ui; } -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() + "/license/" + iter->second; - QString text = MOBase::readFileText(filePath); - ui->licenseText->setText(text); - } else { - ui->licenseText->setText(tr("No license")); - } + +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() + "/license/" + iter->second; + QString text = MOBase::readFileText(filePath); + ui->licenseText->setText(text); + } else { + ui->licenseText->setText(tr("No license")); + } } diff --git a/src/aboutdialog.h b/src/aboutdialog.h index a5c93834..103a0d2f 100644 --- a/src/aboutdialog.h +++ b/src/aboutdialog.h @@ -18,6 +18,7 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ + #define ABOUTDIALOG_H #include @@ -28,38 +29,43 @@ class QListWidgetItem; #include namespace Ui { -class AboutDialog; + class AboutDialog; } -class AboutDialog : public QDialog { - Q_OBJECT +class AboutDialog : public QDialog +{ + Q_OBJECT public: - explicit AboutDialog(const QString& version, QWidget* parent = 0); - ~AboutDialog(); + explicit AboutDialog(const QString &version, QWidget *parent = 0); + ~AboutDialog(); private: - enum Licenses { - LICENSE_NONE, - LICENSE_LGPL3, - LICENSE_GPL3, - LICENSE_BSD3, - LICENSE_BOOST, - LICENSE_CCBY3, - LICENSE_ZLIB, - LICENSE_APACHE2 - }; + + enum Licenses { + LICENSE_NONE, + LICENSE_LGPL3, + LICENSE_GPL3, + LICENSE_BSD3, + LICENSE_BOOST, + LICENSE_CCBY3, + LICENSE_ZLIB, + LICENSE_APACHE2 + }; private: - void addLicense(const QString& name, Licenses license); + + void addLicense(const QString &name, Licenses license); private slots: - void on_creditsList_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous); + void on_creditsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); private: - Ui::AboutDialog* ui; - std::map m_LicenseFiles; + Ui::AboutDialog *ui; + + std::map m_LicenseFiles; + }; #endif // ABOUTDIALOG_H diff --git a/src/activatemodsdialog.cpp b/src/activatemodsdialog.cpp index f17bd286..c7e3dca2 100644 --- a/src/activatemodsdialog.cpp +++ b/src/activatemodsdialog.cpp @@ -28,60 +28,69 @@ along with Mod Organizer. If not, see . #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(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"); +ActivateModsDialog::~ActivateModsDialog() +{ + delete ui; +} + - for (int row = 0; row < modsTable->rowCount(); ++row) { - QComboBox* comboBox = dynamic_cast(modsTable->cellWidget(row, 1)); - if (comboBox != nullptr) { - result.insert(comboBox->currentText()); - } +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; + } + 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)); +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()); - } + result.insert(espName->text()); } - return result; + } + return result; } diff --git a/src/activatemodsdialog.h b/src/activatemodsdialog.h index b4702da3..f36b5fde 100644 --- a/src/activatemodsdialog.h +++ b/src/activatemodsdialog.h @@ -31,46 +31,46 @@ class QWidget; #include namespace Ui { -class ActivateModsDialog; + class ActivateModsDialog; } /** * @brief Dialog that is used to batch activate/deactivate mods and plugins **/ -class ActivateModsDialog : public MOBase::TutorableDialog { - Q_OBJECT +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(); + /** + * @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; + Ui::ActivateModsDialog *ui; }; #endif // ACTIVATEMODSDIALOG_H diff --git a/src/bbcode.cpp b/src/bbcode.cpp index 3e709376..56369538 100644 --- a/src/bbcode.cpp +++ b/src/bbcode.cpp @@ -22,222 +22,251 @@ along with Mod Organizer. If not, see . #include #include + namespace BBCode { + class BBCodeMap { - typedef std::map> TagMap; + 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 - m_TagNameExp.indexIn(input, 1, QRegExp::CaretAtOffset); - QString tagName = m_TagNameExp.cap(0).toLower(); - TagMap::iterator tagIter = m_TagMap.find(tagName); - if (tagIter != m_TagMap.end()) { - // recognized tag - if (tagName.endsWith('=')) { - tagName.chop(1); - } + static BBCodeMap &instance() { + static BBCodeMap s_Instance; + return s_Instance; + } - int closeTagPos = 0; - int closeTagLength = 0; - if (tagName == "*") { - // ends at the next bullet point - closeTagPos = input.indexOf(QRegExp("(\\[\\*\\]|)", Qt::CaseInsensitive), 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 { - QString closeTag = QString("[/%1]").arg(tagName); - closeTagPos = input.indexOf(closeTag, 0, Qt::CaseInsensitive); - if (closeTagPos == -1) { - // workaround to improve compatibility: add fake closing tag - input.append(closeTag); - closeTagPos = input.size() - closeTag.size(); - } - closeTagLength = closeTag.size(); - } + QString convertTag(QString input, int &length) + { + // extract the tag name + m_TagNameExp.indexIn(input, 1, QRegExp::CaretAtOffset); + QString tagName = m_TagNameExp.cap(0).toLower(); + TagMap::iterator tagIter = m_TagMap.find(tagName); + if (tagIter != m_TagMap.end()) { + // recognized tag + if (tagName.endsWith('=')) { + tagName.chop(1); + } - if (closeTagPos > -1) { - length = closeTagPos + closeTagLength; - QString temp = input.mid(0, length); - if (tagIter->second.first.indexIn(temp) == 0) { - if (tagIter->second.second.isEmpty()) { - if (tagName == "color") { - QString color = tagIter->second.first.cap(1); - QString content = tagIter->second.first.cap(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 { - qWarning("don't know how to deal with tag %s", qPrintable(tagName)); - } - } else { - if (tagName == "*") { - temp.remove(QRegExp("(\\[/\\*\\])?(
)?$")); - } - return temp.replace(tagIter->second.first, tagIter->second.second); - } - } else { - // expression doesn't match. either the input string is invalid - // or the expression is - qWarning("%s doesn't match the expression for %s", temp.toUtf8().constData(), - tagName.toUtf8().constData()); - length = 0; - return QString(); + int closeTagPos = 0; + int closeTagLength = 0; + if (tagName == "*") { + // ends at the next bullet point + closeTagPos = input.indexOf(QRegExp("(\\[\\*\\]|)", Qt::CaseInsensitive), 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 { + QString closeTag = QString("[/%1]").arg(tagName); + closeTagPos = input.indexOf(closeTag, 0, Qt::CaseInsensitive); + 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); + if (tagIter->second.first.indexIn(temp) == 0) { + if (tagIter->second.second.isEmpty()) { + if (tagName == "color") { + QString color = tagIter->second.first.cap(1); + QString content = tagIter->second.first.cap(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 { + qWarning("don't know how to deal with tag %s", qPrintable(tagName)); + } + } else { + if (tagName == "*") { + temp.remove(QRegExp("(\\[/\\*\\])?(
)?$")); } + return temp.replace(tagIter->second.first, tagIter->second.second); + } + } else { + // expression doesn't match. either the input string is invalid + // or the expression is + qWarning("%s doesn't match the expression for %s", + temp.toUtf8().constData(), tagName.toUtf8().constData()); + length = 0; + return QString(); } - - // not a recognized tag or tag invalid - 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(QRegExp("\\[b\\](.*)\\[/b\\]"), "\\1"); - m_TagMap["i"] = std::make_pair(QRegExp("\\[i\\](.*)\\[/i\\]"), "\\1"); - m_TagMap["u"] = std::make_pair(QRegExp("\\[u\\](.*)\\[/u\\]"), "\\1"); - m_TagMap["s"] = std::make_pair(QRegExp("\\[s\\](.*)\\[/s\\]"), "\\1"); - m_TagMap["sub"] = std::make_pair(QRegExp("\\[sub\\](.*)\\[/sub\\]"), "\\1"); - m_TagMap["sup"] = std::make_pair(QRegExp("\\[sup\\](.*)\\[/sup\\]"), "\\1"); - m_TagMap["size="] = - std::make_pair(QRegExp("\\[size=([^\\]]*)\\](.*)\\[/size\\]"), "\\2"); - m_TagMap["color="] = std::make_pair(QRegExp("\\[color=([^\\]]*)\\](.*)\\[/color\\]"), ""); - m_TagMap["font="] = std::make_pair(QRegExp("\\[font=([^\\]]*)\\](.*)\\[/font\\]"), - "\\2"); - m_TagMap["center"] = - std::make_pair(QRegExp("\\[center\\](.*)\\[/center\\]"), "
\\1
"); - m_TagMap["quote"] = std::make_pair(QRegExp("\\[quote\\](.*)\\[/quote\\]"), "
\"\\1\"
"); - m_TagMap["quote="] = std::make_pair(QRegExp("\\[quote=([^\\]]*)\\](.*)\\[/quote\\]"), - "
\"\\2\"
--\\1

"); - m_TagMap["code"] = std::make_pair(QRegExp("\\[code\\](.*)\\[/code\\]"), "
\\1
"); - m_TagMap["heading"] = - std::make_pair(QRegExp("\\[heading\\](.*)\\[/heading\\]"), "

\\1

"); - m_TagMap["line"] = std::make_pair(QRegExp("\\[line\\]"), "
"); - - // lists - m_TagMap["list"] = std::make_pair(QRegExp("\\[list\\](.*)\\[/list\\]"), "
    \\1
"); - m_TagMap["list="] = std::make_pair(QRegExp("\\[list.*\\](.*)\\[/list\\]"), "
    \\1
"); - m_TagMap["ul"] = std::make_pair(QRegExp("\\[ul\\](.*)\\[/ul\\]"), "
    \\1
"); - m_TagMap["ol"] = std::make_pair(QRegExp("\\[ol\\](.*)\\[/ol\\]"), "
    \\1
"); - m_TagMap["li"] = std::make_pair(QRegExp("\\[li\\](.*)\\[/li\\]"), "
  • \\1
  • "); - - // tables - m_TagMap["table"] = std::make_pair(QRegExp("\\[table\\](.*)\\[/table\\]"), "\\1
    "); - m_TagMap["tr"] = std::make_pair(QRegExp("\\[tr\\](.*)\\[/tr\\]"), "\\1"); - m_TagMap["th"] = std::make_pair(QRegExp("\\[th\\](.*)\\[/th\\]"), "\\1"); - m_TagMap["td"] = std::make_pair(QRegExp("\\[td\\](.*)\\[/td\\]"), "\\1"); - - // web content - m_TagMap["url"] = std::make_pair(QRegExp("\\[url\\](.*)\\[/url\\]"), "\\1"); - m_TagMap["url="] = std::make_pair(QRegExp("\\[url=([^\\]]*)\\](.*)\\[/url\\]"), "\\2"); - m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), ""); - m_TagMap["img="] = - std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), "\"\\1\""); - m_TagMap["email="] = - std::make_pair(QRegExp("\\[email=\"?([^\\]]*)\"?\\](.*)\\[/email\\]"), "\\2"); - m_TagMap["youtube"] = - std::make_pair(QRegExp("\\[youtube\\](.*)\\[/youtube\\]"), - "http://www.youtube.com/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.setCaseSensitivity(Qt::CaseInsensitive); - iter->second.first.setMinimal(true); - } + BBCodeMap() + : m_TagNameExp("^[a-zA-Z*]*=?") + { + m_TagMap["b"] = std::make_pair(QRegExp("\\[b\\](.*)\\[/b\\]"), + "\\1"); + m_TagMap["i"] = std::make_pair(QRegExp("\\[i\\](.*)\\[/i\\]"), + "\\1"); + m_TagMap["u"] = std::make_pair(QRegExp("\\[u\\](.*)\\[/u\\]"), + "\\1"); + m_TagMap["s"] = std::make_pair(QRegExp("\\[s\\](.*)\\[/s\\]"), + "\\1"); + m_TagMap["sub"] = std::make_pair(QRegExp("\\[sub\\](.*)\\[/sub\\]"), + "\\1"); + m_TagMap["sup"] = std::make_pair(QRegExp("\\[sup\\](.*)\\[/sup\\]"), + "\\1"); + m_TagMap["size="] = std::make_pair(QRegExp("\\[size=([^\\]]*)\\](.*)\\[/size\\]"), + "\\2"); + m_TagMap["color="] = std::make_pair(QRegExp("\\[color=([^\\]]*)\\](.*)\\[/color\\]"), + ""); + m_TagMap["font="] = std::make_pair(QRegExp("\\[font=([^\\]]*)\\](.*)\\[/font\\]"), + "\\2"); + m_TagMap["center"] = std::make_pair(QRegExp("\\[center\\](.*)\\[/center\\]"), + "
    \\1
    "); + m_TagMap["quote"] = std::make_pair(QRegExp("\\[quote\\](.*)\\[/quote\\]"), + "
    \"\\1\"
    "); + m_TagMap["quote="] = std::make_pair(QRegExp("\\[quote=([^\\]]*)\\](.*)\\[/quote\\]"), + "
    \"\\2\"
    --\\1

    "); + m_TagMap["code"] = std::make_pair(QRegExp("\\[code\\](.*)\\[/code\\]"), + "
    \\1
    "); + m_TagMap["heading"]= std::make_pair(QRegExp("\\[heading\\](.*)\\[/heading\\]"), + "

    \\1

    "); + m_TagMap["line"] = std::make_pair(QRegExp("\\[line\\]"), + "
    "); - // this tag is in fact greedy - m_TagMap["*"] = std::make_pair(QRegExp("\\[\\*\\](.*)"), "
  • \\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")); + // lists + m_TagMap["list"] = std::make_pair(QRegExp("\\[list\\](.*)\\[/list\\]"), + "
      \\1
    "); + m_TagMap["list="] = std::make_pair(QRegExp("\\[list.*\\](.*)\\[/list\\]"), + "
      \\1
    "); + m_TagMap["ul"] = std::make_pair(QRegExp("\\[ul\\](.*)\\[/ul\\]"), + "
      \\1
    "); + m_TagMap["ol"] = std::make_pair(QRegExp("\\[ol\\](.*)\\[/ol\\]"), + "
      \\1
    "); + m_TagMap["li"] = std::make_pair(QRegExp("\\[li\\](.*)\\[/li\\]"), + "
  • \\1
  • "); + + // tables + m_TagMap["table"] = std::make_pair(QRegExp("\\[table\\](.*)\\[/table\\]"), + "\\1
    "); + m_TagMap["tr"] = std::make_pair(QRegExp("\\[tr\\](.*)\\[/tr\\]"), + "\\1"); + m_TagMap["th"] = std::make_pair(QRegExp("\\[th\\](.*)\\[/th\\]"), + "\\1"); + m_TagMap["td"] = std::make_pair(QRegExp("\\[td\\](.*)\\[/td\\]"), + "\\1"); + + // web content + m_TagMap["url"] = std::make_pair(QRegExp("\\[url\\](.*)\\[/url\\]"), + "\\1"); + m_TagMap["url="] = std::make_pair(QRegExp("\\[url=([^\\]]*)\\](.*)\\[/url\\]"), + "\\2"); + m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), + ""); + m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), + "\"\\1\""); + m_TagMap["email="] = std::make_pair(QRegExp("\\[email=\"?([^\\]]*)\"?\\](.*)\\[/email\\]"), + "\\2"); + m_TagMap["youtube"] = std::make_pair(QRegExp("\\[youtube\\](.*)\\[/youtube\\]"), + "http://www.youtube.com/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.setCaseSensitivity(Qt::CaseInsensitive); + iter->second.first.setMinimal(true); } + // this tag is in fact greedy + m_TagMap["*"] = std::make_pair(QRegExp("\\[\\*\\](.*)"), + "
  • \\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: - QRegExp m_TagNameExp; - TagMap m_TagMap; - std::map m_ColorMap; + + QRegExp 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.midRef(lastBlock, pos - lastBlock)); - - if ((pos < (input.size() - 1)) && (input.at(pos + 1) == '/')) { - // skip invalid end tag - int tagEnd = input.indexOf(']', pos) + 1; - 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; + +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.midRef(lastBlock, pos - lastBlock)); + + if ((pos < (input.size() - 1)) && (input.at(pos + 1) == '/')) { + // skip invalid end tag + int tagEnd = input.indexOf(']', pos) + 1; + 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.midRef(lastBlock)); - return result; + // append the remainder (everything after the last tag) + result.append(input.midRef(lastBlock)); + return result; } } // namespace BBCode + diff --git a/src/bbcode.h b/src/bbcode.h index d8a10337..708dfe71 100644 --- a/src/bbcode.h +++ b/src/bbcode.h @@ -20,8 +20,10 @@ along with Mod Organizer. If not, see . #ifndef BBCODE_H #define BBCODE_H + #include + namespace BBCode { /** @@ -30,8 +32,9 @@ namespace BBCode { * @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); +QString convertToHTML(const QString &input); + +} -} // namespace BBCode #endif // BBCODE_H diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index d5a43332..c2c65acc 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -19,228 +19,276 @@ along with Mod Organizer. If not, see . #include "browserdialog.h" +#include "ui_browserdialog.h" #include "browserview.h" #include "messagedialog.h" -#include "persistentcookiejar.h" #include "report.h" -#include "ui_browserdialog.h" +#include "persistentcookiejar.h" -#include "settings.h" #include +#include "settings.h" -#include -#include -#include -#include -#include -#include +#include #include +#include +#include +#include #include -#include +#include +#include +#include + -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().getCacheDirectory() + "/cookies.dat"))); +BrowserDialog::BrowserDialog(QWidget *parent) + : QDialog(parent) + , ui(new Ui::BrowserDialog) + , m_AccessManager(new QNetworkAccessManager(this)) +{ + ui->setupUi(this); - Qt::WindowFlags flags = windowFlags() | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint; - Qt::WindowFlags helpFlag = Qt::WindowContextHelpButtonHint; - flags = flags & (~helpFlag); - setWindowFlags(flags); + m_AccessManager->setCookieJar(new PersistentCookieJar( + QDir::fromNativeSeparators(Settings::instance().getCacheDirectory() + "/cookies.dat"))); - m_Tabs = this->findChild("browserTabWidget"); + Qt::WindowFlags flags = windowFlags() | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint; + Qt::WindowFlags helpFlag = Qt::WindowContextHelpButtonHint; + flags = flags & (~helpFlag); + setWindowFlags(flags); - installEventFilter(this); + m_Tabs = this->findChild("browserTabWidget"); - connect(m_Tabs, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int))); + installEventFilter(this); - ui->urlEdit->setVisible(false); + connect(m_Tabs, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int))); + + ui->urlEdit->setVisible(false); } -BrowserDialog::~BrowserDialog() { delete ui; } +BrowserDialog::~BrowserDialog() +{ + delete ui; +} -void BrowserDialog::closeEvent(QCloseEvent* event) { - // m_AccessManager->showCookies(); - QDialog::closeEvent(event); +void BrowserDialog::closeEvent(QCloseEvent *event) +{ +// m_AccessManager->showCookies(); + QDialog::closeEvent(event); } -void BrowserDialog::initTab(BrowserView* newView) { - // newView->page()->setNetworkAccessManager(m_AccessManager); - // newView->page()->setForwardUnsupportedContent(true); +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); +} - 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); } -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()); } -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::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()) { - show(); - } - openInNewTab(url); + +void BrowserDialog::openUrl(const QUrl &url) +{ + if (isHidden()) { + show(); + } + openInNewTab(url); } -void BrowserDialog::maximizeWidth() { - int viewportWidth = getCurrentView()->page()->contentsSize().width(); - int frameWidth = width() - viewportWidth; - int contentWidth = getCurrentView()->page()->contentsSize().width(); +void BrowserDialog::maximizeWidth() +{ + int viewportWidth = getCurrentView()->page()->contentsSize ().width(); + int frameWidth = width() - viewportWidth; + + int contentWidth = getCurrentView()->page()->contentsSize().width(); - QDesktopWidget screen; - int currentScreen = screen.screenNumber(this); - int screenWidth = screen.screenGeometry(currentScreen).size().width(); + QDesktopWidget screen; + int currentScreen = screen.screenNumber(this); + int screenWidth = screen.screenGeometry(currentScreen).size().width(); - int targetWidth = std::min(std::max(viewportWidth, contentWidth) + frameWidth, screenWidth); - this->resize(targetWidth, height()); + 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::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); - } + +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) { - QRegExp uploadsExp(QString("http://.+/uploads/([^/]+)$")); - if (uploadsExp.indexIn(url) != -1) { - // these seem to be premium downloads - return uploadsExp.cap(1); - } - QRegExp filesExp(QString("http://.+\\?file=([^&]+)")); - if (filesExp.indexIn(url) != -1) { - // a regular manual download? - return filesExp.cap(1); +QString BrowserDialog::guessFileName(const QString &url) +{ + QRegExp uploadsExp(QString("http://.+/uploads/([^/]+)$")); + if (uploadsExp.indexIn(url) != -1) { + // these seem to be premium downloads + return uploadsExp.cap(1); + } + + QRegExp filesExp(QString("http://.+\\?file=([^&]+)")); + if (filesExp.indexIn(url) != -1) { + // a regular manual download? + return filesExp.cap(1); + } + return "unknown"; +} + +void BrowserDialog::unsupportedContent(QNetworkReply *reply) +{ + try { + QWebEnginePage *page = qobject_cast(sender()); + if (page == nullptr) { + qCritical("sender not a page"); + return; + } + BrowserView *view = qobject_cast(page->view()); + if (view == nullptr) { + qCritical("no view?"); + return; } - return "unknown"; -} - -void BrowserDialog::unsupportedContent(QNetworkReply* reply) { - try { - QWebEnginePage* page = qobject_cast(sender()); - if (page == nullptr) { - qCritical("sender not a page"); - return; - } - BrowserView* view = qobject_cast(page->view()); - if (view == nullptr) { - qCritical("no view?"); - return; - } - - emit requestDownload(view->url(), reply); - } catch (const std::exception& e) { - if (isVisible()) { - MessageDialog::showMessage(tr("failed to start download"), this); - } - qCritical("exception downloading unsupported content: %s", e.what()); + + emit requestDownload(view->url(), reply); + } catch (const std::exception &e) { + if (isVisible()) { + MessageDialog::showMessage(tr("failed to start download"), this); } + qCritical("exception downloading unsupported content: %s", e.what()); + } } -void BrowserDialog::downloadRequested(const QNetworkRequest& request) { - qCritical("download request %s ignored", request.url().toString().toUtf8().constData()); + +void BrowserDialog::downloadRequested(const QNetworkRequest &request) +{ + qCritical("download request %s ignored", request.url().toString().toUtf8().constData()); } -void BrowserDialog::tabCloseRequested(int index) { - if (m_Tabs->count() == 1) { - this->close(); - } else { - m_Tabs->widget(index)->deleteLater(); - m_Tabs->removeTab(index); - } + +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_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::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::startSearch() +{ + ui->searchEdit->setFocus(); } -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_searchEdit_returnPressed() +{ +// BrowserView *currentView = getCurrentView(); +// if (currentView != nullptr) { +// currentView->findText(ui->searchEdit->text(), QWebEnginePage::FindWrapsAroundDocument); +// } } -void BrowserDialog::on_urlEdit_returnPressed() { - QWebEngineView* currentView = getCurrentView(); - if (currentView != nullptr) { - currentView->setUrl(QUrl(ui->urlEdit->text())); - } +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; - } +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); + } + return QDialog::eventFilter(object, event); } diff --git a/src/browserdialog.h b/src/browserdialog.h index 6947cc43..354a377b 100644 --- a/src/browserdialog.h +++ b/src/browserdialog.h @@ -20,17 +20,18 @@ along with Mod Organizer. If not, see . #ifndef BROWSERDIALOG_H #define BROWSERDIALOG_H -#include #include -#include #include -#include -#include +#include #include #include +#include +#include +#include + namespace Ui { -class BrowserDialog; + class BrowserDialog; } class BrowserView; @@ -38,81 +39,88 @@ class BrowserView; /** * @brief a dialog containing a webbrowser that is intended to browse the nexus network **/ -class BrowserDialog : public QDialog { +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); + + /** + * @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); + /** + * @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*); + + virtual void closeEvent(QCloseEvent *); private slots: - void initTab(BrowserView* newView); - void openInNewTab(const QUrl& url); + void initTab(BrowserView *newView); + void openInNewTab(const QUrl &url); - void progress(int value); + void progress(int value); - void titleChanged(const QString& title); - void unsupportedContent(QNetworkReply* reply); - void downloadRequested(const QNetworkRequest& request); + void titleChanged(const QString &title); + void unsupportedContent(QNetworkReply *reply); + void downloadRequested(const QNetworkRequest &request); - void tabCloseRequested(int index); + void tabCloseRequested(int index); - void urlChanged(const QUrl& url); + void urlChanged(const QUrl &url); - void on_backBtn_clicked(); + void on_backBtn_clicked(); - void on_fwdBtn_clicked(); + void on_fwdBtn_clicked(); - void on_searchEdit_returnPressed(); + void on_searchEdit_returnPressed(); - void startSearch(); + void startSearch(); - void on_refreshBtn_clicked(); + void on_refreshBtn_clicked(); - void on_browserTabWidget_currentChanged(int index); + void on_browserTabWidget_currentChanged(int index); - void on_urlEdit_returnPressed(); + void on_urlEdit_returnPressed(); private: - QString guessFileName(const QString& url); - BrowserView* getCurrentView(); + QString guessFileName(const QString &url); + + BrowserView *getCurrentView(); - void maximizeWidth(); + void maximizeWidth(); private: - Ui::BrowserDialog* ui; - QNetworkAccessManager* m_AccessManager; + Ui::BrowserDialog *ui; + + QNetworkAccessManager *m_AccessManager; + + QTabWidget *m_Tabs; + - QTabWidget* m_Tabs; }; #endif // BROWSERDIALOG_H diff --git a/src/browserview.cpp b/src/browserview.cpp index a44792b2..0b871e23 100644 --- a/src/browserview.cpp +++ b/src/browserview.cpp @@ -19,53 +19,58 @@ along with Mod Organizer. If not, see . #include "browserview.h" -#include "utility.h" #include #include -#include #include #include #include +#include #include +#include "utility.h" + -BrowserView::BrowserView(QWidget* parent) : QWebEngineView(parent) { - installEventFilter(this); +BrowserView::BrowserView(QWidget *parent) + : QWebEngineView(parent) +{ + installEventFilter(this); - // page()->settings()->setMaximumPagesInCache(10); + //page()->settings()->setMaximumPagesInCache(10); } -QWebEngineView* BrowserView::createWindow(QWebEnginePage::WebWindowType) { - BrowserView* newView = new BrowserView(parentWidget()); - emit initTab(newView); - return newView; +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::MidButton) { - 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; - // } +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::MidButton) { + mouseEvent->ignore(); + return true; } - return QWebEngineView::eventFilter(obj, event); +// 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 b390ed4e..24be21c1 100644 --- a/src/browserview.h +++ b/src/browserview.h @@ -20,55 +20,62 @@ along with Mod Organizer. If not, see . #ifndef NEXUSVIEW_H #define NEXUSVIEW_H + class QEvent; class QUrl; class QWidget; -#include #include +#include /** * @brief web view used to display a nexus page **/ -class BrowserView : public QWebEngineView { +class BrowserView : public QWebEngineView +{ Q_OBJECT public: - explicit BrowserView(QWidget* parent = 0); + + 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(); + /** + * @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); + virtual QWebEngineView *createWindow(QWebEnginePage::WebWindowType type); + + virtual bool eventFilter(QObject *obj, QEvent *event); + private: - QString m_FindPattern; - bool m_MiddleClick; + + QString m_FindPattern; + bool m_MiddleClick; + }; #endif // NEXUSVIEW_H diff --git a/src/categories.cpp b/src/categories.cpp index c88227e6..d8cd49fb 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -19,302 +19,350 @@ along with Mod Organizer. If not, see . #include "categories.h" -#include #include +#include -#include -#include +#include #include +#include #include -#include +#include + using namespace MOBase; + CategoryFactory* CategoryFactory::s_Instance = nullptr; -QString CategoryFactory::categoriesFilePath() { return qApp->property("dataPath").toString() + "/categories.dat"; } -CategoryFactory::CategoryFactory() { atexit(&cleanup); } +QString CategoryFactory::categoriesFilePath() +{ + return qApp->property("dataPath").toString() + "/categories.dat"; +} -void CategoryFactory::loadCategories() { - reset(); - QFile categoryFile(categoriesFilePath()); +CategoryFactory::CategoryFactory() +{ + atexit(&cleanup); +} - if (!categoryFile.open(QIODevice::ReadOnly)) { - loadDefaultCategories(); - } else { - int lineNum = 0; - while (!categoryFile.atEnd()) { - QByteArray line = categoryFile.readLine(); - ++lineNum; - QList cells = line.split('|'); - if (cells.count() != 4) { - qCritical("invalid category line %d: %s (%d cells)", lineNum, line.constData(), cells.count()); - } else { - std::vector nexusIDs; - 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) { - qCritical("invalid id %s", iter->constData()); - } - nexusIDs.push_back(temp); - } - } - bool cell0Ok = true; - bool cell3Ok = true; - int id = cells[0].toInt(&cell0Ok); - int parentID = cells[3].trimmed().toInt(&cell3Ok); - if (!cell0Ok || !cell3Ok) { - qCritical("invalid category line %d: %s", lineNum, line.constData()); - } - addCategory(id, QString::fromUtf8(cells[1].constData()), nexusIDs, parentID); +void CategoryFactory::loadCategories() +{ + reset(); + + QFile categoryFile(categoriesFilePath()); + + if (!categoryFile.open(QIODevice::ReadOnly)) { + loadDefaultCategories(); + } else { + int lineNum = 0; + while (!categoryFile.atEnd()) { + QByteArray line = categoryFile.readLine(); + ++lineNum; + QList cells = line.split('|'); + if (cells.count() != 4) { + qCritical("invalid category line %d: %s (%d cells)", + lineNum, line.constData(), cells.count()); + } else { + std::vector nexusIDs; + 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) { + qCritical("invalid id %s", iter->constData()); } + nexusIDs.push_back(temp); + } + } + bool cell0Ok = true; + bool cell3Ok = true; + int id = cells[0].toInt(&cell0Ok); + int parentID = cells[3].trimmed().toInt(&cell3Ok); + if (!cell0Ok || !cell3Ok) { + qCritical("invalid category line %d: %s", + lineNum, line.constData()); } - categoryFile.close(); + addCategory(id, QString::fromUtf8(cells[1].constData()), nexusIDs, parentID); + } } - std::sort(m_Categories.begin(), m_Categories.end()); - setParents(); + categoryFile.close(); + } + std::sort(m_Categories.begin(), m_Categories.end()); + setParents(); } -CategoryFactory& CategoryFactory::instance() { - if (s_Instance == nullptr) { - s_Instance = new CategoryFactory; - } - return *s_Instance; + +CategoryFactory &CategoryFactory::instance() +{ + if (s_Instance == nullptr) { + s_Instance = new CategoryFactory; + } + return *s_Instance; } -void CategoryFactory::reset() { - m_Categories.clear(); - m_IDMap.clear(); - // 28 = - // 43 = Savegames (makes no sense to install them through MO) - // 45 = Videos and trailers - // 87 = Miscelanous - addCategory(0, "None", MakeVector(4, 28, 43, 45, 87), 0); + +void CategoryFactory::reset() +{ + m_Categories.clear(); + m_IDMap.clear(); + // 28 = + // 43 = Savegames (makes no sense to install them through MO) + // 45 = Videos and trailers + // 87 = Miscelanous + addCategory(0, "None", MakeVector(4, 28, 43, 45, 87), 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::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::cleanup() { - delete s_Instance; - s_Instance = nullptr; +void CategoryFactory::cleanup() +{ + delete s_Instance; + s_Instance = nullptr; } -void CategoryFactory::saveCategories() { - QFile categoryFile(categoriesFilePath()); - if (!categoryFile.open(QIODevice::WriteOnly)) { - reportError(QObject::tr("Failed to save custom categories")); - return; - } +void CategoryFactory::saveCategories() +{ + QFile categoryFile(categoriesFilePath()); - 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(VectorJoin(iter->m_NexusIDs, ",")) - .append("|") - .append(QByteArray::number(iter->m_ParentID)) - .append("\n"); - categoryFile.write(line); + if (!categoryFile.open(QIODevice::WriteOnly)) { + reportError(QObject::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; } - categoryFile.close(); + QByteArray line; + line.append(QByteArray::number(iter->m_ID)).append("|") + .append(iter->m_Name.toUtf8()).append("|") + .append(VectorJoin(iter->m_NexusIDs, ",")).append("|") + .append(QByteArray::number(iter->m_ParentID)).append("\n"); + categoryFile.write(line); + } + categoryFile.close(); } -unsigned int CategoryFactory::countCategories(std::tr1::function filter) { - unsigned int result = 0; - for (const Category& cat : m_Categories) { - if (filter(cat)) { - ++result; - } + +unsigned int CategoryFactory::countCategories(std::tr1::function filter) +{ + unsigned int result = 0; + for (const Category &cat : m_Categories) { + if (filter(cat)) { + ++result; } - return result; + } + return result; } -int CategoryFactory::addCategory(const QString& name, const std::vector& nexusIDs, int parentID) { - int id = 1; - while (m_IDMap.find(id) != m_IDMap.end()) { - ++id; - } - addCategory(id, name, nexusIDs, parentID); +int CategoryFactory::addCategory(const QString &name, const std::vector &nexusIDs, int parentID) +{ + int id = 1; + while (m_IDMap.find(id) != m_IDMap.end()) { + ++id; + } + addCategory(id, name, nexusIDs, parentID); - saveCategories(); - return id; + saveCategories(); + return id; } -void CategoryFactory::addCategory(int id, const QString& name, const std::vector& nexusIDs, int parentID) { - int index = static_cast(m_Categories.size()); - m_Categories.push_back(Category(index, id, name, nexusIDs, parentID)); - for (int nexusID : nexusIDs) { - m_NexusMap[nexusID] = index; - } - m_IDMap[id] = index; +void CategoryFactory::addCategory(int id, const QString &name, const std::vector &nexusIDs, int parentID) +{ + int index = static_cast(m_Categories.size()); + m_Categories.push_back(Category(index, id, name, nexusIDs, parentID)); + for (int nexusID : nexusIDs) { + m_NexusMap[nexusID] = index; + } + m_IDMap[id] = index; } -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", MakeVector(2, 4, 51), 0); - addCategory(52, "Poses", MakeVector(1, 29), 1); - addCategory(2, "Armour", MakeVector(2, 5, 54), 0); - addCategory(53, "Power Armor", MakeVector(1, 53), 2); - addCategory(3, "Audio", MakeVector(3, 33, 35, 106), 0); - addCategory(38, "Music", MakeVector(2, 34, 61), 0); - addCategory(39, "Voice", MakeVector(2, 36, 107), 0); - addCategory(5, "Clothing", MakeVector(2, 9, 60), 0); - addCategory(41, "Jewelry", MakeVector(1, 102), 5); - addCategory(42, "Backpacks", MakeVector(1, 49), 5); - addCategory(6, "Collectables", MakeVector(2, 10, 92), 0); - addCategory(28, "Companions", MakeVector(3, 11, 66, 96), 0); - addCategory(7, "Creatures, Mounts, & Vehicles", MakeVector(4, 12, 65, 83, 101), 0); - addCategory(8, "Factions", MakeVector(2, 16, 25), 0); - addCategory(9, "Gameplay", MakeVector(2, 15, 24), 0); - addCategory(27, "Combat", MakeVector(1, 77), 9); - addCategory(43, "Crafting", MakeVector(2, 50, 100), 9); - addCategory(48, "Overhauls", MakeVector(2, 24, 79), 9); - addCategory(49, "Perks", MakeVector(1, 27), 9); - addCategory(54, "Radio", MakeVector(1, 31), 9); - addCategory(55, "Shouts", MakeVector(1, 104), 9); - addCategory(22, "Skills & Levelling", MakeVector(2, 46, 73), 9); - addCategory(58, "Weather & Lighting", MakeVector(1, 56), 9); - addCategory(44, "Equipment", MakeVector(1, 44), 43); - addCategory(45, "Home/Settlement", MakeVector(1, 45), 43); - addCategory(10, "Body, Face, & Hair", MakeVector(2, 17, 26), 0); - addCategory(39, "Tattoos", MakeVector(1, 57), 10); - addCategory(40, "Character Presets", MakeVector(1, 58), 0); - addCategory(11, "Items", MakeVector(2, 27, 85), 0); - addCategory(32, "Mercantile", MakeVector(2, 23, 69), 0); - addCategory(37, "Ammo", MakeVector(1, 3), 11); - addCategory(19, "Weapons", MakeVector(2, 41, 55), 11); - addCategory(36, "Weapon & Armour Sets", MakeVector(1, 42), 11); - addCategory(23, "Player Homes", MakeVector(2, 28, 67), 0); - addCategory(25, "Castles & Mansions", MakeVector(1, 68), 23); - addCategory(51, "Settlements", MakeVector(1, 48), 23); - addCategory(12, "Locations", MakeVector(10, 20, 21, 22, 30, 47, 70, 88, 89, 90, 91), 0); - addCategory(4, "Cities", MakeVector(1, 53), 12); - addCategory(31, "Landscape Changes", MakeVector(1, 58), 0); - addCategory(29, "Environment", MakeVector(2, 14, 74), 0); - addCategory(30, "Immersion", MakeVector(2, 51, 78), 0); - addCategory(20, "Magic", MakeVector(3, 75, 93, 94), 0); - addCategory(21, "Models & Textures", MakeVector(2, 19, 29), 0); - addCategory(33, "Modders resources", MakeVector(2, 18, 82), 0); - addCategory(13, "NPCs", MakeVector(3, 22, 33, 99), 0); - addCategory(24, "Bugfixes", MakeVector(2, 6, 95), 0); - addCategory(14, "Patches", MakeVector(2, 25, 84), 24); - addCategory(35, "Utilities", MakeVector(2, 38, 39), 0); - addCategory(26, "Cheats", MakeVector(1, 8), 0); - addCategory(15, "Quests", MakeVector(2, 30, 35), 0); - addCategory(16, "Races & Classes", MakeVector(1, 34), 0); - addCategory(34, "Stealth", MakeVector(1, 76), 0); - addCategory(17, "UI", MakeVector(2, 37, 42), 0); - addCategory(18, "Visuals", MakeVector(2, 40, 62), 0); - addCategory(50, "Pip-Boy", MakeVector(1, 52), 18); - addCategory(46, "Shader Presets", MakeVector(3, 13, 97, 105), 0); - addCategory(47, "Miscellaneous", MakeVector(2, 2, 28), 0); + +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", MakeVector(2, 4, 51), 0); + addCategory(52, "Poses", MakeVector(1, 29), 1); + addCategory(2, "Armour", MakeVector(2, 5, 54), 0); + addCategory(53, "Power Armor", MakeVector(1, 53), 2); + addCategory(3, "Audio", MakeVector(3, 33, 35, 106), 0); + addCategory(38, "Music", MakeVector(2, 34, 61), 0); + addCategory(39, "Voice", MakeVector(2, 36, 107), 0); + addCategory(5, "Clothing", MakeVector(2, 9, 60), 0); + addCategory(41, "Jewelry", MakeVector(1, 102), 5); + addCategory(42, "Backpacks", MakeVector(1, 49), 5); + addCategory(6, "Collectables", MakeVector(2, 10, 92), 0); + addCategory(28, "Companions", MakeVector(3, 11, 66, 96), 0); + addCategory(7, "Creatures, Mounts, & Vehicles", MakeVector(4, 12, 65, 83, 101), 0); + addCategory(8, "Factions", MakeVector(2, 16, 25), 0); + addCategory(9, "Gameplay", MakeVector(2, 15, 24), 0); + addCategory(27, "Combat", MakeVector(1, 77), 9); + addCategory(43, "Crafting", MakeVector(2, 50, 100), 9); + addCategory(48, "Overhauls", MakeVector(2, 24, 79), 9); + addCategory(49, "Perks", MakeVector(1, 27), 9); + addCategory(54, "Radio", MakeVector(1, 31), 9); + addCategory(55, "Shouts", MakeVector(1, 104), 9); + addCategory(22, "Skills & Levelling", MakeVector(2, 46, 73), 9); + addCategory(58, "Weather & Lighting", MakeVector(1, 56), 9); + addCategory(44, "Equipment", MakeVector(1, 44), 43); + addCategory(45, "Home/Settlement", MakeVector(1, 45), 43); + addCategory(10, "Body, Face, & Hair", MakeVector(2, 17, 26), 0); + addCategory(39, "Tattoos", MakeVector(1, 57), 10); + addCategory(40, "Character Presets", MakeVector(1, 58), 0); + addCategory(11, "Items", MakeVector(2, 27, 85), 0); + addCategory(32, "Mercantile", MakeVector(2, 23, 69), 0); + addCategory(37, "Ammo", MakeVector(1, 3), 11); + addCategory(19, "Weapons", MakeVector(2, 41, 55), 11); + addCategory(36, "Weapon & Armour Sets", MakeVector(1, 42), 11); + addCategory(23, "Player Homes", MakeVector(2, 28, 67), 0); + addCategory(25, "Castles & Mansions", MakeVector(1, 68), 23); + addCategory(51, "Settlements", MakeVector(1, 48), 23); + addCategory(12, "Locations", MakeVector(10, 20, 21, 22, 30, 47, 70, 88, 89, 90, 91), 0); + addCategory(4, "Cities", MakeVector(1, 53), 12); + addCategory(31, "Landscape Changes", MakeVector(1, 58), 0); + addCategory(29, "Environment", MakeVector(2, 14, 74), 0); + addCategory(30, "Immersion", MakeVector(2, 51, 78), 0); + addCategory(20, "Magic", MakeVector(3, 75, 93, 94), 0); + addCategory(21, "Models & Textures", MakeVector(2, 19, 29), 0); + addCategory(33, "Modders resources", MakeVector(2, 18, 82), 0); + addCategory(13, "NPCs", MakeVector(3, 22, 33, 99), 0); + addCategory(24, "Bugfixes", MakeVector(2, 6, 95), 0); + addCategory(14, "Patches", MakeVector(2, 25, 84), 24); + addCategory(35, "Utilities", MakeVector(2, 38, 39), 0); + addCategory(26, "Cheats", MakeVector(1, 8), 0); + addCategory(15, "Quests", MakeVector(2, 30, 35), 0); + addCategory(16, "Races & Classes", MakeVector(1, 34), 0); + addCategory(34, "Stealth", MakeVector(1, 76), 0); + addCategory(17, "UI", MakeVector(2, 37, 42), 0); + addCategory(18, "Visuals", MakeVector(2, 40, 62), 0); + addCategory(50, "Pip-Boy", MakeVector(1, 52), 18); + addCategory(46, "Shader Presets", MakeVector(3, 13, 97, 105), 0); + addCategory(47, "Miscellaneous", MakeVector(2, 2, 28), 0); } -int CategoryFactory::getParentID(unsigned int index) const { - if (index >= m_Categories.size()) { - throw MyException(QObject::tr("invalid index %1").arg(index)); - } - return m_Categories[index].m_ParentID; +int CategoryFactory::getParentID(unsigned int index) const +{ + if (index >= m_Categories.size()) { + throw MyException(QObject::tr("invalid 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::isDecendantOf(int id, int parentID) const { - 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 isDecendantOf(m_Categories[index].m_ParentID, parentID); - } + +bool CategoryFactory::categoryExists(int id) const +{ + return m_IDMap.find(id) != m_IDMap.end(); +} + + +bool CategoryFactory::isDecendantOf(int id, int parentID) const +{ + 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 { - qWarning("%d is no valid category id", id); - return false; + return isDecendantOf(m_Categories[index].m_ParentID, parentID); } + } else { + qWarning("%d is no valid category id", id); + return false; + } } -bool CategoryFactory::hasChildren(unsigned int index) const { - if (index >= m_Categories.size()) { - throw MyException(QObject::tr("invalid index %1").arg(index)); - } - return m_Categories[index].m_HasChildren; +bool CategoryFactory::hasChildren(unsigned int index) const +{ + if (index >= m_Categories.size()) { + throw MyException(QObject::tr("invalid index %1").arg(index)); + } + + return m_Categories[index].m_HasChildren; } -QString CategoryFactory::getCategoryName(unsigned int index) const { - if (index >= m_Categories.size()) { - throw MyException(QObject::tr("invalid index %1").arg(index)); - } - return m_Categories[index].m_Name; +QString CategoryFactory::getCategoryName(unsigned int index) const +{ + if (index >= m_Categories.size()) { + throw MyException(QObject::tr("invalid index %1").arg(index)); + } + + return m_Categories[index].m_Name; } -int CategoryFactory::getCategoryID(unsigned int index) const { - if (index >= m_Categories.size()) { - throw MyException(QObject::tr("invalid index %1").arg(index)); - } - return m_Categories[index].m_ID; +int CategoryFactory::getCategoryID(unsigned int index) const +{ + if (index >= m_Categories.size()) { + throw MyException(QObject::tr("invalid 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(QObject::tr("invalid category id %1").arg(ID)); - } - return iter->second; + +int CategoryFactory::getCategoryIndex(int ID) const +{ + std::map::const_iterator iter = m_IDMap.find(ID); + if (iter == m_IDMap.end()) { + throw MyException(QObject::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; - } +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 { - std::map::const_iterator iter = m_NexusMap.find(nexusID); - if (iter != m_NexusMap.end()) { - qDebug("nexus category id %d maps to internal %d", nexusID, iter->second); - return iter->second; - } else { - qDebug("nexus category id %d not mapped", nexusID); - return 0U; - } + +unsigned int CategoryFactory::resolveNexusID(int nexusID) const +{ + std::map::const_iterator iter = m_NexusMap.find(nexusID); + if (iter != m_NexusMap.end()) { + qDebug("nexus category id %d maps to internal %d", nexusID, iter->second); + return iter->second; + } else { + qDebug("nexus category id %d not mapped", nexusID); + return 0U; + } } diff --git a/src/categories.h b/src/categories.h index 0ed627d1..66299c30 100644 --- a/src/categories.h +++ b/src/categories.h @@ -20,10 +20,12 @@ along with Mod Organizer. If not, see . #ifndef CATEGORIES_H #define CATEGORIES_H + #include -#include -#include #include +#include +#include + /** * @brief Manage the available mod categories @@ -32,170 +34,180 @@ along with Mod Organizer. If not, see . **/ class CategoryFactory { - friend class CategoriesDialog; + friend class CategoriesDialog; public: - static const int CATEGORY_NONE = 0; - - static const int CATEGORY_SPECIAL_FIRST = 10000; - static const int CATEGORY_SPECIAL_CHECKED = CATEGORY_SPECIAL_FIRST; - static const int CATEGORY_SPECIAL_UNCHECKED = 10001; - static const int CATEGORY_SPECIAL_UPDATEAVAILABLE = 10002; - static const int CATEGORY_SPECIAL_NOCATEGORY = 10003; - static const int CATEGORY_SPECIAL_CONFLICT = 10004; - static const int CATEGORY_SPECIAL_NOTENDORSED = 10005; - static const int CATEGORY_SPECIAL_MANAGED = 10006; - static const int CATEGORY_SPECIAL_UNMANAGED = 10007; + + static const int CATEGORY_NONE = 0; + + static const int CATEGORY_SPECIAL_FIRST = 10000; + static const int CATEGORY_SPECIAL_CHECKED = CATEGORY_SPECIAL_FIRST; + static const int CATEGORY_SPECIAL_UNCHECKED = 10001; + static const int CATEGORY_SPECIAL_UPDATEAVAILABLE = 10002; + static const int CATEGORY_SPECIAL_NOCATEGORY = 10003; + static const int CATEGORY_SPECIAL_CONFLICT = 10004; + static const int CATEGORY_SPECIAL_NOTENDORSED = 10005; + static const int CATEGORY_SPECIAL_MANAGED = 10006; + static const int CATEGORY_SPECIAL_UNMANAGED = 10007; public: - struct Category { - Category(int sortValue, int id, const QString& name, const std::vector& nexusIDs, int parentID) - : m_SortValue(sortValue), m_ID(id), m_Name(name), m_HasChildren(false), m_NexusIDs(nexusIDs), - m_ParentID(parentID) {} - int m_SortValue; - int m_ID; - int m_ParentID; - bool m_HasChildren; - QString m_Name; - std::vector m_NexusIDs; - - friend bool operator<(const Category& LHS, const Category& RHS) { return LHS.m_SortValue < RHS.m_SortValue; } - }; + + struct Category { + Category(int sortValue, int id, const QString &name, const std::vector &nexusIDs, int parentID) + : m_SortValue(sortValue), m_ID(id), m_Name(name), m_HasChildren(false), + m_NexusIDs(nexusIDs), m_ParentID(parentID) {} + int m_SortValue; + int m_ID; + int m_ParentID; + bool m_HasChildren; + QString m_Name; + std::vector m_NexusIDs; + + 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(); - - int addCategory(const QString& name, const std::vector& nexusIDs, 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::tr1::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 isDecendantOf(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; - - /** - * @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; + + /** + * @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(); + + int addCategory(const QString &name, const std::vector &nexusIDs, 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::tr1::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 isDecendantOf(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; + + /** + * @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(); + + /** + * @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(); private: - CategoryFactory(); - void loadDefaultCategories(); + CategoryFactory(); - void addCategory(int id, const QString& name, const std::vector& nexusID, int parentID); + void loadDefaultCategories(); - void setParents(); + void addCategory(int id, const QString &name, const std::vector &nexusID, int parentID); - static void cleanup(); + void setParents(); + + static void cleanup(); private: - static CategoryFactory* s_Instance; - std::vector m_Categories; - std::map m_IDMap; - std::map m_NexusMap; + static CategoryFactory *s_Instance; + + std::vector m_Categories; + std::map m_IDMap; + std::map m_NexusMap; private: + }; + #endif // CATEGORIES_H diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 1629e00d..881179a4 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -18,198 +18,226 @@ along with Mod Organizer. If not, see . */ #include "categoriesdialog.h" -#include "categories.h" #include "ui_categoriesdialog.h" +#include "categories.h" #include "utility.h" #include +#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; + 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; + 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; - } + 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; + 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; + 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); } - 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; + QValidator *m_Validator; }; -CategoriesDialog::CategoriesDialog(QWidget* parent) - : TutorableDialog("Categories", parent), ui(new Ui::CategoriesDialog) { - ui->setupUi(this); - fillTable(); - connect(ui->categoriesTable, SIGNAL(cellChanged(int, int)), this, SLOT(cellChanged(int, int))); + +CategoriesDialog::CategoriesDialog(QWidget *parent) + : TutorableDialog("Categories", parent), ui(new Ui::CategoriesDialog) +{ + ui->setupUi(this); + fillTable(); + connect(ui->categoriesTable, SIGNAL(cellChanged(int,int)), this, SLOT(cellChanged(int,int))); +} + +CategoriesDialog::~CategoriesDialog() +{ + delete ui; } -CategoriesDialog::~CategoriesDialog() { delete ui; } -void CategoriesDialog::cellChanged(int row, int) { - int currentID = ui->categoriesTable->item(row, 0)->text().toInt(); - if (currentID > m_HighestID) { - m_HighestID = currentID; - } +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); - QString nexusIDString = ui->categoriesTable->item(index, 2)->text(); - QStringList nexusIDStringList = nexusIDString.split(',', QString::SkipEmptyParts); - std::vector nexusIDs; - for (QStringList::iterator iter = nexusIDStringList.begin(); iter != nexusIDStringList.end(); ++iter) { - nexusIDs.push_back(iter->toInt()); - } - - categories.addCategory(ui->categoriesTable->item(index, 0)->text().toInt(), - ui->categoriesTable->item(index, 1)->text(), nexusIDs, - ui->categoriesTable->item(index, 3)->text().toInt()); + +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); + QString nexusIDString = ui->categoriesTable->item(index, 2)->text(); + QStringList nexusIDStringList = nexusIDString.split(',', QString::SkipEmptyParts); + std::vector nexusIDs; + for (QStringList::iterator iter = nexusIDStringList.begin(); + iter != nexusIDStringList.end(); ++iter) { + nexusIDs.push_back(iter->toInt()); } - categories.setParents(); - categories.saveCategories(); + categories.addCategory( + ui->categoriesTable->item(index, 0)->text().toInt(), + ui->categoriesTable->item(index, 1)->text(), + nexusIDs, + ui->categoriesTable->item(index, 3)->text().toInt()); + } + categories.setParents(); + + 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::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; - -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); - table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); - table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); - table->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); - table->verticalHeader()->setSectionsMovable(true); - table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); + +void CategoriesDialog::fillTable() +{ + CategoryFactory &categories = CategoryFactory::instance(); + QTableWidget *table = ui->categoriesTable; + +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); + table->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch); + table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); + table->horizontalHeader()->setSectionResizeMode(3, QHeaderView::Fixed); + table->verticalHeader()->setSectionsMovable(true); + table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); #else - table->horizontalHeader()->setResizeMode(0, QHeaderView::Fixed); - table->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch); - table->horizontalHeader()->setResizeMode(2, QHeaderView::Fixed); - table->horizontalHeader()->setResizeMode(3, QHeaderView::Fixed); - table->verticalHeader()->setMovable(true); - table->verticalHeader()->setResizeMode(QHeaderView::Fixed); + table->horizontalHeader()->setResizeMode(0, QHeaderView::Fixed); + table->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch); + table->horizontalHeader()->setResizeMode(2, QHeaderView::Fixed); + table->horizontalHeader()->setResizeMode(3, QHeaderView::Fixed); + table->verticalHeader()->setMovable(true); + table->verticalHeader()->setResizeMode(QHeaderView::Fixed); #endif - table->setItemDelegateForColumn(0, new ValidatingDelegate(this, new NewIDValidator(m_IDs))); - table->setItemDelegateForColumn( - 2, new ValidatingDelegate(this, new QRegExpValidator(QRegExp("([0-9]+)?(,[0-9]+)*"), this))); - table->setItemDelegateForColumn(3, new ValidatingDelegate(this, new ExistingIDValidator(m_IDs))); - - 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 nexusIDItem( - new QTableWidgetItem(MOBase::VectorJoin(category.m_NexusIDs, ","))); - QScopedPointer parentIDItem(new QTableWidgetItem()); - parentIDItem->setData(Qt::DisplayRole, category.m_ParentID); - - table->setItem(row, 0, idItem.take()); - table->setItem(row, 1, nameItem.take()); - table->setItem(row, 2, nexusIDItem.take()); - table->setItem(row, 3, parentIDItem.take()); + + table->setItemDelegateForColumn(0, new ValidatingDelegate(this, new NewIDValidator(m_IDs))); + table->setItemDelegateForColumn(2, new ValidatingDelegate(this, new QRegExpValidator(QRegExp("([0-9]+)?(,[0-9]+)*"), this))); + table->setItemDelegateForColumn(3, new ValidatingDelegate(this, new ExistingIDValidator(m_IDs))); + + 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(" ")); - refreshIDs(); + QScopedPointer idItem(new QTableWidgetItem()); + idItem->setData(Qt::DisplayRole, category.m_ID); + + QScopedPointer nameItem(new QTableWidgetItem(category.m_Name)); + QScopedPointer nexusIDItem(new QTableWidgetItem(MOBase::VectorJoin(category.m_NexusIDs, ","))); + QScopedPointer parentIDItem(new QTableWidgetItem()); + parentIDItem->setData(Qt::DisplayRole, category.m_ParentID); + + table->setItem(row, 0, idItem.take()); + table->setItem(row, 1, nameItem.take()); + table->setItem(row, 2, nexusIDItem.take()); + table->setItem(row, 3, parentIDItem.take()); + } + + 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("")); - ui->categoriesTable->setItem(row, 3, new QTableWidgetItem("0")); + +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("")); + ui->categoriesTable->setItem(row, 3, new QTableWidgetItem("0")); +} + + +void CategoriesDialog::removeCategory_clicked() +{ + ui->categoriesTable->removeRow(m_ContextRow); } -void CategoriesDialog::removeCategory_clicked() { ui->categoriesTable->removeRow(m_ContextRow); } -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())); +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)); + menu.exec(ui->categoriesTable->mapToGlobal(pos)); } diff --git a/src/categoriesdialog.h b/src/categoriesdialog.h index 141eb907..72d2154d 100644 --- a/src/categoriesdialog.h +++ b/src/categoriesdialog.h @@ -30,36 +30,42 @@ class CategoriesDialog; /** * @brief Dialog that allows users to configure mod categories **/ -class CategoriesDialog : public MOBase::TutorableDialog { - Q_OBJECT - +class CategoriesDialog : public MOBase::TutorableDialog +{ + Q_OBJECT + public: - explicit CategoriesDialog(QWidget* parent = 0); - ~CategoriesDialog(); - /** - * @brief store changes here to the global categories store (categories.h) - * - **/ - void commitChanges(); + explicit CategoriesDialog(QWidget *parent = 0); + ~CategoriesDialog(); + + /** + * @brief store changes here to the global categories store (categories.h) + * + **/ + void commitChanges(); private slots: - void on_categoriesTable_customContextMenuRequested(const QPoint& pos); - void addCategory_clicked(); - void removeCategory_clicked(); - void cellChanged(int row, int column); + void on_categoriesTable_customContextMenuRequested(const QPoint &pos); + void addCategory_clicked(); + void removeCategory_clicked(); + void cellChanged(int row, int column); private: - void refreshIDs(); - void fillTable(); + + void refreshIDs(); + void fillTable(); private: - Ui::CategoriesDialog* ui; - int m_ContextRow; - int m_HighestID; - std::set m_IDs; + Ui::CategoriesDialog *ui; + int m_ContextRow; + + int m_HighestID; + std::set m_IDs; + }; #endif // CATEGORIESDIALOG_H + diff --git a/src/credentialsdialog.cpp b/src/credentialsdialog.cpp index fd6554ec..04774548 100644 --- a/src/credentialsdialog.cpp +++ b/src/credentialsdialog.cpp @@ -20,16 +20,34 @@ 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(QWidget *parent) : + QDialog(parent), + ui(new Ui::CredentialsDialog) +{ + ui->setupUi(this); } -CredentialsDialog::~CredentialsDialog() { delete ui; } +CredentialsDialog::~CredentialsDialog() +{ + delete ui; +} -QString CredentialsDialog::username() const { return ui->usernameEdit->text(); } +QString CredentialsDialog::username() const +{ + return ui->usernameEdit->text(); +} -QString CredentialsDialog::password() const { return ui->passwordEdit->text(); } +QString CredentialsDialog::password() const +{ + return ui->passwordEdit->text(); +} -bool CredentialsDialog::store() const { return ui->rememberCheck->isChecked(); } +bool CredentialsDialog::store() const +{ + return ui->rememberCheck->isChecked(); +} -bool CredentialsDialog::neverAsk() const { return ui->dontaskBox->isChecked(); } +bool CredentialsDialog::neverAsk() const +{ + return ui->dontaskBox->isChecked(); +} diff --git a/src/credentialsdialog.h b/src/credentialsdialog.h index 79554fe7..8a68c7d8 100644 --- a/src/credentialsdialog.h +++ b/src/credentialsdialog.h @@ -26,21 +26,22 @@ namespace Ui { class CredentialsDialog; } -class CredentialsDialog : public QDialog { - Q_OBJECT - +class CredentialsDialog : public QDialog +{ + Q_OBJECT + public: - explicit CredentialsDialog(QWidget* parent = 0); - ~CredentialsDialog(); + explicit CredentialsDialog(QWidget *parent = 0); + ~CredentialsDialog(); - QString username() const; - QString password() const; + QString username() const; + QString password() const; - bool store() const; - bool neverAsk() const; + bool store() const; + bool neverAsk() const; private: - Ui::CredentialsDialog* ui; + Ui::CredentialsDialog *ui; }; #endif // CREDENTIALSDIALOG_H diff --git a/src/csvbuilder.cpp b/src/csvbuilder.cpp index 495922bd..fa4218a2 100644 --- a/src/csvbuilder.cpp +++ b/src/csvbuilder.cpp @@ -1,200 +1,248 @@ #include "csvbuilder.h" -CSVBuilder::CSVBuilder(QIODevice* target) : m_Out(target), m_Separator(','), m_LineBreak(BREAK_CRLF) { - m_Out.setCodec("UTF-8"); - m_QuoteMode[TYPE_INTEGER] = QUOTE_NEVER; - m_QuoteMode[TYPE_FLOAT] = QUOTE_NEVER; - m_QuoteMode[TYPE_STRING] = QUOTE_ONDEMAND; +CSVBuilder::CSVBuilder(QIODevice *target) + : m_Out(target), m_Separator(','), m_LineBreak(BREAK_CRLF) +{ + m_Out.setCodec("UTF-8"); + + 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; - } +CSVBuilder::~CSVBuilder() +{ + } -void CSVBuilder::setLineBreak(CSVBuilder::ELineBreak lineBreak) { m_LineBreak = lineBreak; } -void CSVBuilder::setEscapeMode(CSVBuilder::EFieldType type, CSVBuilder::EQuoteMode mode) { m_QuoteMode[type] = mode; } +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::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; - } +void CSVBuilder::setLineBreak(CSVBuilder::ELineBreak lineBreak) +{ + m_LineBreak = lineBreak; +} - checkFields(fieldNames); - m_Fields = fieldNames; - m_FieldTypes = fieldTypes; - m_Defaults.clear(); - m_RowBuffer.clear(); +void CSVBuilder::setEscapeMode(CSVBuilder::EFieldType type, CSVBuilder::EQuoteMode mode) +{ + m_QuoteMode[type] = mode; } -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) { +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)); - } + 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)); - } + 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)); - } + 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::setDefault(const QString& field, const QVariant& value) { - checkValue(field, value); - m_Defaults[field] = value; + +void CSVBuilder::setRowField(const QString &field, const QVariant &value) +{ + checkValue(field, value); + m_RowBuffer[field] = value; } -void CSVBuilder::writeHeader() { - if (m_Fields.size() == 0) { - throw CSVException(QObject::tr("no fields set up yet!")); + +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(); } - for (auto iter = m_Fields.begin(); iter != m_Fields.end(); ++iter) { - if (iter != m_Fields.begin()) { - m_Out << m_Separator; - } - m_Out << *iter; + 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; } - 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; - } + + 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(); + } + m_Out << line << lineBreak(); + m_Out.flush(); } -void CSVBuilder::quoteInsert(QTextStream& stream, int value) { - switch (m_QuoteMode[TYPE_INTEGER]) { + +void CSVBuilder::quoteInsert(QTextStream &stream, int value) +{ + switch (m_QuoteMode[TYPE_INTEGER]) { case QUOTE_NEVER: case QUOTE_ONDEMAND: { - stream << value; + stream << value; } break; case QUOTE_ALWAYS: { - stream << "\"" << value << "\""; + stream << "\"" << value << "\""; } break; - } + } } -void CSVBuilder::quoteInsert(QTextStream& stream, float value) { - switch (m_QuoteMode[TYPE_FLOAT]) { + +void CSVBuilder::quoteInsert(QTextStream &stream, float value) +{ + switch (m_QuoteMode[TYPE_FLOAT]) { case QUOTE_NEVER: case QUOTE_ONDEMAND: { - stream << value; + stream << value; } break; case QUOTE_ALWAYS: { - stream << "\"" << value << "\""; + stream << "\"" << value << "\""; } break; - } + } } -void CSVBuilder::quoteInsert(QTextStream& stream, const QString& value) { - switch (m_QuoteMode[TYPE_STRING]) { + +void CSVBuilder::quoteInsert(QTextStream &stream, const QString &value) +{ + switch (m_QuoteMode[TYPE_STRING]) { case QUOTE_NEVER: { - stream << value; + stream << value; } break; case QUOTE_ONDEMAND: { - if (value.contains("[,\r\n]")) { - stream << "\"" << value.mid(0).replace("\"", "\"\"") << "\""; - } else { - stream << value; - } + if (value.contains("[,\r\n]")) { + stream << "\"" << value.mid(0).replace("\"", "\"\"") << "\""; + } else { + stream << value; + } } break; case QUOTE_ALWAYS: { - stream << "\"" << value.mid(0).replace("\"", "\"\"") << "\""; + stream << "\"" << value.mid(0).replace("\"", "\"\"") << "\""; } break; - } + } +} + + +void CSVBuilder::writeRow() +{ + writeData(m_RowBuffer, false); // data was tested on input + m_RowBuffer.clear(); } -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::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")); - } +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) @@ -207,17 +255,18 @@ if(cell.contains(KDefaultEscapeChar) || cell.contains(KDefaultNewLine) 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::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; } +const char CSVBuilder::separator() +{ + return m_Separator; +} diff --git a/src/csvbuilder.h b/src/csvbuilder.h index 70d712c4..f2e0e987 100644 --- a/src/csvbuilder.h +++ b/src/csvbuilder.h @@ -1,70 +1,93 @@ #ifndef CSVBUILDER_H #define CSVBUILDER_H + +#include #include -#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(); } + CSVException(const QString &text) + : std::exception(), m_Message(text.toLocal8Bit()) {} + virtual const char* what() const throw() + { return m_Message.constData(); } private: - QByteArray m_Message; + QByteArray m_Message; + }; -class CSVBuilder { + +class CSVBuilder +{ public: - enum EFieldType { TYPE_INTEGER, TYPE_STRING, TYPE_FLOAT }; - enum EQuoteMode { QUOTE_NEVER, QUOTE_ONDEMAND, QUOTE_ALWAYS }; + enum EFieldType { + TYPE_INTEGER, + TYPE_STRING, + TYPE_FLOAT + }; - enum ELineBreak { BREAK_LF, BREAK_CRLF, BREAK_CR }; + 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); + 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 writeHeader(); - void setRowField(const QString& field, const QVariant& value); - void writeRow(); + void setRowField(const QString &field, const QVariant &value); + void writeRow(); - void addRow(const std::map& data); + 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); + const char *lineBreak(); + const char separator(); - void quoteInsert(QTextStream& stream, int value); - void quoteInsert(QTextStream& stream, float value); - void quoteInsert(QTextStream& stream, const QString& value); + 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; + + 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/descriptionpage.h b/src/descriptionpage.h index 3956b7e8..f6158ee0 100644 --- a/src/descriptionpage.h +++ b/src/descriptionpage.h @@ -3,14 +3,17 @@ #ifndef DESCRIPTIONPAGE_H #define DESCRIPTIONPAGE_H -class DescriptionPage : public QWebEnginePage { +class DescriptionPage : public QWebEnginePage +{ Q_OBJECT public: - DescriptionPage(QObject* parent = 0) : QWebEnginePage(parent) {} + DescriptionPage(QObject* parent = 0) : QWebEnginePage(parent){} - bool acceptNavigationRequest(const QUrl& url, QWebEnginePage::NavigationType type, bool isMainFrame) { - if (type == QWebEnginePage::NavigationTypeLinkClicked) { + bool acceptNavigationRequest(const QUrl & url, QWebEnginePage::NavigationType type, bool isMainFrame) + { + if (type == QWebEnginePage::NavigationTypeLinkClicked) + { emit linkClicked(url); return false; } @@ -19,6 +22,7 @@ public: signals: void linkClicked(const QUrl&); + }; -#endif // DESCRIPTIONPAGE_H +#endif //DESCRIPTIONPAGE_H diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp index a1df5146..272b0596 100644 --- a/src/directoryrefresher.cpp +++ b/src/directoryrefresher.cpp @@ -20,136 +20,151 @@ along with Mod Organizer. If not, see . #include "directoryrefresher.h" #include "iplugingame.h" -#include "modinfo.h" -#include "report.h" #include "utility.h" +#include "report.h" +#include "modinfo.h" #include #include #include + using namespace MOBase; using namespace MOShared; -DirectoryRefresher::DirectoryRefresher() : m_DirectoryStructure(nullptr) {} -DirectoryRefresher::~DirectoryRefresher() { delete m_DirectoryStructure; } +DirectoryRefresher::DirectoryRefresher() + : m_DirectoryStructure(nullptr) +{ +} + +DirectoryRefresher::~DirectoryRefresher() +{ + delete m_DirectoryStructure; +} -DirectoryEntry* DirectoryRefresher::getDirectoryStructure() { - QMutexLocker locker(&m_RefreshLock); - DirectoryEntry* result = m_DirectoryStructure; - m_DirectoryStructure = nullptr; - return result; +DirectoryEntry *DirectoryRefresher::getDirectoryStructure() +{ + QMutexLocker locker(&m_RefreshLock); + DirectoryEntry *result = m_DirectoryStructure; + m_DirectoryStructure = nullptr; + return result; } -void DirectoryRefresher::setMods(const std::vector>& mods, - const std::set& managedArchives) { - QMutexLocker locker(&m_RefreshLock); +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_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; + 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::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* directoryStructure, const QString& modName, int priority, - const QString& directory, const QStringList& archives) { - std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory)); - - for (const QString& archive : archives) { - QFileInfo fileInfo(archive); - if (m_EnabledArchives.find(fileInfo.fileName()) != m_EnabledArchives.end()) { - try { - // directoryStructure->addFromBSA(ToWString(modName), directoryW, - // ToWString(QDir::toNativeSeparators(fileInfo.absoluteFilePath())), - // priority); - } catch (const std::exception& e) { - throw MyException(tr("failed to parse bsa %1: %2").arg(archive, e.what())); - } - } +void DirectoryRefresher::addModBSAToStructure(DirectoryEntry *directoryStructure, const QString &modName, + int priority, const QString &directory, const QStringList &archives) +{ + std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory)); + + for (const QString &archive : archives) { + QFileInfo fileInfo(archive); + if (m_EnabledArchives.find(fileInfo.fileName()) != m_EnabledArchives.end()) { + try { + //directoryStructure->addFromBSA(ToWString(modName), directoryW, + // ToWString(QDir::toNativeSeparators(fileInfo.absoluteFilePath())), priority); + } catch (const std::exception &e) { + throw MyException(tr("failed to parse bsa %1: %2").arg(archive, e.what())); + } } + } } -void DirectoryRefresher::addModFilesToStructure(DirectoryEntry* directoryStructure, const QString& modName, - int priority, const QString& directory, const QStringList& stealFiles) { - std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory)); - - if (stealFiles.length() > 0) { - // instead of adding all the files of the target directory, we just change the root of the specified - // files to this mod - FilesOrigin& origin = directoryStructure->createOrigin(ToWString(modName), directoryW, priority); - for (const QString& filename : stealFiles) { - QFileInfo fileInfo(filename); - FileEntry::Ptr 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""); - } else { - qWarning("%s not found", qPrintable(fileInfo.fileName())); - } +void DirectoryRefresher::addModFilesToStructure(DirectoryEntry *directoryStructure, const QString &modName, + int priority, const QString &directory, const QStringList &stealFiles) +{ + std::wstring directoryW = ToWString(QDir::toNativeSeparators(directory)); + + if (stealFiles.length() > 0) { + // instead of adding all the files of the target directory, we just change the root of the specified + // files to this mod + FilesOrigin &origin = directoryStructure->createOrigin(ToWString(modName), directoryW, priority); + for (const QString &filename : stealFiles) { + QFileInfo fileInfo(filename); + FileEntry::Ptr 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); } - } else { - directoryStructure->addFromOrigin(ToWString(modName), directoryW, priority); + origin.addFile(file->getIndex()); + file->addOrigin(origin.getID(), file->getFileTime(), L""); + } else { + qWarning("%s not found", qPrintable(fileInfo.fileName())); + } } + } else { + directoryStructure->addFromOrigin(ToWString(modName), directoryW, priority); + } } -void DirectoryRefresher::addModToStructure(DirectoryEntry* directoryStructure, const QString& modName, int priority, - const QString& directory, const QStringList& stealFiles, - const QStringList& archives) { - addModFilesToStructure(directoryStructure, modName, priority, directory, stealFiles); - addModBSAToStructure(directoryStructure, modName, priority, directory, archives); +void DirectoryRefresher::addModToStructure(DirectoryEntry *directoryStructure + , const QString &modName + , int priority + , const QString &directory + , const QStringList &stealFiles + , const QStringList &archives) +{ + addModFilesToStructure(directoryStructure, modName, priority, directory, stealFiles); + addModBSAToStructure(directoryStructure, modName, priority, directory, archives); } -void DirectoryRefresher::refresh() { - QMutexLocker locker(&m_RefreshLock); +void DirectoryRefresher::refresh() +{ + QMutexLocker locker(&m_RefreshLock); - delete m_DirectoryStructure; + delete m_DirectoryStructure; - m_DirectoryStructure = new DirectoryEntry(L"data", nullptr, 0); + m_DirectoryStructure = new DirectoryEntry(L"data", nullptr, 0); - IPluginGame* game = qApp->property("managed_game").value(); + IPluginGame *game = qApp->property("managed_game").value(); - std::wstring dataDirectory = QDir::toNativeSeparators(game->dataDirectory().absolutePath()).toStdWString(); - m_DirectoryStructure->addFromOrigin(L"data", dataDirectory, 0); + std::wstring dataDirectory = QDir::toNativeSeparators(game->dataDirectory().absolutePath()).toStdWString(); + m_DirectoryStructure->addFromOrigin(L"data", dataDirectory, 0); - // TODO what was the point of having the priority in this tuple? the list is already sorted by priority - auto iter = m_Mods.begin(); + // TODO what was the point of having the priority in this tuple? the list is already sorted by priority + auto iter = m_Mods.begin(); - // TODO i is the priority here, where higher = more important. the input vector is also sorted by priority but - // inverted! - for (int i = 1; iter != m_Mods.end(); ++iter, ++i) { - try { - addModToStructure(m_DirectoryStructure, iter->modName, i, iter->absolutePath, iter->stealFiles, - iter->archives); - } catch (const std::exception& e) { - emit error(tr("failed to read mod (%1): %2").arg(iter->modName, e.what())); - } - emit progress((i * 100) / static_cast(m_Mods.size()) + 1); + //TODO i is the priority here, where higher = more important. the input vector is also sorted by priority but inverted! + for (int i = 1; iter != m_Mods.end(); ++iter, ++i) { + try { + addModToStructure(m_DirectoryStructure, iter->modName, i, iter->absolutePath, iter->stealFiles, iter->archives); + } catch (const std::exception &e) { + emit error(tr("failed to read mod (%1): %2").arg(iter->modName, e.what())); } + emit progress((i * 100) / static_cast(m_Mods.size()) + 1); + } - emit progress(100); + emit progress(100); - cleanStructure(m_DirectoryStructure); + cleanStructure(m_DirectoryStructure); - emit refreshed(); + emit refreshed(); } diff --git a/src/directoryrefresher.h b/src/directoryrefresher.h index dbe36aa4..53709e1e 100644 --- a/src/directoryrefresher.h +++ b/src/directoryrefresher.h @@ -20,126 +20,128 @@ along with Mod Organizer. If not, see . #ifndef DIRECTORYREFRESHER_H #define DIRECTORYREFRESHER_H -#include +#include #include +#include #include -#include +#include #include #include -#include + /** * @brief used to asynchronously generate the virtual view of the combined data directory **/ -class DirectoryRefresher : public QObject { +class DirectoryRefresher : public QObject +{ - Q_OBJECT + Q_OBJECT public: - /** - * @brief constructor - * - **/ - DirectoryRefresher(); - - ~DirectoryRefresher(); - - /** - * @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* getDirectoryStructure(); - - /** - * @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); + + /** + * @brief constructor + * + **/ + DirectoryRefresher(); + + ~DirectoryRefresher(); + + /** + * @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 *getDirectoryStructure(); + + /** + * @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); public slots: - /** - * @brief generate a directory structure from the mods set earlier - **/ - void refresh(); + /** + * @brief generate a directory structure from the mods set earlier + **/ + void refresh(); signals: - void progress(int progress); - void error(const QString& error); - void refreshed(); + void progress(int progress); + void error(const QString &error); + void refreshed(); private: - 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; - }; + + 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; + }; private: - std::vector m_Mods; - std::set m_EnabledArchives; - MOShared::DirectoryEntry* m_DirectoryStructure; - QMutex m_RefreshLock; + + std::vector m_Mods; + std::set m_EnabledArchives; + MOShared::DirectoryEntry *m_DirectoryStructure; + QMutex m_RefreshLock; + }; #endif // DIRECTORYREFRESHER_H diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp index 4feffa04..f13cdef1 100644 --- a/src/downloadlist.cpp +++ b/src/downloadlist.cpp @@ -23,71 +23,92 @@ along with Mod Organizer. If not, see . #include -DownloadList::DownloadList(DownloadManager* manager, QObject* parent) - : QAbstractTableModel(parent), m_Manager(manager) { - connect(m_Manager, SIGNAL(update(int)), this, SLOT(update(int))); - connect(m_Manager, SIGNAL(aboutToUpdate()), this, SLOT(aboutToUpdate())); + +DownloadList::DownloadList(DownloadManager *manager, QObject *parent) + : QAbstractTableModel(parent), m_Manager(manager) +{ + connect(m_Manager, SIGNAL(update(int)), this, SLOT(update(int))); + connect(m_Manager, SIGNAL(aboutToUpdate()), this, SLOT(aboutToUpdate())); } -int DownloadList::rowCount(const QModelIndex&) const { - return m_Manager->numTotalDownloads() + m_Manager->numPendingDownloads(); + +int DownloadList::rowCount(const QModelIndex&) const +{ + return m_Manager->numTotalDownloads() + m_Manager->numPendingDownloads(); } -int DownloadList::columnCount(const QModelIndex&) const { return 3; } -QModelIndex DownloadList::index(int row, int column, const QModelIndex&) const { return createIndex(row, column, row); } +int DownloadList::columnCount(const QModelIndex&) const +{ + return 3; +} -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_FILETIME: - return tr("Filetime"); - default: - return tr("Done"); - } - } else { - return QAbstractItemModel::headerData(section, orientation, role); +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_FILETIME: return tr("Filetime"); + default: return tr("Done"); } + } else { + return QAbstractItemModel::headerData(section, orientation, role); + } } -QVariant DownloadList::data(const QModelIndex& index, int role) const { - if (role == Qt::DisplayRole) { - return index.row(); - } else if (role == Qt::ToolTipRole) { - if (index.row() < m_Manager->numTotalDownloads()) { - 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); - } - return text; - } else { - return tr("pending download"); - } + +QVariant DownloadList::data(const QModelIndex &index, int role) const +{ + if (role == Qt::DisplayRole) { + return index.row(); + } else if (role == Qt::ToolTipRole) { + if (index.row() < m_Manager->numTotalDownloads()) { + 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); + } + return text; } else { - return QVariant(); + return tr("pending download"); } + } else { + return QVariant(); + } +} + + +void DownloadList::aboutToUpdate() +{ + emit beginResetModel(); } -void DownloadList::aboutToUpdate() { emit beginResetModel(); } -void DownloadList::update(int row) { - if (row < 0) { - emit endResetModel(); - } else if (row < this->rowCount()) { +void DownloadList::update(int row) +{ + if (row < 0) { + emit endResetModel(); + } else if (row < this->rowCount()) { #pragma message("updating only the one column is a hack") - emit dataChanged(this->index(row, 2, QModelIndex()), this->index(row, 2, QModelIndex())); - } else { - qCritical("invalid row %d in download list, update failed", row); - } + emit dataChanged(this->index(row, 2, QModelIndex()), this->index(row, 2, QModelIndex())); + } else { + qCritical("invalid row %d in download list, update failed", row); + } } + diff --git a/src/downloadlist.h b/src/downloadlist.h index 4765a098..2316dddc 100644 --- a/src/downloadlist.h +++ b/src/downloadlist.h @@ -22,65 +22,75 @@ along with Mod Organizer. If not, see . #include + class DownloadManager; + /** * @brief model of the list of active and completed downloads **/ -class DownloadList : public QAbstractTableModel { +class DownloadList : public QAbstractTableModel +{ - Q_OBJECT + Q_OBJECT public: - enum EColumn { COL_NAME = 0, COL_FILETIME, COL_STATUS }; + + enum EColumn { + COL_NAME = 0, + COL_FILETIME, + COL_STATUS + }; public: - /** - * @brief constructor - * - * @param manager the download manager processing downloads - * @param parent parent object Defaults to 0. - **/ - explicit DownloadList(DownloadManager* manager, 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; - - 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; + + /** + * @brief constructor + * + * @param manager the download manager processing downloads + * @param parent parent object Defaults to 0. + **/ + explicit DownloadList(DownloadManager *manager, 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; + + 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; 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); + /** + * @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(); + void aboutToUpdate(); private: - DownloadManager* m_Manager; + + DownloadManager *m_Manager; + }; #endif // DOWNLOADLIST_H diff --git a/src/downloadlistsortproxy.cpp b/src/downloadlistsortproxy.cpp index bef8ffc2..2780f973 100644 --- a/src/downloadlistsortproxy.cpp +++ b/src/downloadlistsortproxy.cpp @@ -22,43 +22,52 @@ along with Mod Organizer. If not, see . #include "downloadmanager.h" #include "settings.h" -DownloadListSortProxy::DownloadListSortProxy(const DownloadManager* manager, QObject* parent) - : QSortFilterProxyModel(parent), m_Manager(manager), m_CurrentFilter() {} +DownloadListSortProxy::DownloadListSortProxy(const DownloadManager *manager, QObject *parent) + : QSortFilterProxyModel(parent), m_Manager(manager), m_CurrentFilter() +{ +} -void DownloadListSortProxy::updateFilter(const QString& filter) { - m_CurrentFilter = filter; - invalidateFilter(); +void DownloadListSortProxy::updateFilter(const QString &filter) +{ + m_CurrentFilter = filter; + invalidateFilter(); } -bool DownloadListSortProxy::lessThan(const QModelIndex& left, const QModelIndex& right) const { - int leftIndex = left.data().toInt(); - int rightIndex = right.data().toInt(); - if ((leftIndex < m_Manager->numTotalDownloads()) && (rightIndex < m_Manager->numTotalDownloads())) { - if (left.column() == DownloadList::COL_NAME) { - return m_Manager->getFileName(leftIndex).compare(m_Manager->getFileName(rightIndex), Qt::CaseInsensitive) < - 0; - } else if (left.column() == DownloadList::COL_FILETIME) { - return m_Manager->getFileTime(leftIndex) < m_Manager->getFileTime(rightIndex); - } else if (left.column() == DownloadList::COL_STATUS) { - return m_Manager->getState(leftIndex) < m_Manager->getState(rightIndex); - } else { - return leftIndex < rightIndex; - } + +bool DownloadListSortProxy::lessThan(const QModelIndex &left, + const QModelIndex &right) const +{ + int leftIndex = left.data().toInt(); + int rightIndex = right.data().toInt(); + if ((leftIndex < m_Manager->numTotalDownloads()) + && (rightIndex < m_Manager->numTotalDownloads())) { + if (left.column() == DownloadList::COL_NAME) { + return m_Manager->getFileName(leftIndex).compare(m_Manager->getFileName(rightIndex), Qt::CaseInsensitive) < 0; + } else if (left.column() == DownloadList::COL_FILETIME) { + return m_Manager->getFileTime(leftIndex) < m_Manager->getFileTime(rightIndex); + } else if (left.column() == DownloadList::COL_STATUS) { + return m_Manager->getState(leftIndex) < m_Manager->getState(rightIndex); } else { - return leftIndex < rightIndex; + return leftIndex < rightIndex; } + } else { + return leftIndex < rightIndex; + } } -bool DownloadListSortProxy::filterAcceptsRow(int sourceRow, const QModelIndex&) const { - if (m_CurrentFilter.length() == 0) { - return true; - } else if (sourceRow < m_Manager->numTotalDownloads()) { - int downloadIndex = sourceModel()->index(sourceRow, 0).data().toInt(); - QString displayedName = Settings::instance().metaDownloads() ? m_Manager->getDisplayName(downloadIndex) - : m_Manager->getFileName(downloadIndex); - return displayedName.contains(m_CurrentFilter, Qt::CaseInsensitive); - } else { - return false; - } +bool DownloadListSortProxy::filterAcceptsRow(int sourceRow, const QModelIndex&) const +{ + if (m_CurrentFilter.length() == 0) { + return true; + } else if (sourceRow < m_Manager->numTotalDownloads()) { + int downloadIndex = sourceModel()->index(sourceRow, 0).data().toInt(); + + QString displayedName = Settings::instance().metaDownloads() + ? m_Manager->getDisplayName(downloadIndex) + : m_Manager->getFileName(downloadIndex); + return displayedName.contains(m_CurrentFilter, Qt::CaseInsensitive); + } else { + return false; + } } diff --git a/src/downloadlistsortproxy.h b/src/downloadlistsortproxy.h index 9cb16a83..59f46179 100644 --- a/src/downloadlistsortproxy.h +++ b/src/downloadlistsortproxy.h @@ -20,30 +20,39 @@ along with Mod Organizer. If not, see . #ifndef DOWNLOADLISTSORTPROXY_H #define DOWNLOADLISTSORTPROXY_H + #include + class DownloadManager; -class DownloadListSortProxy : public QSortFilterProxyModel { - Q_OBJECT + +class DownloadListSortProxy : public QSortFilterProxyModel +{ + Q_OBJECT public: - explicit DownloadListSortProxy(const DownloadManager* manager, QObject* parent = 0); + + explicit DownloadListSortProxy(const DownloadManager *manager, QObject *parent = 0); public slots: - void updateFilter(const QString& filter); + void updateFilter(const QString &filter); protected: - bool lessThan(const QModelIndex& left, const QModelIndex& right) const; - bool filterAcceptsRow(int sourceRow, const QModelIndex& source_parent) const; -signals: + bool lessThan(const QModelIndex &left, const QModelIndex &right) const; + bool filterAcceptsRow(int sourceRow, const QModelIndex &source_parent) const; +signals: + public slots: private: - const DownloadManager* m_Manager; - QString m_CurrentFilter; + + const DownloadManager *m_Manager; + QString m_CurrentFilter; + + }; #endif // DOWNLOADLISTSORTPROXY_H diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index 29b027a4..9a8ef572 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -19,280 +19,332 @@ along with Mod Organizer. If not, see . #include "downloadlistwidget.h" #include "ui_downloadlistwidget.h" +#include +#include #include #include -#include -#include #include -DownloadListWidget::DownloadListWidget(QWidget* parent) : QWidget(parent), ui(new Ui::DownloadListWidget) { - ui->setupUi(this); + +DownloadListWidget::DownloadListWidget(QWidget *parent) + : QWidget(parent), ui(new Ui::DownloadListWidget) +{ + ui->setupUi(this); } -DownloadListWidget::~DownloadListWidget() { delete ui; } -DownloadListWidgetDelegate::DownloadListWidgetDelegate(DownloadManager* manager, bool metaDisplay, QTreeView* view, - QObject* parent) - : QItemDelegate(parent), m_Manager(manager), m_MetaDisplay(metaDisplay), m_ItemWidget(new DownloadListWidget), - m_ContextRow(0), m_View(view) { - m_NameLabel = m_ItemWidget->findChild("nameLabel"); - m_SizeLabel = m_ItemWidget->findChild("sizeLabel"); - m_Progress = m_ItemWidget->findChild("downloadProgress"); - m_InstallLabel = m_ItemWidget->findChild("installLabel"); +DownloadListWidget::~DownloadListWidget() +{ + delete ui; +} - m_InstallLabel->setVisible(false); - connect(manager, SIGNAL(stateChanged(int, DownloadManager::DownloadState)), this, - SLOT(stateChanged(int, DownloadManager::DownloadState))); - connect(manager, SIGNAL(downloadRemoved(int)), this, SLOT(resetCache(int))); +DownloadListWidgetDelegate::DownloadListWidgetDelegate(DownloadManager *manager, bool metaDisplay, QTreeView *view, QObject *parent) + : QItemDelegate(parent) + , m_Manager(manager) + , m_MetaDisplay(metaDisplay) + , m_ItemWidget(new DownloadListWidget) + , m_ContextRow(0) + , m_View(view) +{ + m_NameLabel = m_ItemWidget->findChild("nameLabel"); + m_SizeLabel = m_ItemWidget->findChild("sizeLabel"); + m_Progress = m_ItemWidget->findChild("downloadProgress"); + m_InstallLabel = m_ItemWidget->findChild("installLabel"); + + m_InstallLabel->setVisible(false); + + connect(manager, SIGNAL(stateChanged(int,DownloadManager::DownloadState)), this, SLOT(stateChanged(int,DownloadManager::DownloadState))); + connect(manager, SIGNAL(downloadRemoved(int)), this, SLOT(resetCache(int))); } -DownloadListWidgetDelegate::~DownloadListWidgetDelegate() { delete m_ItemWidget; } -void DownloadListWidgetDelegate::stateChanged(int row, DownloadManager::DownloadState) { m_Cache.remove(row); } +DownloadListWidgetDelegate::~DownloadListWidgetDelegate() +{ + delete m_ItemWidget; +} -void DownloadListWidgetDelegate::resetCache(int) { m_Cache.clear(); } -void DownloadListWidgetDelegate::drawCache(QPainter* painter, const QStyleOptionViewItem& option, - const QPixmap& cache) const { - QRect rect = option.rect; - rect.setLeft(0); - rect.setWidth(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2)); - painter->drawPixmap(rect, cache); +void DownloadListWidgetDelegate::stateChanged(int row,DownloadManager::DownloadState) +{ + m_Cache.remove(row); } -void DownloadListWidgetDelegate::paintPendingDownload(int downloadIndex) const { - std::pair nexusids = m_Manager->getPendingDownload(downloadIndex); - m_NameLabel->setText(tr("< mod %1 file %2 >").arg(nexusids.first).arg(nexusids.second)); - m_SizeLabel->setText("???"); - m_InstallLabel->setVisible(true); - m_InstallLabel->setText(tr("Pending")); - m_Progress->setVisible(false); + +void DownloadListWidgetDelegate::resetCache(int) +{ + m_Cache.clear(); } -void DownloadListWidgetDelegate::paintRegularDownload(int downloadIndex) const { - QString name = m_MetaDisplay ? m_Manager->getDisplayName(downloadIndex) : m_Manager->getFileName(downloadIndex); - if (name.length() > 53) { - name.truncate(50); - name.append("..."); - } - m_NameLabel->setText(name); - m_SizeLabel->setText(QString::number(m_Manager->getFileSize(downloadIndex) / 1024)); - DownloadManager::DownloadState state = m_Manager->getState(downloadIndex); - if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { - QPalette labelPalette; - m_InstallLabel->setVisible(true); - m_Progress->setVisible(false); -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Paused - Double Click to resume", 0)); + +void DownloadListWidgetDelegate::drawCache(QPainter *painter, const QStyleOptionViewItem &option, const QPixmap &cache) const +{ + QRect rect = option.rect; + rect.setLeft(0); + rect.setWidth(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2)); + painter->drawPixmap(rect, cache); +} + + +void DownloadListWidgetDelegate::paintPendingDownload(int downloadIndex) const +{ + std::pair nexusids = m_Manager->getPendingDownload(downloadIndex); + m_NameLabel->setText(tr("< mod %1 file %2 >").arg(nexusids.first).arg(nexusids.second)); + m_SizeLabel->setText("???"); + m_InstallLabel->setVisible(true); + m_InstallLabel->setText(tr("Pending")); + m_Progress->setVisible(false); +} + + +void DownloadListWidgetDelegate::paintRegularDownload(int downloadIndex) const +{ + QString name = m_MetaDisplay ? m_Manager->getDisplayName(downloadIndex) : m_Manager->getFileName(downloadIndex); + if (name.length() > 53) { + name.truncate(50); + name.append("..."); + } + m_NameLabel->setText(name); + m_SizeLabel->setText(QString::number(m_Manager->getFileSize(downloadIndex) / 1024)); + DownloadManager::DownloadState state = m_Manager->getState(downloadIndex); + if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { + QPalette labelPalette; + m_InstallLabel->setVisible(true); + m_Progress->setVisible(false); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Paused - Double Click to resume", 0)); #else - m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Paused - Double Click to resume", 0, - QApplication::UnicodeUTF8)); + m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Paused - Double Click to resume", 0, QApplication::UnicodeUTF8)); #endif - labelPalette.setColor(QPalette::WindowText, Qt::darkRed); - m_InstallLabel->setPalette(labelPalette); - } else if (state == DownloadManager::STATE_FETCHINGMODINFO) { - m_InstallLabel->setText(tr("Fetching Info 1")); - m_Progress->setVisible(false); - } else if (state == DownloadManager::STATE_FETCHINGFILEINFO) { - m_InstallLabel->setText(tr("Fetching Info 2")); - m_Progress->setVisible(false); - } else if (state >= DownloadManager::STATE_READY) { - QPalette labelPalette; - m_InstallLabel->setVisible(true); - m_Progress->setVisible(false); - if (state == DownloadManager::STATE_INSTALLED) { - // the tr-macro doesn't work here, maybe because the translation is actually associated with DownloadListWidget - // instead of DownloadListWidgetDelegate? -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - m_InstallLabel->setText( - QApplication::translate("DownloadListWidget", "Installed - Double Click to re-install", 0)); + labelPalette.setColor(QPalette::WindowText, Qt::darkRed); + m_InstallLabel->setPalette(labelPalette); + } else if (state == DownloadManager::STATE_FETCHINGMODINFO) { + m_InstallLabel->setText(tr("Fetching Info 1")); + m_Progress->setVisible(false); + } else if (state == DownloadManager::STATE_FETCHINGFILEINFO) { + m_InstallLabel->setText(tr("Fetching Info 2")); + m_Progress->setVisible(false); + } else if (state >= DownloadManager::STATE_READY) { + QPalette labelPalette; + m_InstallLabel->setVisible(true); + m_Progress->setVisible(false); + if (state == DownloadManager::STATE_INSTALLED) { + // the tr-macro doesn't work here, maybe because the translation is actually associated with DownloadListWidget instead + // of DownloadListWidgetDelegate? +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Installed - Double Click to re-install", 0)); #else - m_InstallLabel->setText(QApplication::translate( - "DownloadListWidget", "Installed - Double Click to re-install", 0, QApplication::UnicodeUTF8)); + m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Installed - Double Click to re-install", 0, QApplication::UnicodeUTF8)); #endif - labelPalette.setColor(QPalette::WindowText, Qt::darkGray); - } else if (state == DownloadManager::STATE_UNINSTALLED) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - m_InstallLabel->setText( - QApplication::translate("DownloadListWidget", "Uninstalled - Double Click to re-install", 0)); + labelPalette.setColor(QPalette::WindowText, Qt::darkGray); + } else if (state == DownloadManager::STATE_UNINSTALLED) { +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Uninstalled - Double Click to re-install", 0)); #else - m_InstallLabel->setText(QApplication::translate( - "DownloadListWidget", "Uninstalled - Double Click to re-install", 0, QApplication::UnicodeUTF8)); + m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Uninstalled - Double Click to re-install", 0, QApplication::UnicodeUTF8)); #endif - labelPalette.setColor(QPalette::WindowText, Qt::lightGray); - } else { -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Done - Double Click to install", 0)); + labelPalette.setColor(QPalette::WindowText, Qt::lightGray); + } else { +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Done - Double Click to install", 0)); #else - m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Done - Double Click to install", 0, - QApplication::UnicodeUTF8)); + m_InstallLabel->setText(QApplication::translate("DownloadListWidget", "Done - Double Click to install", 0, QApplication::UnicodeUTF8)); #endif - labelPalette.setColor(QPalette::WindowText, Qt::darkGreen); - } - m_InstallLabel->setPalette(labelPalette); - if (m_Manager->isInfoIncomplete(downloadIndex)) { - m_NameLabel->setText(" " + m_NameLabel->text()); - } - } else { - m_InstallLabel->setVisible(false); - m_Progress->setVisible(true); - m_Progress->setValue(m_Manager->getProgress(downloadIndex)); + labelPalette.setColor(QPalette::WindowText, Qt::darkGreen); + } + m_InstallLabel->setPalette(labelPalette); + if (m_Manager->isInfoIncomplete(downloadIndex)) { + m_NameLabel->setText(" " + m_NameLabel->text()); } + } else { + m_InstallLabel->setVisible(false); + m_Progress->setVisible(true); + m_Progress->setValue(m_Manager->getProgress(downloadIndex)); + } } -void DownloadListWidgetDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const { - try { - auto iter = m_Cache.find(index.row()); - if (iter != m_Cache.end()) { - drawCache(painter, option, *iter); - return; - } +void DownloadListWidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + try { + auto iter = m_Cache.find(index.row()); + if (iter != m_Cache.end()) { + drawCache(painter, option, *iter); + return; + } - m_ItemWidget->resize( - QSize(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2), option.rect.height())); + m_ItemWidget->resize(QSize(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2), option.rect.height())); - int downloadIndex = index.data().toInt(); + int downloadIndex = index.data().toInt(); - if (downloadIndex >= m_Manager->numTotalDownloads()) { - paintPendingDownload(downloadIndex - m_Manager->numTotalDownloads()); - } else { - paintRegularDownload(downloadIndex); - } + if (downloadIndex >= m_Manager->numTotalDownloads()) { + paintPendingDownload(downloadIndex - m_Manager->numTotalDownloads()); + } else { + paintRegularDownload(downloadIndex); + } #pragma message("caching disabled because changes in the list (including resorting) doesn't work correctly") - // if (state >= DownloadManager::STATE_READY) { - if (false) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - QPixmap cache = m_ItemWidget->grab(); +// if (state >= DownloadManager::STATE_READY) { + if (false) { +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + QPixmap cache = m_ItemWidget->grab(); #else - QPixmap cache = QPixmap::grabWidget(m_ItemWidget); + QPixmap cache = QPixmap::grabWidget(m_ItemWidget); #endif - m_Cache[index.row()] = cache; - drawCache(painter, option, cache); - } else { - painter->save(); - painter->translate(QPoint(0, option.rect.topLeft().y())); - - m_ItemWidget->render(painter); - painter->restore(); - } - } catch (const std::exception& e) { - qCritical("failed to paint download list: %s", e.what()); + m_Cache[index.row()] = cache; + drawCache(painter, option, cache); + } else { + painter->save(); + painter->translate(QPoint(0, option.rect.topLeft().y())); + + m_ItemWidget->render(painter); + painter->restore(); } + } catch (const std::exception &e) { + qCritical("failed to paint download list: %s", e.what()); + } } -QSize DownloadListWidgetDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const { - const int width = m_ItemWidget->minimumWidth(); - const int height = m_ItemWidget->height(); - return QSize(width, height); +QSize DownloadListWidgetDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const +{ + const int width = m_ItemWidget->minimumWidth(); + const int height = m_ItemWidget->height(); + return QSize(width, height); } -void DownloadListWidgetDelegate::issueInstall() { emit installDownload(m_ContextRow); } -void DownloadListWidgetDelegate::issueQueryInfo() { emit queryInfo(m_ContextRow); } +void DownloadListWidgetDelegate::issueInstall() +{ + emit installDownload(m_ContextRow); +} + +void DownloadListWidgetDelegate::issueQueryInfo() +{ + emit queryInfo(m_ContextRow); +} -void DownloadListWidgetDelegate::issueDelete() { emit removeDownload(m_ContextRow, true); } +void DownloadListWidgetDelegate::issueDelete() +{ + emit removeDownload(m_ContextRow, true); +} -void DownloadListWidgetDelegate::issueRemoveFromView() { emit removeDownload(m_ContextRow, false); } +void DownloadListWidgetDelegate::issueRemoveFromView() +{ + emit removeDownload(m_ContextRow, false); +} -void DownloadListWidgetDelegate::issueRestoreToView() { emit restoreDownload(m_ContextRow); } +void DownloadListWidgetDelegate::issueRestoreToView() +{ + emit restoreDownload(m_ContextRow); +} -void DownloadListWidgetDelegate::issueCancel() { emit cancelDownload(m_ContextRow); } +void DownloadListWidgetDelegate::issueCancel() +{ + emit cancelDownload(m_ContextRow); +} -void DownloadListWidgetDelegate::issuePause() { emit pauseDownload(m_ContextRow); } +void DownloadListWidgetDelegate::issuePause() +{ + emit pauseDownload(m_ContextRow); +} -void DownloadListWidgetDelegate::issueResume() { emit resumeDownload(m_ContextRow); } +void DownloadListWidgetDelegate::issueResume() +{ + emit resumeDownload(m_ContextRow); +} -void DownloadListWidgetDelegate::issueDeleteAll() { - if (QMessageBox::question(nullptr, tr("Are you sure?"), - tr("This will remove all finished downloads from this list and from disk."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-1, true); - } +void DownloadListWidgetDelegate::issueDeleteAll() +{ + if (QMessageBox::question(nullptr, tr("Are you sure?"), + tr("This will remove all finished downloads from this list and from disk."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-1, true); + } } -void DownloadListWidgetDelegate::issueDeleteCompleted() { - if (QMessageBox::question(nullptr, tr("Are you sure?"), - tr("This will remove all installed downloads from this list and from disk."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-2, true); - } +void DownloadListWidgetDelegate::issueDeleteCompleted() +{ + if (QMessageBox::question(nullptr, tr("Are you sure?"), + tr("This will remove all installed downloads from this list and from disk."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-2, true); + } } -void DownloadListWidgetDelegate::issueRemoveFromViewAll() { - if (QMessageBox::question(nullptr, tr("Are you sure?"), - 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 DownloadListWidgetDelegate::issueRemoveFromViewAll() +{ + if (QMessageBox::question(nullptr, tr("Are you sure?"), + 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 DownloadListWidgetDelegate::issueRemoveFromViewCompleted() { - if (QMessageBox::question(nullptr, tr("Are you sure?"), - 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 DownloadListWidgetDelegate::issueRemoveFromViewCompleted() +{ + if (QMessageBox::question(nullptr, tr("Are you sure?"), + tr("This will remove all installed downloads from this list (but NOT from disk)."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-2, false); + } } -bool DownloadListWidgetDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, - const QStyleOptionViewItem& option, const QModelIndex& index) { - try { - if (event->type() == QEvent::MouseButtonDblClick) { - 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) { - emit resumeDownload(sourceIndex.row()); +bool DownloadListWidgetDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index) +{ + try { + if (event->type() == QEvent::MouseButtonDblClick) { + 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) { + emit resumeDownload(sourceIndex.row()); + } + return true; + } else if (event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::RightButton) { + QMenu menu(m_View); + bool hidden = false; + m_ContextRow = qobject_cast(model)->mapToSource(index).row(); + if (m_ContextRow < m_Manager->numTotalDownloads()) { + DownloadManager::DownloadState state = m_Manager->getState(m_ContextRow); + hidden = m_Manager->isHidden(m_ContextRow); + if (state >= DownloadManager::STATE_READY) { + menu.addAction(tr("Install"), this, SLOT(issueInstall())); + if (m_Manager->isInfoIncomplete(m_ContextRow)) { + menu.addAction(tr("Query Info"), this, SLOT(issueQueryInfo())); } - return true; - } else if (event->type() == QEvent::MouseButtonRelease) { - QMouseEvent* mouseEvent = static_cast(event); - if (mouseEvent->button() == Qt::RightButton) { - QMenu menu(m_View); - bool hidden = false; - m_ContextRow = qobject_cast(model)->mapToSource(index).row(); - if (m_ContextRow < m_Manager->numTotalDownloads()) { - DownloadManager::DownloadState state = m_Manager->getState(m_ContextRow); - hidden = m_Manager->isHidden(m_ContextRow); - if (state >= DownloadManager::STATE_READY) { - menu.addAction(tr("Install"), this, SLOT(issueInstall())); - if (m_Manager->isInfoIncomplete(m_ContextRow)) { - menu.addAction(tr("Query Info"), this, SLOT(issueQueryInfo())); - } - menu.addAction(tr("Delete"), this, SLOT(issueDelete())); - if (hidden) { - menu.addAction(tr("Un-Hide"), this, SLOT(issueRestoreToView())); - } else { - menu.addAction(tr("Hide"), this, SLOT(issueRemoveFromView())); - } - } else if (state == DownloadManager::STATE_DOWNLOADING) { - menu.addAction(tr("Cancel"), this, SLOT(issueCancel())); - menu.addAction(tr("Pause"), this, SLOT(issuePause())); - } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { - menu.addAction(tr("Remove"), this, SLOT(issueDelete())); - menu.addAction(tr("Resume"), this, SLOT(issueResume())); - } - - menu.addSeparator(); - } - menu.addAction(tr("Delete Installed..."), this, SLOT(issueDeleteCompleted())); - menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll())); - if (!hidden) { - menu.addSeparator(); - menu.addAction(tr("Hide Installed..."), this, SLOT(issueRemoveFromViewCompleted())); - menu.addAction(tr("Hide All..."), this, SLOT(issueRemoveFromViewAll())); - } - menu.exec(mouseEvent->globalPos()); - - event->accept(); - return true; + menu.addAction(tr("Delete"), this, SLOT(issueDelete())); + if (hidden) { + menu.addAction(tr("Un-Hide"), this, SLOT(issueRestoreToView())); + } else { + menu.addAction(tr("Hide"), this, SLOT(issueRemoveFromView())); } + } else if (state == DownloadManager::STATE_DOWNLOADING){ + menu.addAction(tr("Cancel"), this, SLOT(issueCancel())); + menu.addAction(tr("Pause"), this, SLOT(issuePause())); + } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { + menu.addAction(tr("Remove"), this, SLOT(issueDelete())); + menu.addAction(tr("Resume"), this, SLOT(issueResume())); + } + + menu.addSeparator(); + } + menu.addAction(tr("Delete Installed..."), this, SLOT(issueDeleteCompleted())); + menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll())); + if (!hidden) { + menu.addSeparator(); + menu.addAction(tr("Hide Installed..."), this, SLOT(issueRemoveFromViewCompleted())); + menu.addAction(tr("Hide All..."), this, SLOT(issueRemoveFromViewAll())); } - } catch (const std::exception& e) { - qCritical("failed to handle editor event: %s", e.what()); + menu.exec(mouseEvent->globalPos()); + + event->accept(); + return true; + } } - return QItemDelegate::editorEvent(event, model, option, index); + } catch (const std::exception &e) { + qCritical("failed to handle editor event: %s", e.what()); + } + return QItemDelegate::editorEvent(event, model, option, index); } diff --git a/src/downloadlistwidget.h b/src/downloadlistwidget.h index 5f7cdc79..c1dfe4cd 100644 --- a/src/downloadlistwidget.h +++ b/src/downloadlistwidget.h @@ -21,93 +21,100 @@ along with Mod Organizer. If not, see . #define DOWNLOADLISTWIDGET_H #include "downloadmanager.h" +#include #include #include #include #include -#include namespace Ui { -class DownloadListWidget; + class DownloadListWidget; } -class DownloadListWidget : public QWidget { +class DownloadListWidget : public QWidget +{ Q_OBJECT public: - explicit DownloadListWidget(QWidget* parent = 0); + explicit DownloadListWidget(QWidget *parent = 0); ~DownloadListWidget(); + private: - Ui::DownloadListWidget* ui; + Ui::DownloadListWidget *ui; }; class DownloadManager; -class DownloadListWidgetDelegate : public QItemDelegate { +class DownloadListWidgetDelegate : public QItemDelegate +{ - Q_OBJECT + Q_OBJECT public: - DownloadListWidgetDelegate(DownloadManager* manager, bool metaDisplay, QTreeView* view, QObject* parent = 0); - ~DownloadListWidgetDelegate(); - virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; - virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; + DownloadListWidgetDelegate(DownloadManager *manager, bool metaDisplay, QTreeView *view, QObject *parent = 0); + ~DownloadListWidgetDelegate(); - void paintPendingDownload(int downloadIndex) const; - void paintRegularDownload(int downloadIndex) const; + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; + + void paintPendingDownload(int downloadIndex) const; + void paintRegularDownload(int downloadIndex) const; signals: - void installDownload(int index); - void queryInfo(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 installDownload(int index); + void queryInfo(int index); + void removeDownload(int index, bool deleteFile); + void restoreDownload(int index); + void cancelDownload(int index); + void pauseDownload(int index); + void resumeDownload(int index); protected: - bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, - const QModelIndex& index); + + bool editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index); private: - void drawCache(QPainter* painter, const QStyleOptionViewItem& option, const QPixmap& cache) const; + + void drawCache(QPainter *painter, const QStyleOptionViewItem &option, const QPixmap &cache) const; private slots: - void issueInstall(); - void issueDelete(); - void issueRemoveFromView(); - void issueRestoreToView(); - void issueCancel(); - void issuePause(); - void issueResume(); - void issueDeleteAll(); - void issueDeleteCompleted(); - void issueRemoveFromViewAll(); - void issueRemoveFromViewCompleted(); - void issueQueryInfo(); - - void stateChanged(int row, DownloadManager::DownloadState); - void resetCache(int); + void issueInstall(); + void issueDelete(); + void issueRemoveFromView(); + void issueRestoreToView(); + void issueCancel(); + void issuePause(); + void issueResume(); + void issueDeleteAll(); + void issueDeleteCompleted(); + void issueRemoveFromViewAll(); + void issueRemoveFromViewCompleted(); + void issueQueryInfo(); + + void stateChanged(int row, DownloadManager::DownloadState); + void resetCache(int); private: - DownloadListWidget* m_ItemWidget; - DownloadManager* m_Manager; - bool m_MetaDisplay; + DownloadListWidget *m_ItemWidget; + DownloadManager *m_Manager; + + bool m_MetaDisplay; - QLabel* m_NameLabel; - QLabel* m_SizeLabel; - QProgressBar* m_Progress; - QLabel* m_InstallLabel; - int m_ContextRow; + QLabel *m_NameLabel; + QLabel *m_SizeLabel; + QProgressBar *m_Progress; + QLabel *m_InstallLabel; + int m_ContextRow; - QTreeView* m_View; + QTreeView *m_View; - mutable QMap m_Cache; + mutable QMap m_Cache; }; #endif // DOWNLOADLISTWIDGET_H diff --git a/src/downloadlistwidgetcompact.cpp b/src/downloadlistwidgetcompact.cpp index 30e3ed21..f7975150 100644 --- a/src/downloadlistwidgetcompact.cpp +++ b/src/downloadlistwidgetcompact.cpp @@ -19,262 +19,319 @@ along with Mod Organizer. If not, see . #include "downloadlistwidgetcompact.h" #include "ui_downloadlistwidgetcompact.h" +#include +#include #include #include -#include -#include #include -DownloadListWidgetCompact::DownloadListWidgetCompact(QWidget* parent) - : QWidget(parent), ui(new Ui::DownloadListWidgetCompact) { - ui->setupUi(this); + +DownloadListWidgetCompact::DownloadListWidgetCompact(QWidget *parent) : + QWidget(parent), + ui(new Ui::DownloadListWidgetCompact) +{ + ui->setupUi(this); +} + +DownloadListWidgetCompact::~DownloadListWidgetCompact() +{ + delete ui; } -DownloadListWidgetCompact::~DownloadListWidgetCompact() { delete ui; } -DownloadListWidgetCompactDelegate::DownloadListWidgetCompactDelegate(DownloadManager* manager, bool metaDisplay, - QTreeView* view, QObject* parent) - : QItemDelegate(parent), m_Manager(manager), m_MetaDisplay(metaDisplay), - m_ItemWidget(new DownloadListWidgetCompact), m_View(view) { - m_NameLabel = m_ItemWidget->findChild("nameLabel"); - m_SizeLabel = m_ItemWidget->findChild("sizeLabel"); - m_Progress = m_ItemWidget->findChild("downloadProgress"); - m_DoneLabel = m_ItemWidget->findChild("doneLabel"); +DownloadListWidgetCompactDelegate::DownloadListWidgetCompactDelegate(DownloadManager *manager, bool metaDisplay, QTreeView *view, QObject *parent) + : QItemDelegate(parent) + , m_Manager(manager) + , m_MetaDisplay(metaDisplay) + , m_ItemWidget(new DownloadListWidgetCompact) + , m_View(view) +{ + m_NameLabel = m_ItemWidget->findChild("nameLabel"); + m_SizeLabel = m_ItemWidget->findChild("sizeLabel"); + m_Progress = m_ItemWidget->findChild("downloadProgress"); + m_DoneLabel = m_ItemWidget->findChild("doneLabel"); - m_DoneLabel->setVisible(false); + m_DoneLabel->setVisible(false); - connect(manager, SIGNAL(stateChanged(int, DownloadManager::DownloadState)), this, - SLOT(stateChanged(int, DownloadManager::DownloadState))); - connect(manager, SIGNAL(downloadRemoved(int)), this, SLOT(resetCache(int))); + connect(manager, SIGNAL(stateChanged(int,DownloadManager::DownloadState)), + this, SLOT(stateChanged(int,DownloadManager::DownloadState))); + connect(manager, SIGNAL(downloadRemoved(int)), this, SLOT(resetCache(int))); } -DownloadListWidgetCompactDelegate::~DownloadListWidgetCompactDelegate() { delete m_ItemWidget; } -void DownloadListWidgetCompactDelegate::stateChanged(int row, DownloadManager::DownloadState) { m_Cache.remove(row); } +DownloadListWidgetCompactDelegate::~DownloadListWidgetCompactDelegate() +{ + delete m_ItemWidget; +} -void DownloadListWidgetCompactDelegate::resetCache(int) { m_Cache.clear(); } -void DownloadListWidgetCompactDelegate::drawCache(QPainter* painter, const QStyleOptionViewItem& option, - const QPixmap& cache) const { - QRect rect = option.rect; - rect.setLeft(0); - rect.setWidth(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2)); - painter->drawPixmap(rect, cache); +void DownloadListWidgetCompactDelegate::stateChanged(int row,DownloadManager::DownloadState) +{ + m_Cache.remove(row); } -void DownloadListWidgetCompactDelegate::paintPendingDownload(int downloadIndex) const { - std::pair nexusids = m_Manager->getPendingDownload(downloadIndex); - m_NameLabel->setText(tr("< mod %1 file %2 >").arg(nexusids.first).arg(nexusids.second)); - if (m_SizeLabel != nullptr) { - m_SizeLabel->setText("???"); - } - m_DoneLabel->setVisible(true); - m_DoneLabel->setText(tr("Pending")); - m_Progress->setVisible(false); + +void DownloadListWidgetCompactDelegate::resetCache(int) +{ + m_Cache.clear(); } -void DownloadListWidgetCompactDelegate::paintRegularDownload(int downloadIndex) const { - QString name = m_MetaDisplay ? m_Manager->getDisplayName(downloadIndex) : m_Manager->getFileName(downloadIndex); - if (name.length() > 53) { - name.truncate(50); - name.append("..."); - } - m_NameLabel->setText(name); - DownloadManager::DownloadState state = m_Manager->getState(downloadIndex); +void DownloadListWidgetCompactDelegate::drawCache(QPainter *painter, const QStyleOptionViewItem &option, const QPixmap &cache) const +{ + QRect rect = option.rect; + rect.setLeft(0); + rect.setWidth(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2)); + painter->drawPixmap(rect, cache); +} - if ((m_SizeLabel != nullptr) && (state >= DownloadManager::STATE_READY)) { - m_SizeLabel->setText(QString::number(m_Manager->getFileSize(downloadIndex) / 1048576)); - } - if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { - m_DoneLabel->setVisible(true); - m_Progress->setVisible(false); - m_DoneLabel->setText(QString("%1").arg(tr("Paused"))); - } else if (state == DownloadManager::STATE_FETCHINGMODINFO) { - m_DoneLabel->setText(QString("%1").arg(tr("Fetching Info 1"))); - } else if (state == DownloadManager::STATE_FETCHINGFILEINFO) { - m_DoneLabel->setText(QString("%1").arg(tr("Fetching Info 2"))); - } else if (state >= DownloadManager::STATE_READY) { - m_DoneLabel->setVisible(true); - m_Progress->setVisible(false); - if (state == DownloadManager::STATE_INSTALLED) { - m_DoneLabel->setText(QString("%1").arg(tr("Installed"))); - } else if (state == DownloadManager::STATE_UNINSTALLED) { - m_DoneLabel->setText(QString("%1").arg(tr("Uninstalled"))); - } else { - m_DoneLabel->setText(QString("%1").arg(tr("Done"))); - } - if (m_Manager->isInfoIncomplete(downloadIndex)) { - m_NameLabel->setText(" " + m_NameLabel->text()); - } +void DownloadListWidgetCompactDelegate::paintPendingDownload(int downloadIndex) const +{ + std::pair nexusids = m_Manager->getPendingDownload(downloadIndex); + m_NameLabel->setText(tr("< mod %1 file %2 >").arg(nexusids.first).arg(nexusids.second)); + if (m_SizeLabel != nullptr) { + m_SizeLabel->setText("???"); + } + m_DoneLabel->setVisible(true); + m_DoneLabel->setText(tr("Pending")); + m_Progress->setVisible(false); +} + + +void DownloadListWidgetCompactDelegate::paintRegularDownload(int downloadIndex) const +{ + QString name = m_MetaDisplay ? m_Manager->getDisplayName(downloadIndex) : m_Manager->getFileName(downloadIndex); + if (name.length() > 53) { + name.truncate(50); + name.append("..."); + } + m_NameLabel->setText(name); + + DownloadManager::DownloadState state = m_Manager->getState(downloadIndex); + + if ((m_SizeLabel != nullptr) && (state >= DownloadManager::STATE_READY)) { + m_SizeLabel->setText(QString::number(m_Manager->getFileSize(downloadIndex) / 1048576)); + } + + if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { + m_DoneLabel->setVisible(true); + m_Progress->setVisible(false); + m_DoneLabel->setText(QString("%1").arg(tr("Paused"))); + } else if (state == DownloadManager::STATE_FETCHINGMODINFO) { + m_DoneLabel->setText(QString("%1").arg(tr("Fetching Info 1"))); + } else if (state == DownloadManager::STATE_FETCHINGFILEINFO) { + m_DoneLabel->setText(QString("%1").arg(tr("Fetching Info 2"))); + } else if (state >= DownloadManager::STATE_READY) { + m_DoneLabel->setVisible(true); + m_Progress->setVisible(false); + if (state == DownloadManager::STATE_INSTALLED) { + m_DoneLabel->setText(QString("%1").arg(tr("Installed"))); + } else if (state == DownloadManager::STATE_UNINSTALLED) { + m_DoneLabel->setText(QString("%1").arg(tr("Uninstalled"))); } else { - m_DoneLabel->setVisible(false); - m_Progress->setVisible(true); - m_Progress->setValue(m_Manager->getProgress(downloadIndex)); + m_DoneLabel->setText(QString("%1").arg(tr("Done"))); + } + if (m_Manager->isInfoIncomplete(downloadIndex)) { + m_NameLabel->setText(" " + m_NameLabel->text()); } + } else { + m_DoneLabel->setVisible(false); + m_Progress->setVisible(true); + m_Progress->setValue(m_Manager->getProgress(downloadIndex)); + } } -void DownloadListWidgetCompactDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, - const QModelIndex& index) const { +void DownloadListWidgetCompactDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ #pragma message("This is quite costy - room for optimization?") - try { - auto iter = m_Cache.find(index.row()); - if (iter != m_Cache.end()) { - drawCache(painter, option, *iter); - return; - } + try { + auto iter = m_Cache.find(index.row()); + if (iter != m_Cache.end()) { + drawCache(painter, option, *iter); + return; + } - m_ItemWidget->resize( - QSize(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2), option.rect.height())); - if (index.row() % 2 == 1) { - m_ItemWidget->setBackgroundRole(QPalette::AlternateBase); - } else { - m_ItemWidget->setBackgroundRole(QPalette::Base); - } + m_ItemWidget->resize(QSize(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2), option.rect.height())); + if (index.row() % 2 == 1) { + m_ItemWidget->setBackgroundRole(QPalette::AlternateBase); + } else { + m_ItemWidget->setBackgroundRole(QPalette::Base); + } - int downloadIndex = index.data().toInt(); - if (downloadIndex >= m_Manager->numTotalDownloads()) { - paintPendingDownload(downloadIndex - m_Manager->numTotalDownloads()); - } else { - paintRegularDownload(downloadIndex); - } + int downloadIndex = index.data().toInt(); + if (downloadIndex >= m_Manager->numTotalDownloads()) { + paintPendingDownload(downloadIndex - m_Manager->numTotalDownloads()); + } else { + paintRegularDownload(downloadIndex); + } #pragma message("caching disabled because changes in the list (including resorting) doesn't work correctly") - if (false) { + if (false) { // if (state >= DownloadManager::STATE_READY) { -#if QT_VERSION >= QT_VERSION_CHECK(5, 0, 0) - QPixmap cache = m_ItemWidget->grab(); +#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) + QPixmap cache = m_ItemWidget->grab(); #else - QPixmap cache = QPixmap::grabWidget(m_ItemWidget); + QPixmap cache = QPixmap::grabWidget(m_ItemWidget); #endif - m_Cache[index.row()] = cache; - drawCache(painter, option, cache); - } else { - painter->save(); - painter->translate(QPoint(0, option.rect.topLeft().y())); - - m_ItemWidget->render(painter); - painter->restore(); - } - } catch (const std::exception& e) { - qCritical("failed to paint download list item %d: %s", index.row(), e.what()); + m_Cache[index.row()] = cache; + drawCache(painter, option, cache); + } else { + painter->save(); + painter->translate(QPoint(0, option.rect.topLeft().y())); + + m_ItemWidget->render(painter); + painter->restore(); } + } catch (const std::exception &e) { + qCritical("failed to paint download list item %d: %s", index.row(), e.what()); + } } -QSize DownloadListWidgetCompactDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const { - const int width = m_ItemWidget->minimumWidth(); - const int height = m_ItemWidget->height(); - return QSize(width, height); +QSize DownloadListWidgetCompactDelegate::sizeHint(const QStyleOptionViewItem&, const QModelIndex&) const +{ + const int width = m_ItemWidget->minimumWidth(); + const int height = m_ItemWidget->height(); + return QSize(width, height); } -void DownloadListWidgetCompactDelegate::issueInstall() { emit installDownload(m_ContextIndex.row()); } -void DownloadListWidgetCompactDelegate::issueQueryInfo() { emit queryInfo(m_ContextIndex.row()); } +void DownloadListWidgetCompactDelegate::issueInstall() +{ + emit installDownload(m_ContextIndex.row()); +} -void DownloadListWidgetCompactDelegate::issueDelete() { emit removeDownload(m_ContextIndex.row(), true); } +void DownloadListWidgetCompactDelegate::issueQueryInfo() +{ + emit queryInfo(m_ContextIndex.row()); +} -void DownloadListWidgetCompactDelegate::issueRemoveFromView() { emit removeDownload(m_ContextIndex.row(), false); } +void DownloadListWidgetCompactDelegate::issueDelete() +{ + emit removeDownload(m_ContextIndex.row(), true); +} -void DownloadListWidgetCompactDelegate::issueRestoreToView() { emit restoreDownload(m_ContextIndex.row()); } +void DownloadListWidgetCompactDelegate::issueRemoveFromView() +{ + emit removeDownload(m_ContextIndex.row(), false); +} -void DownloadListWidgetCompactDelegate::issueCancel() { emit cancelDownload(m_ContextIndex.row()); } +void DownloadListWidgetCompactDelegate::issueRestoreToView() +{ + emit restoreDownload(m_ContextIndex.row()); +} -void DownloadListWidgetCompactDelegate::issuePause() { emit pauseDownload(m_ContextIndex.row()); } +void DownloadListWidgetCompactDelegate::issueCancel() +{ + emit cancelDownload(m_ContextIndex.row()); +} -void DownloadListWidgetCompactDelegate::issueResume() { emit resumeDownload(m_ContextIndex.row()); } +void DownloadListWidgetCompactDelegate::issuePause() +{ + emit pauseDownload(m_ContextIndex.row()); +} -void DownloadListWidgetCompactDelegate::issueDeleteAll() { - if (QMessageBox::question(nullptr, tr("Are you sure?"), - tr("This will remove all finished downloads from this list and from disk."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-1, true); - } +void DownloadListWidgetCompactDelegate::issueResume() +{ + emit resumeDownload(m_ContextIndex.row()); } -void DownloadListWidgetCompactDelegate::issueDeleteCompleted() { - if (QMessageBox::question(nullptr, tr("Are you sure?"), - tr("This will remove all installed downloads from this list and from disk."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-2, true); - } +void DownloadListWidgetCompactDelegate::issueDeleteAll() +{ + if (QMessageBox::question(nullptr, tr("Are you sure?"), + tr("This will remove all finished downloads from this list and from disk."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-1, true); + } } -void DownloadListWidgetCompactDelegate::issueRemoveFromViewAll() { - if (QMessageBox::question( - nullptr, tr("Are you sure?"), - tr("This will permanently remove all finished downloads from this list (but NOT from disk)."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-1, false); - } +void DownloadListWidgetCompactDelegate::issueDeleteCompleted() +{ + if (QMessageBox::question(nullptr, tr("Are you sure?"), + tr("This will remove all installed downloads from this list and from disk."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-2, true); + } } -void DownloadListWidgetCompactDelegate::issueRemoveFromViewCompleted() { - if (QMessageBox::question( - nullptr, tr("Are you sure?"), - tr("This will permanently remove all installed downloads from this list (but NOT from disk)."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit removeDownload(-2, false); - } +void DownloadListWidgetCompactDelegate::issueRemoveFromViewAll() +{ + if (QMessageBox::question(nullptr, tr("Are you sure?"), + tr("This will permanently remove all finished downloads from this list (but NOT from disk)."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-1, false); + } +} + +void DownloadListWidgetCompactDelegate::issueRemoveFromViewCompleted() +{ + if (QMessageBox::question(nullptr, tr("Are you sure?"), + tr("This will permanently remove all installed downloads from this list (but NOT from disk)."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit removeDownload(-2, false); + } } -bool DownloadListWidgetCompactDelegate::editorEvent(QEvent* event, QAbstractItemModel* model, - const QStyleOptionViewItem& option, const QModelIndex& index) { - try { - if (event->type() == QEvent::MouseButtonDblClick) { - 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) { - emit resumeDownload(sourceIndex.row()); + +bool DownloadListWidgetCompactDelegate::editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index) +{ + try { + if (event->type() == QEvent::MouseButtonDblClick) { + 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) { + emit resumeDownload(sourceIndex.row()); + } + return true; + } else if (event->type() == QEvent::MouseButtonRelease) { + QMouseEvent *mouseEvent = static_cast(event); + if (mouseEvent->button() == Qt::RightButton) { + QMenu menu; + bool hidden = false; + m_ContextIndex = qobject_cast(model)->mapToSource(index); + if (m_ContextIndex.row() < m_Manager->numTotalDownloads()) { + DownloadManager::DownloadState state = m_Manager->getState(m_ContextIndex.row()); + hidden = m_Manager->isHidden(m_ContextIndex.row()); + if (state >= DownloadManager::STATE_READY) { + menu.addAction(tr("Install"), this, SLOT(issueInstall())); + if (m_Manager->isInfoIncomplete(m_ContextIndex.row())) { + menu.addAction(tr("Query Info"), this, SLOT(issueQueryInfo())); } - return true; - } else if (event->type() == QEvent::MouseButtonRelease) { - QMouseEvent* mouseEvent = static_cast(event); - if (mouseEvent->button() == Qt::RightButton) { - QMenu menu; - bool hidden = false; - m_ContextIndex = qobject_cast(model)->mapToSource(index); - if (m_ContextIndex.row() < m_Manager->numTotalDownloads()) { - DownloadManager::DownloadState state = m_Manager->getState(m_ContextIndex.row()); - hidden = m_Manager->isHidden(m_ContextIndex.row()); - if (state >= DownloadManager::STATE_READY) { - menu.addAction(tr("Install"), this, SLOT(issueInstall())); - if (m_Manager->isInfoIncomplete(m_ContextIndex.row())) { - menu.addAction(tr("Query Info"), this, SLOT(issueQueryInfo())); - } - menu.addAction(tr("Delete"), this, SLOT(issueDelete())); - if (hidden) { - menu.addAction(tr("Un-Hide"), this, SLOT(issueRestoreToView())); - } else { - menu.addAction(tr("Hide"), this, SLOT(issueRemoveFromView())); - } - } else if (state == DownloadManager::STATE_DOWNLOADING) { - menu.addAction(tr("Cancel"), this, SLOT(issueCancel())); - menu.addAction(tr("Pause"), this, SLOT(issuePause())); - } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { - menu.addAction(tr("Remove"), this, SLOT(issueDelete())); - menu.addAction(tr("Resume"), this, SLOT(issueResume())); - } - - menu.addSeparator(); - } - menu.addAction(tr("Delete Installed..."), this, SLOT(issueDeleteCompleted())); - menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll())); - if (!hidden) { - menu.addSeparator(); - menu.addAction(tr("Hide Installed..."), this, SLOT(issueRemoveFromViewCompleted())); - menu.addAction(tr("Hide All..."), this, SLOT(issueRemoveFromViewAll())); - } - menu.exec(mouseEvent->globalPos()); - - event->accept(); - return false; + menu.addAction(tr("Delete"), this, SLOT(issueDelete())); + if (hidden) { + menu.addAction(tr("Un-Hide"), this, SLOT(issueRestoreToView())); + } else { + menu.addAction(tr("Hide"), this, SLOT(issueRemoveFromView())); } + } else if (state == DownloadManager::STATE_DOWNLOADING){ + menu.addAction(tr("Cancel"), this, SLOT(issueCancel())); + menu.addAction(tr("Pause"), this, SLOT(issuePause())); + } else if ((state == DownloadManager::STATE_PAUSED) || (state == DownloadManager::STATE_ERROR)) { + menu.addAction(tr("Remove"), this, SLOT(issueDelete())); + menu.addAction(tr("Resume"), this, SLOT(issueResume())); + } + + menu.addSeparator(); } - } catch (const std::exception& e) { - qCritical("failed to handle editor event: %s", e.what()); + menu.addAction(tr("Delete Installed..."), this, SLOT(issueDeleteCompleted())); + menu.addAction(tr("Delete All..."), this, SLOT(issueDeleteAll())); + if (!hidden) { + menu.addSeparator(); + menu.addAction(tr("Hide Installed..."), this, SLOT(issueRemoveFromViewCompleted())); + menu.addAction(tr("Hide All..."), this, SLOT(issueRemoveFromViewAll())); + } + menu.exec(mouseEvent->globalPos()); + + event->accept(); + return false; + } } + } catch (const std::exception &e) { + qCritical("failed to handle editor event: %s", e.what()); + } - return QItemDelegate::editorEvent(event, model, option, index); + return QItemDelegate::editorEvent(event, model, option, index); } + diff --git a/src/downloadlistwidgetcompact.h b/src/downloadlistwidgetcompact.h index 2b016cf1..bf855d5f 100644 --- a/src/downloadlistwidgetcompact.h +++ b/src/downloadlistwidgetcompact.h @@ -20,95 +20,103 @@ along with Mod Organizer. If not, see . #ifndef DOWNLOADLISTWIDGETCOMPACT_H #define DOWNLOADLISTWIDGETCOMPACT_H -#include "downloadmanager.h" +#include #include #include #include #include -#include +#include "downloadmanager.h" + namespace Ui { class DownloadListWidgetCompact; } -class DownloadListWidgetCompact : public QWidget { - Q_OBJECT - +class DownloadListWidgetCompact : public QWidget +{ + Q_OBJECT + public: - explicit DownloadListWidgetCompact(QWidget* parent = 0); - ~DownloadListWidgetCompact(); + explicit DownloadListWidgetCompact(QWidget *parent = 0); + ~DownloadListWidgetCompact(); private: - Ui::DownloadListWidgetCompact* ui; - int m_ContextRow; + Ui::DownloadListWidgetCompact *ui; + int m_ContextRow; }; class DownloadManager; -class DownloadListWidgetCompactDelegate : public QItemDelegate { +class DownloadListWidgetCompactDelegate : public QItemDelegate +{ - Q_OBJECT + Q_OBJECT public: - DownloadListWidgetCompactDelegate(DownloadManager* manager, bool metaDisplay, QTreeView* view, QObject* parent = 0); - ~DownloadListWidgetCompactDelegate(); - virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; - virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; + DownloadListWidgetCompactDelegate(DownloadManager *manager, bool metaDisplay, QTreeView *view, QObject *parent = 0); + ~DownloadListWidgetCompactDelegate(); + + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; signals: - void installDownload(int index); - void queryInfo(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 installDownload(int index); + void queryInfo(int index); + void removeDownload(int index, bool deleteFile); + void restoreDownload(int index); + void cancelDownload(int index); + void pauseDownload(int index); + void resumeDownload(int index); protected: - bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, - const QModelIndex& index); + + bool editorEvent(QEvent *event, QAbstractItemModel *model, + const QStyleOptionViewItem &option, const QModelIndex &index); private: - void drawCache(QPainter* painter, const QStyleOptionViewItem& option, const QPixmap& cache) const; - void paintPendingDownload(int downloadIndex) const; - void paintRegularDownload(int downloadIndex) const; -private slots: + void drawCache(QPainter *painter, const QStyleOptionViewItem &option, const QPixmap &cache) const; + void paintPendingDownload(int downloadIndex) const; + void paintRegularDownload(int downloadIndex) const; - void issueInstall(); - void issueDelete(); - void issueRemoveFromView(); - void issueRestoreToView(); - void issueCancel(); - void issuePause(); - void issueResume(); - void issueDeleteAll(); - void issueDeleteCompleted(); - void issueRemoveFromViewAll(); - void issueRemoveFromViewCompleted(); - void issueQueryInfo(); - - void stateChanged(int row, DownloadManager::DownloadState); - void resetCache(int); +private slots: + void issueInstall(); + void issueDelete(); + void issueRemoveFromView(); + void issueRestoreToView(); + void issueCancel(); + void issuePause(); + void issueResume(); + void issueDeleteAll(); + void issueDeleteCompleted(); + void issueRemoveFromViewAll(); + void issueRemoveFromViewCompleted(); + void issueQueryInfo(); + + void stateChanged(int row, DownloadManager::DownloadState); + void resetCache(int); private: - DownloadListWidgetCompact* m_ItemWidget; - DownloadManager* m_Manager; - bool m_MetaDisplay; + DownloadListWidgetCompact *m_ItemWidget; + DownloadManager *m_Manager; - QLabel* m_NameLabel; - QLabel* m_SizeLabel; - QProgressBar* m_Progress; - QLabel* m_DoneLabel; + bool m_MetaDisplay; - QModelIndex m_ContextIndex; + QLabel *m_NameLabel; + QLabel *m_SizeLabel; + QProgressBar *m_Progress; + QLabel *m_DoneLabel; - QTreeView* m_View; + QModelIndex m_ContextIndex; + + QTreeView *m_View; + + mutable QMap m_Cache; - mutable QMap m_Cache; }; #endif // DOWNLOADLISTWIDGETCOMPACT_H + diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 5f8fc98b..a4fde093 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -19,1370 +19,1475 @@ along with Mod Organizer. If not, see . #include "downloadmanager.h" -#include "bbcode.h" -#include "iplugingame.h" +#include "nxmurl.h" #include "nexusinterface.h" #include "nxmaccessmanager.h" -#include "nxmurl.h" -#include "selectiondialog.h" -#include "utility.h" +#include "iplugingame.h" #include -#include #include +#include "utility.h" +#include "selectiondialog.h" +#include "bbcode.h" #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 + static const char UNFINISHED[] = ".unfinished"; unsigned int DownloadManager::DownloadInfo::s_NextDownloadID = 1U; -DownloadManager::DownloadInfo* DownloadManager::DownloadInfo::createNew(const ModRepositoryFileInfo* fileInfo, - const QStringList& URLs) { - DownloadInfo* info = new DownloadInfo; - info->m_DownloadID = s_NextDownloadID++; - info->m_StartTime.start(); - info->m_PreResumeSize = 0LL; - info->m_Progress = 0; - 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(); - - return info; -} - -DownloadManager::DownloadInfo* DownloadManager::DownloadInfo::createFromMeta(const QString& filePath, bool showHidden) { - DownloadInfo* info = new DownloadInfo; - - QString metaFileName = filePath + ".meta"; - QSettings metaFile(metaFileName, QSettings::IniFormat); - if (!showHidden && metaFile.value("removed", false).toBool()) { - return nullptr; - } else { - info->m_Hidden = metaFile.value("removed", false).toBool(); - } - QString fileName = QFileInfo(filePath).fileName(); +DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createNew(const ModRepositoryFileInfo *fileInfo, const QStringList &URLs) +{ + DownloadInfo *info = new DownloadInfo; + info->m_DownloadID = s_NextDownloadID++; + info->m_StartTime.start(); + info->m_PreResumeSize = 0LL; + info->m_Progress = 0; + 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(); - if (fileName.endsWith(UNFINISHED)) { - info->m_FileName = fileName.mid(0, fileName.length() - static_cast(strlen(UNFINISHED))); - info->m_State = STATE_PAUSED; - } else { - info->m_FileName = fileName; - - if (metaFile.value("paused", false).toBool()) { - info->m_State = STATE_PAUSED; - } else if (metaFile.value("uninstalled", false).toBool()) { - info->m_State = STATE_UNINSTALLED; - } else if (metaFile.value("installed", false).toBool()) { - info->m_State = STATE_INSTALLED; - } else { - info->m_State = STATE_READY; - } - } + return info; +} - info->m_DownloadID = s_NextDownloadID++; - info->m_Output.setFileName(filePath); - info->m_TotalSize = 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(); - int modID = metaFile.value("modID", 0).toInt(); - int fileID = metaFile.value("fileID", 0).toInt(); - info->m_FileInfo = new ModRepositoryFileInfo(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->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->fileCategory = metaFile.value("fileCategory", 0).toInt(); - info->m_FileInfo->repository = metaFile.value("repository", "Nexus").toString(); - info->m_FileInfo->userData = metaFile.value("userData").toMap(); - - return info; -} - -void DownloadManager::DownloadInfo::setName(QString newName, bool renameFile) { - QString oldMetaFileName = QString("%1.meta").arg(m_FileName); - m_FileName = QFileInfo(newName).fileName(); - if ((m_State == DownloadManager::STATE_STARTED) || (m_State == DownloadManager::STATE_DOWNLOADING)) { - newName.append(UNFINISHED); - } - 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)); - return; - } +DownloadManager::DownloadInfo *DownloadManager::DownloadInfo::createFromMeta(const QString &filePath, bool showHidden) +{ + DownloadInfo *info = new DownloadInfo; - QFile metaFile(oldMetaFileName); - if (metaFile.exists()) { - metaFile.rename(newName.mid(0).append(".meta")); - } + QString metaFileName = filePath + ".meta"; + QSettings metaFile(metaFileName, QSettings::IniFormat); + if (!showHidden && metaFile.value("removed", false).toBool()) { + return nullptr; + } else { + info->m_Hidden = metaFile.value("removed", false).toBool(); + } + + QString fileName = QFileInfo(filePath).fileName(); + + if (fileName.endsWith(UNFINISHED)) { + info->m_FileName = fileName.mid( + 0, fileName.length() - static_cast(strlen(UNFINISHED))); + info->m_State = STATE_PAUSED; + } else { + info->m_FileName = fileName; + + if (metaFile.value("paused", false).toBool()) { + info->m_State = STATE_PAUSED; + } else if (metaFile.value("uninstalled", false).toBool()) { + info->m_State = STATE_UNINSTALLED; + } else if (metaFile.value("installed", false).toBool()) { + info->m_State = STATE_INSTALLED; + } else { + info->m_State = STATE_READY; + } + } + + info->m_DownloadID = s_NextDownloadID++; + info->m_Output.setFileName(filePath); + info->m_TotalSize = 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(); + int modID = metaFile.value("modID", 0).toInt(); + int fileID = metaFile.value("fileID", 0).toInt(); + info->m_FileInfo = new ModRepositoryFileInfo(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->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->fileCategory = metaFile.value("fileCategory", 0).toInt(); + info->m_FileInfo->repository = metaFile.value("repository", "Nexus").toString(); + info->m_FileInfo->userData = metaFile.value("userData").toMap(); + + return info; +} + +void DownloadManager::DownloadInfo::setName(QString newName, bool renameFile) +{ + QString oldMetaFileName = QString("%1.meta").arg(m_FileName); + m_FileName = QFileInfo(newName).fileName(); + if ((m_State == DownloadManager::STATE_STARTED) || + (m_State == DownloadManager::STATE_DOWNLOADING)) { + newName.append(UNFINISHED); + } + 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)); + return; + } + + QFile metaFile(oldMetaFileName); + if (metaFile.exists()) { + metaFile.rename(newName.mid(0).append(".meta")); + } + } + if (!m_Output.isOpen()) { + // can't set file name if it's open + m_Output.setFileName(newName); + } +} + +bool DownloadManager::DownloadInfo::isPausedState() +{ + return m_State == STATE_PAUSED || m_State == STATE_ERROR; +} + +QString DownloadManager::DownloadInfo::currentURL() +{ + return m_Urls[m_CurrentUrl]; +} + + +DownloadManager::DownloadManager(NexusInterface *nexusInterface, QObject *parent) + : IDownloadManager(parent), m_NexusInterface(nexusInterface), m_DirWatcher(), m_ShowHidden(false), + m_DateExpression("/Date\\((\\d+)\\)/") +{ + connect(&m_DirWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); +} + + +DownloadManager::~DownloadManager() +{ + for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter) { + delete *iter; + } + m_ActiveDownloads.clear(); +} + + +bool DownloadManager::downloadsInProgress() +{ + for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter) { + if ((*iter)->m_State < STATE_READY) { + return true; + } + } + return false; +} + +void DownloadManager::pauseAll() +{ + // first loop: pause all downloads + for (int i = 0; i < m_ActiveDownloads.count(); ++i) { + if (m_ActiveDownloads[i]->m_State < STATE_READY) { + pauseDownload(i); + } + } + + ::Sleep(100); + + 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) { + if ((info->m_State < STATE_CANCELED) || + (info->m_State == STATE_FETCHINGFILEINFO) || + (info->m_State == STATE_FETCHINGMODINFO)) { + done = false; + break; + } } - if (!m_Output.isOpen()) { - // can't set file name if it's open - m_Output.setFileName(newName); + if (!done) { + ::Sleep(100); } + } } -bool DownloadManager::DownloadInfo::isPausedState() { return m_State == STATE_PAUSED || m_State == STATE_ERROR; } -QString DownloadManager::DownloadInfo::currentURL() { return m_Urls[m_CurrentUrl]; } +void DownloadManager::setOutputDirectory(const QString &outputDirectory) +{ + QStringList directories = m_DirWatcher.directories(); + if (directories.length() != 0) { + m_DirWatcher.removePaths(directories); + } + m_OutputDirectory = QDir::fromNativeSeparators(outputDirectory); + refreshList(); + m_DirWatcher.addPath(m_OutputDirectory); +} + -DownloadManager::DownloadManager(NexusInterface* nexusInterface, QObject* parent) - : IDownloadManager(parent), m_NexusInterface(nexusInterface), m_DirWatcher(), m_ShowHidden(false), - m_DateExpression("/Date\\((\\d+)\\)/") { - connect(&m_DirWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(directoryChanged(QString))); +void DownloadManager::setPreferredServers(const std::map &preferredServers) +{ + m_PreferredServers = preferredServers; } -DownloadManager::~DownloadManager() { - for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter) { - delete *iter; - } - m_ActiveDownloads.clear(); + +void DownloadManager::setSupportedExtensions(const QStringList &extensions) +{ + m_SupportedExtensions = extensions; + refreshList(); } -bool DownloadManager::downloadsInProgress() { - for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter) { - if ((*iter)->m_State < STATE_READY) { - return true; - } - } - return false; +void DownloadManager::setShowHidden(bool showHidden) +{ + m_ShowHidden = showHidden; + refreshList(); } -void DownloadManager::pauseAll() { - // first loop: pause all downloads - for (int i = 0; i < m_ActiveDownloads.count(); ++i) { - if (m_ActiveDownloads[i]->m_State < STATE_READY) { - pauseDownload(i); - } - } +void DownloadManager::refreshList() +{ + try { + int downloadsBefore = m_ActiveDownloads.size(); - ::Sleep(100); - - 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) { - if ((info->m_State < STATE_CANCELED) || (info->m_State == STATE_FETCHINGFILEINFO) || - (info->m_State == STATE_FETCHINGMODINFO)) { - done = false; - break; - } - } - if (!done) { - ::Sleep(100); + // 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)) { + delete *iter; + iter = m_ActiveDownloads.erase(iter); + } else { + ++iter; + } + } + + QStringList nameFilters(m_SupportedExtensions); + foreach (const QString &extension, m_SupportedExtensions) { + nameFilters.append("*." + extension); + } + + nameFilters.append(QString("*").append(UNFINISHED)); + QDir dir(QDir::fromNativeSeparators(m_OutputDirectory)); + + // 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) { + QString baseFile = metaFile.left(metaFile.length() - 5); + if (!QFile::exists(dir.absoluteFilePath(baseFile))) { + orphans.append(dir.absoluteFilePath(metaFile)); + } + } + if (orphans.size() > 0) { + qDebug("%d orphaned meta files will be deleted", orphans.size()); + shellDelete(orphans, true); + } + + // add existing downloads to list + foreach (QString file, dir.entryList(nameFilters, QDir::Files, QDir::Time)) { + bool Exists = false; + for (QVector::const_iterator Iter = m_ActiveDownloads.begin(); Iter != m_ActiveDownloads.end() && !Exists; ++Iter) { + if (QString::compare((*Iter)->m_FileName, file, Qt::CaseInsensitive) == 0) { + Exists = true; + } else if (QString::compare(QFileInfo((*Iter)->m_Output.fileName()).fileName(), file, Qt::CaseInsensitive) == 0) { + Exists = true; } + } + if (Exists) { + continue; + } + + QString fileName = QDir::fromNativeSeparators(m_OutputDirectory) + "/" + file; + + DownloadInfo *info = DownloadInfo::createFromMeta(fileName, m_ShowHidden); + if (info != nullptr) { + m_ActiveDownloads.push_front(info); + } } -} -void DownloadManager::setOutputDirectory(const QString& outputDirectory) { - QStringList directories = m_DirWatcher.directories(); - if (directories.length() != 0) { - m_DirWatcher.removePaths(directories); + if (m_ActiveDownloads.size() != downloadsBefore) { + qDebug("downloads after refresh: %d", m_ActiveDownloads.size()); } - m_OutputDirectory = QDir::fromNativeSeparators(outputDirectory); - refreshList(); - m_DirWatcher.addPath(m_OutputDirectory); + emit update(-1); + } catch (const std::bad_alloc&) { + reportError(tr("Memory allocation error (in refreshing directory).")); + } } -void DownloadManager::setPreferredServers(const std::map& preferredServers) { - m_PreferredServers = preferredServers; -} -void DownloadManager::setSupportedExtensions(const QStringList& extensions) { - m_SupportedExtensions = extensions; - refreshList(); -} +bool DownloadManager::addDownload(const QStringList &URLs, + int modID, int fileID, const ModRepositoryFileInfo *fileInfo) +{ + QString fileName = QFileInfo(URLs.first()).fileName(); + if (fileName.isEmpty()) { + fileName = "unknown"; + } -void DownloadManager::setShowHidden(bool showHidden) { - m_ShowHidden = showHidden; - refreshList(); + QUrl preferredUrl = QUrl::fromEncoded(URLs.first().toLocal8Bit()); + qDebug("selected download url: %s", qPrintable(preferredUrl.toString())); + QNetworkRequest request(preferredUrl); + return addDownload(m_NexusInterface->getAccessManager()->get(request), URLs, fileName, modID, fileID, fileInfo); } -void DownloadManager::refreshList() { - try { - 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)) { - delete *iter; - iter = m_ActiveDownloads.erase(iter); - } else { - ++iter; - } - } +bool DownloadManager::addDownload(QNetworkReply *reply, const ModRepositoryFileInfo *fileInfo) +{ + QString fileName = getFileNameFromNetworkReply(reply); + if (fileName.isEmpty()) { + fileName = "unknown"; + } - QStringList nameFilters(m_SupportedExtensions); - foreach (const QString& extension, m_SupportedExtensions) { nameFilters.append("*." + extension); } + return addDownload(reply, QStringList(reply->url().toString()), fileName, fileInfo->modID, fileInfo->fileID, fileInfo); +} - nameFilters.append(QString("*").append(UNFINISHED)); - QDir dir(QDir::fromNativeSeparators(m_OutputDirectory)); - // 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) { - QString baseFile = metaFile.left(metaFile.length() - 5); - if (!QFile::exists(dir.absoluteFilePath(baseFile))) { - orphans.append(dir.absoluteFilePath(metaFile)); - } - } - if (orphans.size() > 0) { - qDebug("%d orphaned meta files will be deleted", orphans.size()); - shellDelete(orphans, true); - } +bool DownloadManager::addDownload(QNetworkReply *reply, const QStringList &URLs, const QString &fileName, + 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); - // add existing downloads to list - foreach (QString file, dir.entryList(nameFilters, QDir::Files, QDir::Time)) { - bool Exists = false; - for (QVector::const_iterator Iter = m_ActiveDownloads.begin(); - Iter != m_ActiveDownloads.end() && !Exists; ++Iter) { - if (QString::compare((*Iter)->m_FileName, file, Qt::CaseInsensitive) == 0) { - Exists = true; - } else if (QString::compare(QFileInfo((*Iter)->m_Output.fileName()).fileName(), file, - Qt::CaseInsensitive) == 0) { - Exists = true; - } - } - if (Exists) { - continue; - } - - QString fileName = QDir::fromNativeSeparators(m_OutputDirectory) + "/" + file; - - DownloadInfo* info = DownloadInfo::createFromMeta(fileName, m_ShowHidden); - if (info != nullptr) { - m_ActiveDownloads.push_front(info); - } - } + QString baseName = fileName; + if (!fileInfo->fileName.isEmpty()) { + baseName = fileInfo->fileName; + } else { + QString dispoName = getFileNameFromNetworkReply(reply); - if (m_ActiveDownloads.size() != downloadsBefore) { - qDebug("downloads after refresh: %d", m_ActiveDownloads.size()); - } - emit update(-1); - } catch (const std::bad_alloc&) { - reportError(tr("Memory allocation error (in refreshing directory).")); - } -} - -bool DownloadManager::addDownload(const QStringList& URLs, int modID, int fileID, - const ModRepositoryFileInfo* fileInfo) { - QString fileName = QFileInfo(URLs.first()).fileName(); - if (fileName.isEmpty()) { - fileName = "unknown"; + if (!dispoName.isEmpty()) { + baseName = dispoName; } + } + if (QFile::exists(m_OutputDirectory + "/" + baseName) && + (QMessageBox::question(nullptr, tr("Download again?"), tr("A file with the same name has already been downloaded. " + "Do you want to download it again? The new file will receive a different name."), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)) { + removePending(modID, fileID); + delete newDownload; + return false; + } + newDownload->setName(getDownloadFileName(baseName), false); - QUrl preferredUrl = QUrl::fromEncoded(URLs.first().toLocal8Bit()); - qDebug("selected download url: %s", qPrintable(preferredUrl.toString())); - QNetworkRequest request(preferredUrl); - return addDownload(m_NexusInterface->getAccessManager()->get(request), URLs, fileName, modID, fileID, fileInfo); + startDownload(reply, newDownload, false); +// emit update(-1); + return true; } -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->modID, fileInfo->fileID, - fileInfo); +void DownloadManager::removePending(int modID, int fileID) +{ + emit aboutToUpdate(); + for (auto iter = m_PendingDownloads.begin(); iter != m_PendingDownloads.end(); ++iter) { + if ((iter->first == modID) && (iter->second == fileID)) { + m_PendingDownloads.erase(iter); + break; + } + } + emit update(-1); } -bool DownloadManager::addDownload(QNetworkReply* reply, const QStringList& URLs, const QString& fileName, 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); - QString baseName = fileName; - if (!fileInfo->fileName.isEmpty()) { - baseName = fileInfo->fileName; - } else { - QString dispoName = getFileNameFromNetworkReply(reply); +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 + newDownload->m_Reply = reply; + setState(newDownload, STATE_DOWNLOADING); + if (newDownload->m_Urls.count() == 0) { + newDownload->m_Urls = QStringList(reply->url().toString()); + } - if (!dispoName.isEmpty()) { - baseName = dispoName; - } - } - if (QFile::exists(m_OutputDirectory + "/" + baseName) && - (QMessageBox::question(nullptr, tr("Download again?"), - tr("A file with the same name has already been downloaded. " - "Do you want to download it again? The new file will receive a different name."), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)) { - removePending(modID, fileID); - delete newDownload; - return false; - } - newDownload->setName(getDownloadFileName(baseName), false); + QIODevice::OpenMode mode = QIODevice::WriteOnly; + if (resume) { + mode |= QIODevice::Append; + } - startDownload(reply, newDownload, false); - // emit update(-1); - return true; -} + newDownload->m_StartTime.start(); + createMetaFile(newDownload); -void DownloadManager::removePending(int modID, int fileID) { - emit aboutToUpdate(); - for (auto iter = m_PendingDownloads.begin(); iter != m_PendingDownloads.end(); ++iter) { - if ((iter->first == modID) && (iter->second == fileID)) { - m_PendingDownloads.erase(iter); - break; - } - } - emit update(-1); -} + 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())); + return; + } -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 - newDownload->m_Reply = reply; - setState(newDownload, STATE_DOWNLOADING); - if (newDownload->m_Urls.count() == 0) { - newDownload->m_Urls = QStringList(reply->url().toString()); - } + connect(newDownload->m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64))); + connect(newDownload->m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished())); + connect(newDownload->m_Reply, SIGNAL(error(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())); - QIODevice::OpenMode mode = QIODevice::WriteOnly; - if (resume) { - mode |= QIODevice::Append; - } + if (!resume) { + newDownload->m_PreResumeSize = newDownload->m_Output.size(); + removePending(newDownload->m_FileInfo->modID, newDownload->m_FileInfo->fileID); - newDownload->m_StartTime.start(); - createMetaFile(newDownload); + emit aboutToUpdate(); + m_ActiveDownloads.append(newDownload); - 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())); - return; + emit update(-1); + emit downloadAdded(); + + if (reply->isFinished()) { + // it's possible the download has already finished before this function ran + downloadFinished(); } + } +} - connect(newDownload->m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, - SLOT(downloadProgress(qint64, qint64))); - connect(newDownload->m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished())); - connect(newDownload->m_Reply, SIGNAL(error(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())); - if (!resume) { - newDownload->m_PreResumeSize = newDownload->m_Output.size(); - removePending(newDownload->m_FileInfo->modID, newDownload->m_FileInfo->fileID); +void DownloadManager::addNXMDownload(const QString &url) +{ + NXMUrl nxmInfo(url); - emit aboutToUpdate(); - m_ActiveDownloads.append(newDownload); + QString managedGame = m_ManagedGame->gameShortName(); + qDebug("add nxm download: %s", qPrintable(url)); + if (nxmInfo.game().compare(managedGame, Qt::CaseInsensitive) != 0) { + qDebug("download requested for wrong game (game: %s, url: %s)", qPrintable(managedGame), qPrintable(nxmInfo.game())); + QMessageBox::information(nullptr, 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(managedGame), QMessageBox::Ok); + return; + } - emit update(-1); - emit downloadAdded(); + emit aboutToUpdate(); + m_PendingDownloads.append(std::make_pair(nxmInfo.modId(), nxmInfo.fileId())); - if (reply->isFinished()) { - // it's possible the download has already finished before this function ran - downloadFinished(); - } - } + emit update(-1); + emit downloadAdded(); + m_RequestIDs.insert(m_NexusInterface->requestFileInfo(nxmInfo.modId(), nxmInfo.fileId(), this, nxmInfo.fileId(), "")); } -void DownloadManager::addNXMDownload(const QString& url) { - NXMUrl nxmInfo(url); - - QString managedGame = m_ManagedGame->gameShortName(); - qDebug("add nxm download: %s", qPrintable(url)); - if (nxmInfo.game().compare(managedGame, Qt::CaseInsensitive) != 0) { - qDebug("download requested for wrong game (game: %s, url: %s)", qPrintable(managedGame), - qPrintable(nxmInfo.game())); - QMessageBox::information(nullptr, 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(managedGame), - QMessageBox::Ok); - return; - } - emit aboutToUpdate(); - m_PendingDownloads.append(std::make_pair(nxmInfo.modId(), nxmInfo.fileId())); +void DownloadManager::removeFile(int index, bool deleteFile) +{ + if (index >= m_ActiveDownloads.size()) { + throw MyException(tr("remove: invalid download index %1").arg(index)); + } - emit update(-1); - emit downloadAdded(); - m_RequestIDs.insert( - m_NexusInterface->requestFileInfo(nxmInfo.modId(), nxmInfo.fileId(), this, nxmInfo.fileId(), "")); -} + 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 + qCritical("tried to remove active download"); + return; + } -void DownloadManager::removeFile(int index, bool deleteFile) { - if (index >= m_ActiveDownloads.size()) { - throw MyException(tr("remove: invalid download index %1").arg(index)); - } + if ((download->m_State == STATE_PAUSED) || (download->m_State == STATE_ERROR)) { + filePath = download->m_Output.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 - qCritical("tried to remove active download"); - return; + if (deleteFile) { + if (!shellDelete(QStringList(filePath), true)) { + reportError(tr("failed to delete %1").arg(filePath)); + return; } - if ((download->m_State == STATE_PAUSED) || (download->m_State == STATE_ERROR)) { - filePath = download->m_Output.fileName(); - } - - if (deleteFile) { - if (!shellDelete(QStringList(filePath), true)) { - reportError(tr("failed to delete %1").arg(filePath)); - return; - } - - QFile metaFile(filePath.append(".meta")); - if (metaFile.exists() && !shellDelete(QStringList(filePath), true)) { - reportError(tr("failed to delete meta file for %1").arg(filePath)); - } - } else { - QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); - metaSettings.setValue("removed", true); + QFile metaFile(filePath.append(".meta")); + if (metaFile.exists() && !shellDelete(QStringList(filePath), true)) { + reportError(tr("failed to delete meta file for %1").arg(filePath)); } + } else { + QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); + metaSettings.setValue("removed", true); + } } -class LessThanWrapper { +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; + +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) { - m_AlphabeticalTranslation.push_back(pos); - } - qSort(m_AlphabeticalTranslation.begin(), m_AlphabeticalTranslation.end(), LessThanWrapper(this)); +void DownloadManager::refreshAlphabeticalTranslation() +{ + m_AlphabeticalTranslation.clear(); + int pos = 0; + for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter, ++pos) { + m_AlphabeticalTranslation.push_back(pos); + } + + qSort(m_AlphabeticalTranslation.begin(), m_AlphabeticalTranslation.end(), LessThanWrapper(this)); } -void DownloadManager::restoreDownload(int index) { - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("restore: invalid download index: %1").arg(index)); - } - DownloadInfo* download = m_ActiveDownloads.at(index); - download->m_Hidden = false; +void DownloadManager::restoreDownload(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("restore: invalid download index: %1").arg(index)); + } - QString filePath = m_OutputDirectory + "/" + download->m_FileName; + DownloadInfo *download = m_ActiveDownloads.at(index); + download->m_Hidden = false; - QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); - metaSettings.setValue("removed", false); -} - -void DownloadManager::removeDownload(int index, bool deleteFile) { - try { - emit aboutToUpdate(); - - if (index < 0) { - if (index == -1) { - DownloadState minState = STATE_READY; - index = 0; - for (QVector::iterator iter = m_ActiveDownloads.begin(); - iter != m_ActiveDownloads.end();) { - if ((*iter)->m_State >= minState) { - removeFile(index, deleteFile); - delete *iter; - iter = m_ActiveDownloads.erase(iter); - } else { - ++iter; - ++index; - } - } - } else { - DownloadState minState = STATE_INSTALLED; - index = 0; - for (QVector::iterator iter = m_ActiveDownloads.begin(); - iter != m_ActiveDownloads.end();) { - if ((*iter)->m_State == minState) { - removeFile(index, deleteFile); - delete *iter; - iter = m_ActiveDownloads.erase(iter); - } else { - ++iter; - ++index; - } - } - } - } else { - if (index >= m_ActiveDownloads.size()) { - reportError(tr("remove: invalid download index %1").arg(index)); - return; - } - - removeFile(index, deleteFile); - delete m_ActiveDownloads.at(index); - m_ActiveDownloads.erase(m_ActiveDownloads.begin() + index); - } - emit update(-1); - } catch (const std::exception& e) { - qCritical("failed to remove download: %s", e.what()); - } + QString filePath = m_OutputDirectory + "/" + download->m_FileName; + + QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); + metaSettings.setValue("removed", false); } -void DownloadManager::cancelDownload(int index) { - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - reportError(tr("cancel: invalid download index %1").arg(index)); + +void DownloadManager::removeDownload(int index, bool deleteFile) +{ + try { + emit aboutToUpdate(); + + if (index < 0) { + if (index == -1) { + DownloadState minState = STATE_READY; + index = 0; + for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end();) { + if ((*iter)->m_State >= minState) { + removeFile(index, deleteFile); + delete *iter; + iter = m_ActiveDownloads.erase(iter); + } + else { + ++iter; + ++index; + } + } + } + else { + DownloadState minState = STATE_INSTALLED; + index = 0; + for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end();) { + if ((*iter)->m_State == minState) { + removeFile(index, deleteFile); + delete *iter; + iter = m_ActiveDownloads.erase(iter); + } + else { + ++iter; + ++index; + } + } + } + } else { + if (index >= m_ActiveDownloads.size()) { + reportError(tr("remove: invalid download index %1").arg(index)); return; - } + } - if (m_ActiveDownloads.at(index)->m_State == STATE_DOWNLOADING) { - setState(m_ActiveDownloads.at(index), STATE_CANCELING); + removeFile(index, deleteFile); + delete m_ActiveDownloads.at(index); + m_ActiveDownloads.erase(m_ActiveDownloads.begin() + index); } + emit update(-1); + } catch (const std::exception &e) { + qCritical("failed to remove download: %s", e.what()); + } } -void DownloadManager::pauseDownload(int index) { - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - reportError(tr("pause: invalid download index %1").arg(index)); - return; - } - DownloadInfo* info = m_ActiveDownloads.at(index); +void DownloadManager::cancelDownload(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + reportError(tr("cancel: invalid download index %1").arg(index)); + return; + } - if (info->m_State == STATE_DOWNLOADING) { - if ((info->m_Reply != nullptr) && (info->m_Reply->isRunning())) { - setState(info, STATE_PAUSING); - } else { - setState(info, STATE_PAUSED); - } - } else if ((info->m_State == STATE_FETCHINGMODINFO) || (info->m_State == STATE_FETCHINGFILEINFO)) { - setState(info, STATE_READY); - } + if (m_ActiveDownloads.at(index)->m_State == STATE_DOWNLOADING) { + setState(m_ActiveDownloads.at(index), STATE_CANCELING); + } } -void DownloadManager::resumeDownload(int index) { - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - reportError(tr("resume: invalid download index %1").arg(index)); - return; - } - DownloadInfo* info = m_ActiveDownloads[index]; - info->m_Tries = AUTOMATIC_RETRIES; - resumeDownloadInt(index); -} -void DownloadManager::resumeDownloadInt(int index) { - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - reportError(tr("resume (int): invalid download index %1").arg(index)); - return; - } - DownloadInfo* info = m_ActiveDownloads[index]; - if (info->isPausedState()) { - 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) { - info->m_CurrentUrl = (info->m_CurrentUrl + 1) % info->m_Urls.count(); - } - qDebug("request resume from url %s", qPrintable(info->currentURL())); - QNetworkRequest request(QUrl::fromEncoded(info->currentURL().toLocal8Bit())); - info->m_ResumePos = info->m_Output.size(); - qDebug("resume at %lld bytes", info->m_ResumePos); - QByteArray rangeHeader = "bytes=" + QByteArray::number(info->m_ResumePos) + "-"; - request.setRawHeader("Range", rangeHeader); - startDownload(m_NexusInterface->getAccessManager()->get(request), info, true); - } - emit update(index); -} +void DownloadManager::pauseDownload(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + reportError(tr("pause: invalid download index %1").arg(index)); + return; + } -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; }); - if (iter != m_ActiveDownloads.end()) { - return *iter; + DownloadInfo *info = m_ActiveDownloads.at(index); + + if (info->m_State == STATE_DOWNLOADING) { + if ((info->m_Reply != nullptr) && (info->m_Reply->isRunning())) { + setState(info, STATE_PAUSING); } else { - return nullptr; + setState(info, STATE_PAUSED); } + } else if ((info->m_State == STATE_FETCHINGMODINFO) || (info->m_State == STATE_FETCHINGFILEINFO)) { + setState(info, STATE_READY); + } +} + +void DownloadManager::resumeDownload(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + reportError(tr("resume: invalid download index %1").arg(index)); + return; + } + DownloadInfo *info = m_ActiveDownloads[index]; + info->m_Tries = AUTOMATIC_RETRIES; + resumeDownloadInt(index); +} + +void DownloadManager::resumeDownloadInt(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + reportError(tr("resume (int): invalid download index %1").arg(index)); + return; + } + DownloadInfo *info = m_ActiveDownloads[index]; + if (info->isPausedState()) { + 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) { + info->m_CurrentUrl = (info->m_CurrentUrl + 1) % info->m_Urls.count(); + } + qDebug("request resume from url %s", qPrintable(info->currentURL())); + QNetworkRequest request(QUrl::fromEncoded(info->currentURL().toLocal8Bit())); + info->m_ResumePos = info->m_Output.size(); + qDebug("resume at %lld bytes", info->m_ResumePos); + QByteArray rangeHeader = "bytes=" + QByteArray::number(info->m_ResumePos) + "-"; + request.setRawHeader("Range", rangeHeader); + startDownload(m_NexusInterface->getAccessManager()->get(request), info, true); + } + emit update(index); +} + + +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; }); + if (iter != m_ActiveDownloads.end()) { + return *iter; + } else { + return nullptr; + } } -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]; - if (info->m_FileInfo->repository != "Nexus") { - qWarning("re-querying file info is currently only possible with Nexus"); - return; - } +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]; - if (info->m_State < DownloadManager::STATE_READY) { - // UI shouldn't allow this - return; - } + if (info->m_FileInfo->repository != "Nexus") { + qWarning("re-querying file info is currently only possible with Nexus"); + return; + } - if (info->m_FileInfo->modID <= 0) { - QString fileName = getFileName(index); - QString ignore; - 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); - // careful now: while the dialog was displayed, events were processed. - // the download list might have changed and our info-ptr invalidated. - m_ActiveDownloads[index]->m_FileInfo->modID = modId; - return; - } + if (info->m_State < DownloadManager::STATE_READY) { + // UI shouldn't allow this + return; + } + + if (info->m_FileInfo->modID <= 0) { + QString fileName = getFileName(index); + QString ignore; + 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); + // careful now: while the dialog was displayed, events were processed. + // the download list might have changed and our info-ptr invalidated. + m_ActiveDownloads[index]->m_FileInfo->modID = modId; + return; } - info->m_ReQueried = true; - setState(info, STATE_FETCHINGMODINFO); + } + info->m_ReQueried = true; + setState(info, STATE_FETCHINGMODINFO); } -int DownloadManager::numTotalDownloads() const { return m_ActiveDownloads.size(); } -int DownloadManager::numPendingDownloads() const { return m_PendingDownloads.size(); } +int DownloadManager::numTotalDownloads() const +{ + return m_ActiveDownloads.size(); +} -std::pair DownloadManager::getPendingDownload(int index) { - if ((index < 0) || (index >= m_PendingDownloads.size())) { - throw MyException(tr("get pending: invalid download index %1").arg(index)); - } +int DownloadManager::numPendingDownloads() const +{ + return m_PendingDownloads.size(); +} + +std::pair DownloadManager::getPendingDownload(int index) +{ + if ((index < 0) || (index >= m_PendingDownloads.size())) { + throw MyException(tr("get pending: invalid download index %1").arg(index)); + } - return m_PendingDownloads.at(index); + return m_PendingDownloads.at(index); } -QString DownloadManager::getFilePath(int index) const { - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("get path: invalid download index %1").arg(index)); - } +QString DownloadManager::getFilePath(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("get path: invalid download index %1").arg(index)); + } - return m_OutputDirectory + "/" + m_ActiveDownloads.at(index)->m_FileName; -} - -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("Misc"); - default: - return tr("Unknown"); - } + return m_OutputDirectory + "/" + m_ActiveDownloads.at(index)->m_FileName; } -QString DownloadManager::getDisplayName(int index) const { - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("display name: invalid download index %1").arg(index)); - } +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("Misc"); + default: return tr("Unknown"); + } +} - DownloadInfo* info = m_ActiveDownloads.at(index); +QString DownloadManager::getDisplayName(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("display name: invalid download index %1").arg(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()); - } else { - doc.setHtml(info->m_FileName); - return doc.toPlainText(); - } + 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()); + } else { + doc.setHtml(info->m_FileName); + return doc.toPlainText(); + } } -QString DownloadManager::getFileName(int index) const { - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("file name: invalid download index %1").arg(index)); - } +QString DownloadManager::getFileName(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("file name: invalid download index %1").arg(index)); + } - return m_ActiveDownloads.at(index)->m_FileName; + return m_ActiveDownloads.at(index)->m_FileName; } -QDateTime DownloadManager::getFileTime(int index) const { - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("file time: invalid download index %1").arg(index)); - } +QDateTime DownloadManager::getFileTime(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("file time: invalid download index %1").arg(index)); + } - DownloadInfo* info = m_ActiveDownloads.at(index); - if (!info->m_Created.isValid()) { - info->m_Created = QFileInfo(info->m_Output).created(); - } + DownloadInfo *info = m_ActiveDownloads.at(index); + if (!info->m_Created.isValid()) { + info->m_Created = QFileInfo(info->m_Output).created(); + } - return info->m_Created; + return info->m_Created; } -qint64 DownloadManager::getFileSize(int index) const { - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("file size: invalid download index %1").arg(index)); - } +qint64 DownloadManager::getFileSize(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("file size: invalid download index %1").arg(index)); + } - return m_ActiveDownloads.at(index)->m_TotalSize; + return m_ActiveDownloads.at(index)->m_TotalSize; } -int DownloadManager::getProgress(int index) const { - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("progress: invalid download index %1").arg(index)); - } - return m_ActiveDownloads.at(index)->m_Progress; +int DownloadManager::getProgress(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("progress: invalid download index %1").arg(index)); + } + + return m_ActiveDownloads.at(index)->m_Progress; } -DownloadManager::DownloadState DownloadManager::getState(int index) const { - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("state: invalid download index %1").arg(index)); - } - return m_ActiveDownloads.at(index)->m_State; +DownloadManager::DownloadState DownloadManager::getState(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("state: invalid download index %1").arg(index)); + } + + 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); - if (info->m_FileInfo->repository != "Nexus") { - // other repositories currently don't support re-querying info anyway - return false; - } - return (info->m_FileInfo->fileID == 0) || (info->m_FileInfo->modID == 0); +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); + if (info->m_FileInfo->repository != "Nexus") { + // other repositories currently don't support re-querying info anyway + return false; + } + return (info->m_FileInfo->fileID == 0) || (info->m_FileInfo->modID == 0); } -int DownloadManager::getModID(int index) const { - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("mod id: invalid download index %1").arg(index)); - } - return m_ActiveDownloads.at(index)->m_FileInfo->modID; + +int DownloadManager::getModID(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("mod id: invalid download index %1").arg(index)); + } + return m_ActiveDownloads.at(index)->m_FileInfo->modID; } -bool DownloadManager::isHidden(int index) const { - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("ishidden: invalid download index %1").arg(index)); - } - return m_ActiveDownloads.at(index)->m_Hidden; +bool DownloadManager::isHidden(int index) const +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("ishidden: invalid download index %1").arg(index)); + } + return m_ActiveDownloads.at(index)->m_Hidden; } -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)); - } - return m_ActiveDownloads.at(index)->m_FileInfo; +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)); + } + + 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)); - } - DownloadInfo* info = m_ActiveDownloads.at(index); - QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); - metaFile.setValue("installed", true); - metaFile.setValue("uninstalled", false); +void DownloadManager::markInstalled(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("mark installed: invalid download index %1").arg(index)); + } + + DownloadInfo *info = m_ActiveDownloads.at(index); + QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); + metaFile.setValue("installed", true); + metaFile.setValue("uninstalled", false); - setState(m_ActiveDownloads.at(index), STATE_INSTALLED); + setState(m_ActiveDownloads.at(index), STATE_INSTALLED); } -void DownloadManager::markUninstalled(int index) { - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("mark uninstalled: invalid download index %1").arg(index)); - } - DownloadInfo* info = m_ActiveDownloads.at(index); - QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); - metaFile.setValue("uninstalled", true); +void DownloadManager::markUninstalled(int index) +{ + if ((index < 0) || (index >= m_ActiveDownloads.size())) { + throw MyException(tr("mark uninstalled: invalid download index %1").arg(index)); + } + + DownloadInfo *info = m_ActiveDownloads.at(index); + QSettings metaFile(info->m_Output.fileName() + ".meta", QSettings::IniFormat); + metaFile.setValue("uninstalled", true); - setState(m_ActiveDownloads.at(index), STATE_UNINSTALLED); + setState(m_ActiveDownloads.at(index), STATE_UNINSTALLED); } -QString DownloadManager::getDownloadFileName(const QString& baseName) const { - QString fullPath = m_OutputDirectory + "/" + baseName; - if (QFile::exists(fullPath)) { - int i = 1; - while (QFile::exists(QString("%1/%2_%3").arg(m_OutputDirectory).arg(i).arg(baseName))) { - ++i; - } - fullPath = QString("%1/%2_%3").arg(m_OutputDirectory).arg(i).arg(baseName); +QString DownloadManager::getDownloadFileName(const QString &baseName) const +{ + QString fullPath = m_OutputDirectory + "/" + baseName; + if (QFile::exists(fullPath)) { + int i = 1; + while (QFile::exists(QString("%1/%2_%3").arg(m_OutputDirectory).arg(i).arg(baseName))) { + ++i; } - return fullPath; + + fullPath = QString("%1/%2_%3").arg(m_OutputDirectory).arg(i).arg(baseName); + } + return fullPath; } -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)) { - return QString::fromUtf8(result.str(1).c_str()); - } +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)) { + return QString::fromUtf8(result.str(1).c_str()); } + } - return QString(); + return QString(); } -void DownloadManager::setState(DownloadManager::DownloadInfo* info, DownloadManager::DownloadState state) { - int row = 0; - for (int i = 0; i < m_ActiveDownloads.size(); ++i) { - if (m_ActiveDownloads[i] == info) { - row = i; - break; - } + +void DownloadManager::setState(DownloadManager::DownloadInfo *info, DownloadManager::DownloadState state) +{ + int row = 0; + for (int i = 0; i < m_ActiveDownloads.size(); ++i) { + if (m_ActiveDownloads[i] == info) { + row = i; + break; } - info->m_State = state; - switch (state) { + } + info->m_State = state; + switch (state) { case STATE_PAUSED: case STATE_ERROR: { - info->m_Reply->abort(); + info->m_Reply->abort(); } break; case STATE_CANCELED: { - info->m_Reply->abort(); + info->m_Reply->abort(); } break; case STATE_FETCHINGMODINFO: { - m_RequestIDs.insert( - m_NexusInterface->requestDescription(info->m_FileInfo->modID, this, info->m_DownloadID, QString())); + m_RequestIDs.insert(m_NexusInterface->requestDescription(info->m_FileInfo->modID, this, info->m_DownloadID, QString())); } break; case STATE_FETCHINGFILEINFO: { - m_RequestIDs.insert( - m_NexusInterface->requestFiles(info->m_FileInfo->modID, this, info->m_DownloadID, QString())); + m_RequestIDs.insert(m_NexusInterface->requestFiles(info->m_FileInfo->modID, this, info->m_DownloadID, QString())); } break; case STATE_READY: { - createMetaFile(info); - emit downloadComplete(row); + createMetaFile(info); + emit downloadComplete(row); } break; - default: /* NOP */ - break; - } - emit stateChanged(row, state); + default: /* NOP */ break; + } + emit stateChanged(row, state); } -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) { - if (m_ActiveDownloads[i]->m_Reply == reply) { - if (index != nullptr) { - *index = i; - } - return m_ActiveDownloads[i]; - } - } - return nullptr; -} -void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) { - if (bytesTotal == 0) { - return; - } - int index = 0; - try { - DownloadInfo* info = findDownload(this->sender(), &index); - if (info != nullptr) { - if (info->m_State == STATE_CANCELING) { - setState(info, STATE_CANCELED); - } else if (info->m_State == STATE_PAUSING) { - setState(info, STATE_PAUSED); - } else { - if (bytesTotal > info->m_TotalSize) { - info->m_TotalSize = bytesTotal; - } - int oldProgress = info->m_Progress; - info->m_Progress = ((info->m_ResumePos + bytesReceived) * 100) / (info->m_ResumePos + bytesTotal); - TaskProgressManager::instance().updateProgress(info->m_TaskProgressId, bytesReceived, bytesTotal); - if (oldProgress != info->m_Progress) { - emit update(index); - } - } - } - } catch (const std::bad_alloc&) { - reportError(tr("Memory allocation error (in processing progress event).")); +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) { + if (m_ActiveDownloads[i]->m_Reply == reply) { + if (index != nullptr) { + *index = i; + } + return m_ActiveDownloads[i]; } + } + return nullptr; } -void DownloadManager::downloadReadyRead() { - try { - DownloadInfo* info = findDownload(this->sender()); - if (info != nullptr) { - info->m_Output.write(info->m_Reply->readAll()); - } - } catch (const std::bad_alloc&) { - reportError(tr("Memory allocation error (in processing downloaded data).")); - } -} -void DownloadManager::createMetaFile(DownloadInfo* info) { - QSettings metaFile(QString("%1.meta").arg(info->m_Output.fileName()), QSettings::IniFormat); - metaFile.setValue("modID", info->m_FileInfo->modID); - metaFile.setValue("fileID", info->m_FileInfo->fileID); - metaFile.setValue("url", info->m_Urls.join(";")); - metaFile.setValue("name", info->m_FileInfo->name); - metaFile.setValue("description", info->m_FileInfo->description); - metaFile.setValue("modName", info->m_FileInfo->modName); - metaFile.setValue("version", info->m_FileInfo->version.canonicalString()); - metaFile.setValue("newestVersion", info->m_FileInfo->newestVersion.canonicalString()); - metaFile.setValue("fileTime", info->m_FileInfo->fileTime); - metaFile.setValue("fileCategory", info->m_FileInfo->fileCategory); - metaFile.setValue("category", info->m_FileInfo->categoryID); - metaFile.setValue("repository", info->m_FileInfo->repository); - metaFile.setValue("userData", info->m_FileInfo->userData); - 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)); - metaFile.setValue("removed", info->m_Hidden); - - // slightly hackish... - for (int i = 0; i < m_ActiveDownloads.size(); ++i) { - if (m_ActiveDownloads[i] == info) { - emit update(i); +void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) +{ + if (bytesTotal == 0) { + return; + } + int index = 0; + try { + DownloadInfo *info = findDownload(this->sender(), &index); + if (info != nullptr) { + if (info->m_State == STATE_CANCELING) { + setState(info, STATE_CANCELED); + } else if (info->m_State == STATE_PAUSING) { + setState(info, STATE_PAUSED); + } else { + if (bytesTotal > info->m_TotalSize) { + info->m_TotalSize = bytesTotal; } + int oldProgress = info->m_Progress; + info->m_Progress = ((info->m_ResumePos + bytesReceived) * 100) / (info->m_ResumePos + bytesTotal); + TaskProgressManager::instance().updateProgress(info->m_TaskProgressId, bytesReceived, bytesTotal); + if (oldProgress != info->m_Progress) { + emit update(index); + } + } } + } catch (const std::bad_alloc&) { + reportError(tr("Memory allocation error (in processing progress event).")); + } } -void DownloadManager::nxmDescriptionAvailable(int, QVariant userData, QVariant resultData, int requestID) { - std::set::iterator idIter = m_RequestIDs.find(requestID); - if (idIter == m_RequestIDs.end()) { - return; - } else { - m_RequestIDs.erase(idIter); - } - - QVariantMap result = resultData.toMap(); - - 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()); - info->m_FileInfo->modName = doc.toPlainText(); - info->m_FileInfo->newestVersion.parse(result["version"].toString()); - if (info->m_FileInfo->fileID != 0) { - setState(info, STATE_READY); - } else { - setState(info, STATE_FETCHINGFILEINFO); - } -} - -QDateTime DownloadManager::matchDate(const QString& timeString) { - if (m_DateExpression.exactMatch(timeString)) { - return QDateTime::fromMSecsSinceEpoch(m_DateExpression.cap(1).toLongLong()); - } else { - qWarning("date not matched: %s", qPrintable(timeString)); - return QDateTime::currentDateTime(); - } -} - -static EFileCategory convertFileCategory(int id) { - // TODO: need to handle file categories in the mod page plugin - switch (id) { - case 0: - return TYPE_MAIN; - case 1: - return TYPE_UPDATE; - case 2: - return TYPE_OPTION; - default: - return TYPE_MAIN; - } -} -void DownloadManager::nxmFilesAvailable(int, QVariant userData, QVariant resultData, int requestID) { - std::set::iterator idIter = m_RequestIDs.find(requestID); - if (idIter == m_RequestIDs.end()) { - return; +void DownloadManager::downloadReadyRead() +{ + try { + DownloadInfo *info = findDownload(this->sender()); + if (info != nullptr) { + info->m_Output.write(info->m_Reply->readAll()); + } + } catch (const std::bad_alloc&) { + reportError(tr("Memory allocation error (in processing downloaded data).")); + } +} + + +void DownloadManager::createMetaFile(DownloadInfo *info) +{ + QSettings metaFile(QString("%1.meta").arg(info->m_Output.fileName()), QSettings::IniFormat); + metaFile.setValue("modID", info->m_FileInfo->modID); + metaFile.setValue("fileID", info->m_FileInfo->fileID); + metaFile.setValue("url", info->m_Urls.join(";")); + metaFile.setValue("name", info->m_FileInfo->name); + metaFile.setValue("description", info->m_FileInfo->description); + metaFile.setValue("modName", info->m_FileInfo->modName); + metaFile.setValue("version", info->m_FileInfo->version.canonicalString()); + metaFile.setValue("newestVersion", info->m_FileInfo->newestVersion.canonicalString()); + metaFile.setValue("fileTime", info->m_FileInfo->fileTime); + metaFile.setValue("fileCategory", info->m_FileInfo->fileCategory); + metaFile.setValue("category", info->m_FileInfo->categoryID); + metaFile.setValue("repository", info->m_FileInfo->repository); + metaFile.setValue("userData", info->m_FileInfo->userData); + 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)); + metaFile.setValue("removed", info->m_Hidden); + + // slightly hackish... + for (int i = 0; i < m_ActiveDownloads.size(); ++i) { + if (m_ActiveDownloads[i] == info) { + emit update(i); + } + } +} + + +void DownloadManager::nxmDescriptionAvailable(int, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator idIter = m_RequestIDs.find(requestID); + if (idIter == m_RequestIDs.end()) { + return; + } else { + m_RequestIDs.erase(idIter); + } + + QVariantMap result = resultData.toMap(); + + 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()); + info->m_FileInfo->modName = doc.toPlainText(); + info->m_FileInfo->newestVersion.parse(result["version"].toString()); + if (info->m_FileInfo->fileID != 0) { + setState(info, STATE_READY); + } else { + setState(info, STATE_FETCHINGFILEINFO); + } +} + + +QDateTime DownloadManager::matchDate(const QString &timeString) +{ + if (m_DateExpression.exactMatch(timeString)) { + return QDateTime::fromMSecsSinceEpoch(m_DateExpression.cap(1).toLongLong()); + } else { + qWarning("date not matched: %s", qPrintable(timeString)); + return QDateTime::currentDateTime(); + } +} + + +static EFileCategory convertFileCategory(int id) +{ + // TODO: need to handle file categories in the mod page plugin + switch (id) { + case 0: return TYPE_MAIN; + case 1: return TYPE_UPDATE; + case 2: return TYPE_OPTION; + default: return TYPE_MAIN; + } +} + + +void DownloadManager::nxmFilesAvailable(int, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator idIter = m_RequestIDs.find(requestID); + if (idIter == m_RequestIDs.end()) { + return; + } else { + m_RequestIDs.erase(idIter); + } + + DownloadInfo *info = downloadInfoByID(userData.toInt()); + if (info == nullptr) return; + + QVariantList result = resultData.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; + + QRegExp expression("^\\d_(.*)$"); + if (expression.indexIn(alternativeLocalName) == 0) { + alternativeLocalName = expression.cap(1); + } + + bool found = false; + + for (QVariant file : result) { + QVariantMap fileInfo = file.toMap(); + QString fileName = fileInfo["uri"].toString(); + QString fileNameVariant = fileName.mid(0).replace(' ', '_'); + if ((fileName == info->m_FileName) || (fileName == alternativeLocalName) || + (fileNameVariant == info->m_FileName) || (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; + } + // we receive some names html-encoded. This is used to decode it + QTextDocument doc; + doc.setHtml(fileInfo["modName"].toString()); + info->m_FileInfo->modName = doc.toPlainText(); + info->m_FileInfo->fileCategory = convertFileCategory(fileInfo["category_id"].toInt()); + info->m_FileInfo->fileTime = matchDate(fileInfo["date"].toString()); + info->m_FileInfo->fileID = fileInfo["id"].toInt(); + found = true; + break; + } + } + + if (info->m_ReQueried) { + 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?")); } else { - m_RequestIDs.erase(idIter); - } - - DownloadInfo* info = downloadInfoByID(userData.toInt()); - if (info == nullptr) - return; - - QVariantList result = resultData.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; - - QRegExp expression("^\\d_(.*)$"); - if (expression.indexIn(alternativeLocalName) == 0) { - alternativeLocalName = expression.cap(1); - } - - bool found = false; - - for (QVariant file : result) { + SelectionDialog selection(tr("No file on Nexus matches the selected file by name. Please manually choose the correct one.")); + for (QVariant file : result) { QVariantMap fileInfo = file.toMap(); - QString fileName = fileInfo["uri"].toString(); - QString fileNameVariant = fileName.mid(0).replace(' ', '_'); - if ((fileName == info->m_FileName) || (fileName == alternativeLocalName) || - (fileNameVariant == info->m_FileName) || (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; - } - // we receive some names html-encoded. This is used to decode it - QTextDocument doc; - doc.setHtml(fileInfo["modName"].toString()); - info->m_FileInfo->modName = doc.toPlainText(); - info->m_FileInfo->fileCategory = convertFileCategory(fileInfo["category_id"].toInt()); - info->m_FileInfo->fileTime = matchDate(fileInfo["date"].toString()); - info->m_FileInfo->fileID = fileInfo["id"].toInt(); - found = true; - break; - } + selection.addChoice(fileInfo["uri"].toString(), "", file); + } + if (selection.exec() == QDialog::Accepted) { + QVariantMap fileInfo = selection.getChoiceData().toMap(); + info->m_FileInfo->name = fileInfo["name"].toString(); + info->m_FileInfo->version.parse(fileInfo["version"].toString()); + info->m_FileInfo->fileCategory = convertFileCategory(fileInfo["category_id"].toInt()); + info->m_FileInfo->fileID = fileInfo["id"].toInt(); + } else { + emit showMessage(tr("No matching file found on Nexus! Maybe this file is no longer available or it was renamed?")); + } } - - if (info->m_ReQueried) { - 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?")); - } else { - SelectionDialog selection( - tr("No file on Nexus matches the selected file by name. Please manually choose the correct one.")); - for (QVariant file : result) { - QVariantMap fileInfo = file.toMap(); - selection.addChoice(fileInfo["uri"].toString(), "", file); - } - if (selection.exec() == QDialog::Accepted) { - QVariantMap fileInfo = selection.getChoiceData().toMap(); - info->m_FileInfo->name = fileInfo["name"].toString(); - info->m_FileInfo->version.parse(fileInfo["version"].toString()); - info->m_FileInfo->fileCategory = convertFileCategory(fileInfo["category_id"].toInt()); - info->m_FileInfo->fileID = fileInfo["id"].toInt(); - } else { - 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) { - qWarning("could not determine file id for %s (state %d)", info->m_FileName.toUtf8().constData(), - info->m_State); - } + } else { + if (info->m_FileInfo->fileID == 0) { + qWarning("could not determine file id for %s (state %d)", + info->m_FileName.toUtf8().constData(), info->m_State); } + } - setState(info, STATE_READY); + setState(info, STATE_READY); } -void DownloadManager::nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, - int requestID) { - std::set::iterator idIter = m_RequestIDs.find(requestID); - if (idIter == m_RequestIDs.end()) { - return; - } else { - m_RequestIDs.erase(idIter); - } - ModRepositoryFileInfo* info = new ModRepositoryFileInfo(); +void DownloadManager::nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator idIter = m_RequestIDs.find(requestID); + if (idIter == m_RequestIDs.end()) { + return; + } else { + m_RequestIDs.erase(idIter); + } - QVariantMap result = resultData.toMap(); - info->name = result["name"].toString(); - info->version.parse(result["version"].toString()); - if (!info->version.isValid()) { - info->version = info->newestVersion; - } - info->fileName = result["uri"].toString(); - info->fileCategory = result["category_id"].toInt(); - info->fileTime = matchDate(result["date"].toString()); - info->description = BBCode::convertToHTML(result["description"].toString()); + ModRepositoryFileInfo *info = new ModRepositoryFileInfo(); - info->repository = "Nexus"; - info->modID = modID; - info->fileID = fileID; + QVariantMap result = resultData.toMap(); + info->name = result["name"].toString(); + info->version.parse(result["version"].toString()); + if (!info->version.isValid()) { + info->version = info->newestVersion; + } + info->fileName = result["uri"].toString(); + info->fileCategory = result["category_id"].toInt(); + info->fileTime = matchDate(result["date"].toString()); + info->description = BBCode::convertToHTML(result["description"].toString()); - QObject* test = info; - m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(modID, fileID, this, qVariantFromValue(test), QString())); + info->repository = "Nexus"; + info->modID = modID; + info->fileID = fileID; + + QObject *test = info; + m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(modID, fileID, this, qVariantFromValue(test), QString())); } -static int evaluateFileInfoMap(const QVariantMap& map, const std::map& preferredServers) { - int result = 0; +static int evaluateFileInfoMap(const QVariantMap &map, const std::map &preferredServers) +{ + int result = 0; - int users = map["ConnectedUsers"].toInt(); - // 0 users is probably a sign that the server is offline. Since there is currently no - // mechanism to try a different server, we avoid those without users - if (users == 0) { - result -= 500; - } else { - result -= users; - } + int users = map["ConnectedUsers"].toInt(); + // 0 users is probably a sign that the server is offline. Since there is currently no + // mechanism to try a different server, we avoid those without users + if (users == 0) { + result -= 500; + } else { + result -= users; + } - auto preference = preferredServers.find(map["Name"].toString()); + auto preference = preferredServers.find(map["Name"].toString()); - if (preference != preferredServers.end()) { - result += 100 + preference->second * 20; - } + if (preference != preferredServers.end()) { + result += 100 + preference->second * 20; + } - if (map["IsPremium"].toBool()) - result += 5; + if (map["IsPremium"].toBool()) result += 5; - return result; + return result; } // sort function to sort by best download server -bool DownloadManager::ServerByPreference(const std::map& preferredServers, const QVariant& LHS, - const QVariant& RHS) { - return evaluateFileInfoMap(LHS.toMap(), preferredServers) > evaluateFileInfoMap(RHS.toMap(), preferredServers); +bool DownloadManager::ServerByPreference(const std::map &preferredServers, const QVariant &LHS, const QVariant &RHS) +{ + return evaluateFileInfoMap(LHS.toMap(), preferredServers) > evaluateFileInfoMap(RHS.toMap(), preferredServers); } -int DownloadManager::startDownloadURLs(const QStringList& urls) { - ModRepositoryFileInfo info; - addDownload(urls, -1, -1, &info); - return m_ActiveDownloads.size() - 1; +int DownloadManager::startDownloadURLs(const QStringList &urls) +{ + ModRepositoryFileInfo info; + addDownload(urls, -1, -1, &info); + return m_ActiveDownloads.size() - 1; } -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)); - return newID; +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)); + return newID; } -QString DownloadManager::downloadPath(int id) { return getFilePath(id); } +QString DownloadManager::downloadPath(int id) +{ + return getFilePath(id); +} -int DownloadManager::indexByName(const QString& fileName) const { - for (int i = 0; i < m_ActiveDownloads.size(); ++i) { - if (m_ActiveDownloads[i]->m_FileName == fileName) { - return i; - } +int DownloadManager::indexByName(const QString &fileName) const +{ + for (int i = 0; i < m_ActiveDownloads.size(); ++i) { + if (m_ActiveDownloads[i]->m_FileName == fileName) { + return i; } - return -1; + } + return -1; } -void DownloadManager::nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, - int requestID) { - std::set::iterator idIter = m_RequestIDs.find(requestID); - if (idIter == m_RequestIDs.end()) { - return; - } else { - m_RequestIDs.erase(idIter); - } +void DownloadManager::nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator idIter = m_RequestIDs.find(requestID); + if (idIter == m_RequestIDs.end()) { + return; + } else { + m_RequestIDs.erase(idIter); + } - ModRepositoryFileInfo* info = qobject_cast(qvariant_cast(userData)); - QVariantList resultList = resultData.toList(); - if (resultList.length() == 0) { - removePending(modID, fileID); - emit showMessage(tr("No download server available. Please try again later.")); - return; - } + ModRepositoryFileInfo *info = qobject_cast(qvariant_cast(userData)); + QVariantList resultList = resultData.toList(); + if (resultList.length() == 0) { + removePending(modID, fileID); + emit showMessage(tr("No download server available. Please try again later.")); + return; + } - std::sort(resultList.begin(), resultList.end(), - boost::bind(&DownloadManager::ServerByPreference, m_PreferredServers, _1, _2)); + std::sort(resultList.begin(), resultList.end(), boost::bind(&DownloadManager::ServerByPreference, m_PreferredServers, _1, _2)); - info->userData["downloadMap"] = resultList; + info->userData["downloadMap"] = resultList; - QStringList URLs; + QStringList URLs; - foreach (const QVariant& server, resultList) { URLs.append(server.toMap()["URI"].toString()); } - addDownload(URLs, modID, fileID, info); + foreach (const QVariant &server, resultList) { + URLs.append(server.toMap()["URI"].toString()); + } + addDownload(URLs, modID, fileID, info); } -void DownloadManager::nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, - const QString& errorString) { - std::set::iterator idIter = m_RequestIDs.find(requestID); - if (idIter == m_RequestIDs.end()) { - return; - } else { - m_RequestIDs.erase(idIter); - } - int index = 0; - - for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); - ++iter, ++index) { - DownloadInfo* info = *iter; - if (info->m_FileInfo->modID == modID) { - if (info->m_State < STATE_FETCHINGMODINFO) { - m_ActiveDownloads.erase(iter); - delete info; - } else { - setState(info, STATE_READY); - } - emit update(index); - break; - } - } - - removePending(modID, fileID); - emit showMessage(tr("Failed to request file info from nexus: %1").arg(errorString)); -} +void DownloadManager::nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorString) +{ + std::set::iterator idIter = m_RequestIDs.find(requestID); + if (idIter == m_RequestIDs.end()) { + return; + } else { + m_RequestIDs.erase(idIter); + } -void DownloadManager::downloadFinished() { - int index = 0; + int index = 0; - DownloadInfo* info = findDownload(this->sender(), &index); - if (info != nullptr) { - QNetworkReply* reply = info->m_Reply; - QByteArray data; - if (reply->isOpen()) { - data = reply->readAll(); - info->m_Output.write(data); - } - info->m_Output.close(); - 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_Output.size() == 0) || - ((reply->error() != QNetworkReply::NoError) && - (reply->error() != QNetworkReply::OperationCanceledError)) || - textData) { - if (info->m_Tries == 0) { - if (textData && (reply->error() == QNetworkReply::NoError)) { - emit showMessage(tr("Download failed. Server reported: %1").arg(QString(data))); - } else { - emit showMessage(tr("Download failed: %1 (%2)").arg(reply->errorString()).arg(reply->error())); - } - } - error = true; - setState(info, STATE_PAUSING); - } + for (QVector::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter, ++index) { + DownloadInfo *info = *iter; + if (info->m_FileInfo->modID == modID) { + if (info->m_State < STATE_FETCHINGMODINFO) { + m_ActiveDownloads.erase(iter); + delete info; + } else { + setState(info, STATE_READY); + } + emit update(index); + break; + } + } + + removePending(modID, fileID); + emit showMessage(tr("Failed to request file info from nexus: %1").arg(errorString)); +} + + +void DownloadManager::downloadFinished() +{ + int index = 0; + + DownloadInfo *info = findDownload(this->sender(), &index); + if (info != nullptr) { + QNetworkReply *reply = info->m_Reply; + QByteArray data; + if (reply->isOpen()) { + data = reply->readAll(); + info->m_Output.write(data); + } + info->m_Output.close(); + 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_Output.size() == 0) || + ((reply->error() != QNetworkReply::NoError) && (reply->error() != QNetworkReply::OperationCanceledError)) || + textData) { + if (info->m_Tries == 0) { + if (textData && (reply->error() == QNetworkReply::NoError)) { + emit showMessage(tr("Download failed. Server reported: %1").arg(QString(data))); + } else { + emit showMessage(tr("Download failed: %1 (%2)").arg(reply->errorString()).arg(reply->error())); + } } - - if (info->m_State == STATE_CANCELING) { - setState(info, STATE_CANCELED); - } else if (info->m_State == STATE_PAUSING) { - if (info->m_Output.isOpen()) { - info->m_Output.write(info->m_Reply->readAll()); - } - - if (error) { - setState(info, STATE_ERROR); - } else { - setState(info, STATE_PAUSED); - } + error = true; + setState(info, STATE_PAUSING); + } + } + + if (info->m_State == STATE_CANCELING) { + setState(info, STATE_CANCELED); + } else if (info->m_State == STATE_PAUSING) { + if (info->m_Output.isOpen()) { + info->m_Output.write(info->m_Reply->readAll()); + } + + if (error) { + setState(info, STATE_ERROR); + } else { + setState(info, STATE_PAUSED); + } + } + + if (info->m_State == STATE_CANCELED) { + emit aboutToUpdate(); + info->m_Output.remove(); + delete info; + m_ActiveDownloads.erase(m_ActiveDownloads.begin() + index); + emit update(-1); + } else if (info->isPausedState()) { + info->m_Output.close(); + createMetaFile(info); + emit update(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()) { + QVariantMap serverMap = server.toMap(); + if (serverMap["URI"].toString() == url) { + int deltaTime = info->m_StartTime.secsTo(QTime::currentTime()); + if (deltaTime > 5) { + emit downloadSpeed(serverMap["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 + if (isNexus) { + setState(info, STATE_FETCHINGMODINFO); + } else { + setState(info, STATE_NOFETCH); + } + + QString newName = getFileNameFromNetworkReply(reply); + QString oldName = QFileInfo(info->m_Output).fileName(); + if (!newName.isEmpty() && (newName != oldName)) { + info->setName(getDownloadFileName(newName), true); + } else { + info->setName(m_OutputDirectory + "/" + info->m_FileName, true); // don't rename but remove the ".unfinished" extension + } + + if (!isNexus) { + setState(info, STATE_READY); + } - if (info->m_State == STATE_CANCELED) { - emit aboutToUpdate(); - info->m_Output.remove(); - delete info; - m_ActiveDownloads.erase(m_ActiveDownloads.begin() + index); - emit update(-1); - } else if (info->isPausedState()) { - info->m_Output.close(); - createMetaFile(info); - emit update(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()) { - QVariantMap serverMap = server.toMap(); - if (serverMap["URI"].toString() == url) { - int deltaTime = info->m_StartTime.secsTo(QTime::currentTime()); - if (deltaTime > 5) { - emit downloadSpeed(serverMap["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 - if (isNexus) { - setState(info, STATE_FETCHINGMODINFO); - } else { - setState(info, STATE_NOFETCH); - } - - QString newName = getFileNameFromNetworkReply(reply); - QString oldName = QFileInfo(info->m_Output).fileName(); - if (!newName.isEmpty() && (newName != oldName)) { - info->setName(getDownloadFileName(newName), true); - } else { - info->setName(m_OutputDirectory + "/" + info->m_FileName, - true); // don't rename but remove the ".unfinished" extension - } - - if (!isNexus) { - setState(info, STATE_READY); - } - - emit update(index); - } - reply->close(); - reply->deleteLater(); + emit update(index); + } + reply->close(); + reply->deleteLater(); - if ((info->m_Tries > 0) && error) { - --info->m_Tries; - resumeDownloadInt(index); - } - } else { - qWarning("no download index %d", index); + if ((info->m_Tries > 0) && error) { + --info->m_Tries; + resumeDownloadInt(index); } + } else { + qWarning("no download index %d", index); + } } -void DownloadManager::downloadError(QNetworkReply::NetworkError error) { - if (error != QNetworkReply::OperationCanceledError) { - QNetworkReply* reply = qobject_cast(sender()); - qWarning("%s (%d)", reply != nullptr ? qPrintable(reply->errorString()) : "Download error occured", error); - } + +void DownloadManager::downloadError(QNetworkReply::NetworkError error) +{ + if (error != QNetworkReply::OperationCanceledError) { + QNetworkReply *reply = qobject_cast(sender()); + qWarning("%s (%d)", reply != nullptr ? qPrintable(reply->errorString()) + : "Download error occured", + error); + } } -void DownloadManager::metaDataChanged() { - int index = 0; - DownloadInfo* info = findDownload(this->sender(), &index); - if (info != nullptr) { - QString newName = getFileNameFromNetworkReply(info->m_Reply); - if (!newName.isEmpty() && (newName != info->m_FileName)) { - info->setName(getDownloadFileName(newName), true); - refreshAlphabeticalTranslation(); - if (!info->m_Output.isOpen() && !info->m_Output.open(QIODevice::WriteOnly | QIODevice::Append)) { - reportError(tr("failed to re-open %1").arg(info->m_FileName)); - setState(info, STATE_CANCELING); - } - } - } else { - qWarning("meta data event for unknown download"); +void DownloadManager::metaDataChanged() +{ + int index = 0; + + DownloadInfo *info = findDownload(this->sender(), &index); + if (info != nullptr) { + QString newName = getFileNameFromNetworkReply(info->m_Reply); + if (!newName.isEmpty() && (newName != info->m_FileName)) { + info->setName(getDownloadFileName(newName), true); + refreshAlphabeticalTranslation(); + if (!info->m_Output.isOpen() && !info->m_Output.open(QIODevice::WriteOnly | QIODevice::Append)) { + reportError(tr("failed to re-open %1").arg(info->m_FileName)); + setState(info, STATE_CANCELING); + } } + } else { + qWarning("meta data event for unknown download"); + } } -void DownloadManager::directoryChanged(const QString&) { refreshList(); } +void DownloadManager::directoryChanged(const QString&) +{ + refreshList(); +} -void DownloadManager::managedGameChanged(MOBase::IPluginGame const* managedGame) { m_ManagedGame = managedGame; } +void DownloadManager::managedGameChanged(MOBase::IPluginGame const *managedGame) +{ + m_ManagedGame = managedGame; +} diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 39dc56f4..e63a0113 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -20,496 +20,495 @@ along with Mod Organizer. If not, see . #ifndef DOWNLOADMANAGER_H #define DOWNLOADMANAGER_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 -namespace MOBase { -class IPluginGame; -} +namespace MOBase { class IPluginGame; } class NexusInterface; /*! * \brief manages downloading of files and provides progress information for gui elements **/ -class DownloadManager : public MOBase::IDownloadManager { - Q_OBJECT - -public: - enum DownloadState { - STATE_STARTED = 0, - STATE_DOWNLOADING, - STATE_CANCELING, - STATE_PAUSING, - STATE_CANCELED, - STATE_PAUSED, - STATE_ERROR, - STATE_FETCHINGMODINFO, - STATE_FETCHINGFILEINFO, - STATE_NOFETCH, - STATE_READY, - STATE_INSTALLED, - STATE_UNINSTALLED - }; - -private: - struct DownloadInfo { - ~DownloadInfo() { delete m_FileInfo; } - unsigned int m_DownloadID; - QString m_FileName; - QFile m_Output; - QNetworkReply* m_Reply; - QTime m_StartTime; - qint64 m_PreResumeSize; - int m_Progress; - DownloadState m_State; - int m_CurrentUrl; - QStringList m_Urls; - qint64 m_ResumePos; - qint64 m_TotalSize; - QDateTime m_Created; // used as a cache in DownloadManager::getFileTime, may not be valid elsewhere - - int m_Tries; - bool m_ReQueried; - - quint32 m_TaskProgressId; - - 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); - - /** - * @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 - **/ - void setName(QString newName, bool renameFile); - - unsigned int downloadID() { return m_DownloadID; } - - bool isPausedState(); - - QString currentURL(); - - private: - static unsigned int s_NextDownloadID; - - private: - DownloadInfo() : m_TotalSize(0), m_ReQueried(false), m_Hidden(false) {} - }; +class DownloadManager : public MOBase::IDownloadManager +{ + Q_OBJECT public: - /** - * @brief constructor - * - * @param nexusInterface interface to use to retrieve information from the relevant nexus page - * @param parent parent object - **/ - explicit DownloadManager(NexusInterface* nexusInterface, QObject* parent); - - ~DownloadManager(); - - /** - * @brief determine if a download is currently in progress - * - * @return true if there is currently a download in progress - **/ - bool downloadsInProgress(); - - /** - * @brief set the output directory to write to - * - * @param outputDirectory the new output directory - **/ - void setOutputDirectory(const QString& outputDirectory); - - /** - * @return current download directory - **/ - QString getOutputDirectory() const { return m_OutputDirectory; } - - /** - * @brief setPreferredServers set the list of preferred servers - */ - void setPreferredServers(const std::map& preferredServers); - - /** - * @brief set the list of supported extensions - * @param extensions list of supported extensions - */ - void setSupportedExtensions(const QStringList& extensions); - - /** - * @brief sets whether hidden files are to be shown after all - */ - void setShowHidden(bool showHidden); - - /** - * @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 - **/ - 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 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 - **/ - bool addDownload(QNetworkReply* reply, const QStringList& URLs, const QString& fileName, int modID, int fileID = 0, - const MOBase::ModRepositoryFileInfo* fileInfo = new MOBase::ModRepositoryFileInfo()); - - /** - * @brief start a download using a nxm-link - * - * 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 - **/ - void addNXMDownload(const QString& url); - - /** - * @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) - * @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()[) - * @return pair of modid, fileid - */ - std::pair getPendingDownload(int index); - /** - * @brief retrieve the full path to the download specified by index - * - * @param index the index to look up - * @return absolute path of the file - **/ - QString getFilePath(int index) const; + enum DownloadState { + STATE_STARTED = 0, + STATE_DOWNLOADING, + STATE_CANCELING, + STATE_PAUSING, + STATE_CANCELED, + STATE_PAUSED, + STATE_ERROR, + STATE_FETCHINGMODINFO, + STATE_FETCHINGFILEINFO, + STATE_NOFETCH, + STATE_READY, + STATE_INSTALLED, + STATE_UNINSTALLED + }; - /** - * @brief retrieve a descriptive name of the download specified by index - * - * @param index index of the file to look up - * @return display name of the file - **/ - QString getDisplayName(int index) const; - - /** - * @brief retrieve the filename of the download specified by index - * - * @param index index of the file to look up - * @return name of the file - **/ - QString getFileName(int index) const; - - /** - * @brief retrieve the file size of the download specified by index - * - * @param index index of the file to look up - * @return size of the file (total size during download) - */ - qint64 getFileSize(int index) const; - - /** - * @brief retrieve the creation time of the download specified by index - * @param index index of the file to look up - * @return size of the file (total size during download) - */ - QDateTime getFileTime(int index) const; - - /** - * @brief retrieve the current progress of the download specified by index - * - * @param index index of the file to look up - * @return progress of the download in percent (integer) - **/ - int getProgress(int index) const; - - /** - * @brief retrieve the current state of the download - * - * retrieve the current state of the download. A download usually goes through - * the following states: - * started -> downloading -> fetching mod info -> fetching file info -> done - * in case of downloads started via nxm-link, file information is fetched first - * - * @param index index of the file to look up - * @return the download state - **/ - DownloadState getState(int index) const; - - /** - * @param index index of the file to look up - * @return true if the nexus information for this download is not complete - **/ - bool isInfoIncomplete(int index) const; - - /** - * @brief retrieve the nexus mod id of the download specified by index - * - * @param index index of the file to look up - * @return the nexus mod id - **/ - int getModID(int index) const; - - /** - * @brief determine if the specified file is supposed to be hidden - * @param index index of the file to look up - * @return true if the specified file is supposed to be hidden - */ - bool isHidden(int index) const; +private: - /** - * @brief retrieve all nexus info of the download specified by index - * - * @param index index of the file to look up - * @return the nexus mod information + struct DownloadInfo { + ~DownloadInfo() { delete m_FileInfo; } + unsigned int m_DownloadID; + QString m_FileName; + QFile m_Output; + QNetworkReply *m_Reply; + QTime m_StartTime; + qint64 m_PreResumeSize; + int m_Progress; + DownloadState m_State; + int m_CurrentUrl; + QStringList m_Urls; + qint64 m_ResumePos; + qint64 m_TotalSize; + QDateTime m_Created; // used as a cache in DownloadManager::getFileTime, may not be valid elsewhere + + int m_Tries; + bool m_ReQueried; + + quint32 m_TaskProgressId; + + 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); + + /** + * @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 **/ - const MOBase::ModRepositoryFileInfo* getFileInfo(int index) const; - - /** - * @brief mark a download as installed - * - * @param index index of the file to mark installed - */ - void markInstalled(int index); - - /** - * @brief mark a download as uninstalled - * - * @param index index of the file to mark uninstalled - */ - void markUninstalled(int index); - - /** - * @brief refreshes the list of downloads - */ - void refreshList(); - - /** - * @brief Sort function for download servers - * @param LHS - * @param RHS - * @return - */ - static bool ServerByPreference(const std::map& preferredServers, const QVariant& LHS, - const QVariant& RHS); + void setName(QString newName, bool renameFile); - virtual int startDownloadURLs(const QStringList& urls); + unsigned int downloadID() { return m_DownloadID; } - virtual int startDownloadNexusFile(int modID, int fileID); + bool isPausedState(); - virtual QString downloadPath(int id); + QString currentURL(); + private: + static unsigned int s_NextDownloadID; + private: + DownloadInfo() : m_TotalSize(0), m_ReQueried(false), m_Hidden(false) {} + }; - /** - * @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; +public: - void pauseAll(); + /** + * @brief constructor + * + * @param nexusInterface interface to use to retrieve information from the relevant nexus page + * @param parent parent object + **/ + explicit DownloadManager(NexusInterface *nexusInterface, QObject *parent); + + ~DownloadManager(); + + /** + * @brief determine if a download is currently in progress + * + * @return true if there is currently a download in progress + **/ + bool downloadsInProgress(); + + /** + * @brief set the output directory to write to + * + * @param outputDirectory the new output directory + **/ + void setOutputDirectory(const QString &outputDirectory); + + /** + * @return current download directory + **/ + QString getOutputDirectory() const { return m_OutputDirectory; } + + /** + * @brief setPreferredServers set the list of preferred servers + */ + void setPreferredServers(const std::map &preferredServers); + + /** + * @brief set the list of supported extensions + * @param extensions list of supported extensions + */ + void setSupportedExtensions(const QStringList &extensions); + + /** + * @brief sets whether hidden files are to be shown after all + */ + void setShowHidden(bool showHidden); + + /** + * @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 + **/ + 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 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 + **/ + bool addDownload(QNetworkReply *reply, const QStringList &URLs, const QString &fileName, int modID, int fileID = 0, const MOBase::ModRepositoryFileInfo *fileInfo = new MOBase::ModRepositoryFileInfo()); + + /** + * @brief start a download using a nxm-link + * + * 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 + **/ + void addNXMDownload(const QString &url); + + /** + * @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) + * @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()[) + * @return pair of modid, fileid + */ + std::pair getPendingDownload(int index); + + /** + * @brief retrieve the full path to the download specified by index + * + * @param index the index to look up + * @return absolute path of the file + **/ + QString getFilePath(int index) const; + + /** + * @brief retrieve a descriptive name of the download specified by index + * + * @param index index of the file to look up + * @return display name of the file + **/ + QString getDisplayName(int index) const; + + /** + * @brief retrieve the filename of the download specified by index + * + * @param index index of the file to look up + * @return name of the file + **/ + QString getFileName(int index) const; + + /** + * @brief retrieve the file size of the download specified by index + * + * @param index index of the file to look up + * @return size of the file (total size during download) + */ + qint64 getFileSize(int index) const; + + /** + * @brief retrieve the creation time of the download specified by index + * @param index index of the file to look up + * @return size of the file (total size during download) + */ + QDateTime getFileTime(int index) const; + + /** + * @brief retrieve the current progress of the download specified by index + * + * @param index index of the file to look up + * @return progress of the download in percent (integer) + **/ + int getProgress(int index) const; + + /** + * @brief retrieve the current state of the download + * + * retrieve the current state of the download. A download usually goes through + * the following states: + * started -> downloading -> fetching mod info -> fetching file info -> done + * in case of downloads started via nxm-link, file information is fetched first + * + * @param index index of the file to look up + * @return the download state + **/ + DownloadState getState(int index) const; + + /** + * @param index index of the file to look up + * @return true if the nexus information for this download is not complete + **/ + bool isInfoIncomplete(int index) const; + + /** + * @brief retrieve the nexus mod id of the download specified by index + * + * @param index index of the file to look up + * @return the nexus mod id + **/ + int getModID(int index) const; + + /** + * @brief determine if the specified file is supposed to be hidden + * @param index index of the file to look up + * @return true if the specified file is supposed to be hidden + */ + bool isHidden(int index) const; + + /** + * @brief retrieve all nexus info of the download specified by index + * + * @param index index of the file to look up + * @return the nexus mod information + **/ + const MOBase::ModRepositoryFileInfo *getFileInfo(int index) const; + + /** + * @brief mark a download as installed + * + * @param index index of the file to mark installed + */ + void markInstalled(int index); + + /** + * @brief mark a download as uninstalled + * + * @param index index of the file to mark uninstalled + */ + void markUninstalled(int index); + + /** + * @brief refreshes the list of downloads + */ + void refreshList(); + + /** + * @brief Sort function for download servers + * @param LHS + * @param RHS + * @return + */ + static bool ServerByPreference(const std::map &preferredServers, const QVariant &LHS, const QVariant &RHS); + + + virtual int startDownloadURLs(const QStringList &urls); + + virtual int startDownloadNexusFile(int modID, int fileID); + + virtual QString downloadPath(int id); + + /** + * @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; + + void pauseAll(); signals: - void aboutToUpdate(); - - /** - * @brief signals that the specified download has changed - * - * @param row the row that changed. This corresponds to the download index - **/ - void update(int row); - - /** - * @brief signals the ui that a message should be displayed - * - * @param message the message to display - **/ - void showMessage(const QString& message); - - /** - * @brief emitted whenever the state of a download changes - * @param row the row that changed - * @param state the new state - */ - void stateChanged(int row, DownloadManager::DownloadState state); - - /** - * @brief emitted whenever a download completes successfully, reporting the download speed for the server used - */ - void downloadSpeed(const QString& serverName, int bytesPerSecond); - - /** - * @brief emitted whenever a new download is added to the list - */ - void downloadAdded(); + void aboutToUpdate(); + + /** + * @brief signals that the specified download has changed + * + * @param row the row that changed. This corresponds to the download index + **/ + void update(int row); + + /** + * @brief signals the ui that a message should be displayed + * + * @param message the message to display + **/ + void showMessage(const QString &message); + + /** + * @brief emitted whenever the state of a download changes + * @param row the row that changed + * @param state the new state + */ + void stateChanged(int row, DownloadManager::DownloadState state); + + /** + * @brief emitted whenever a download completes successfully, reporting the download speed for the server used + */ + void downloadSpeed(const QString &serverName, int bytesPerSecond); + + /** + * @brief emitted whenever a new download is added to the list + */ + void downloadAdded(); 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. - **/ - void removeDownload(int index, bool deleteFile); + /** + * @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. + **/ + void removeDownload(int index, bool deleteFile); - /** - * @brief restores the specified download to view (which was previously hidden - * @param index index of the download to restore - */ - void restoreDownload(int index); + /** + * @brief restores the specified download to view (which was previously hidden + * @param index index of the download to restore + */ + void restoreDownload(int index); - /** - * @brief cancel the specified download. This will lead to the corresponding file to be deleted - * - * @param index index of the download to cancel - **/ - void cancelDownload(int index); + /** + * @brief cancel the specified download. This will lead to the corresponding file to be deleted + * + * @param index index of the download to cancel + **/ + void cancelDownload(int index); - void pauseDownload(int index); + void pauseDownload(int index); - void resumeDownload(int index); + void resumeDownload(int index); - void queryInfo(int index); + void queryInfo(int index); - void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString& errorString); + void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorString); - void managedGameChanged(MOBase::IPluginGame const* gamePlugin); + void managedGameChanged(MOBase::IPluginGame const *gamePlugin); private slots: - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void downloadReadyRead(); - void downloadFinished(); - void downloadError(QNetworkReply::NetworkError error); - void metaDataChanged(); - void directoryChanged(const QString& dirctory); + void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); + void downloadReadyRead(); + void downloadFinished(); + void downloadError(QNetworkReply::NetworkError error); + void metaDataChanged(); + void directoryChanged(const QString &dirctory); private: - void createMetaFile(DownloadInfo* info); + + void createMetaFile(DownloadInfo *info); public: - /** Get a unique filename for a download. - * - * This allows you multiple versions of download files, useful if the file - * comes from a web site with no version control - * - * @param basename: Name of the file - * - * @return Unique(ish) name - */ - QString getDownloadFileName(const QString& baseName) const; + + /** Get a unique filename for a download. + * + * This allows you multiple versions of download files, useful if the file + * comes from a web site with no version control + * + * @param basename: Name of the file + * + * @return Unique(ish) name + */ + QString getDownloadFileName(const QString &baseName) const; private: - void startDownload(QNetworkReply* reply, DownloadInfo* newDownload, bool resume); - void resumeDownloadInt(int index); - /** - * @brief start a download from a url - * - * @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 - **/ - bool addDownload(const QStringList& URLs, int modID, int fileID, const MOBase::ModRepositoryFileInfo* fileInfo); + void startDownload(QNetworkReply *reply, DownloadInfo *newDownload, bool resume); + void resumeDownloadInt(int index); + + /** + * @brief start a download from a url + * + * @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 + **/ + bool addDownload(const QStringList &URLs, 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); + void removeFile(int index, bool deleteFile); - void refreshAlphabeticalTranslation(); + void refreshAlphabeticalTranslation(); - bool ByName(int LHS, int RHS); + 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); - QDateTime matchDate(const QString& timeString); + QDateTime matchDate(const QString &timeString); - void removePending(int modID, int fileID); + void removePending(int modID, int fileID); - static QString getFileTypeString(int fileType); + static QString getFileTypeString(int fileType); private: - static const int AUTOMATIC_RETRIES = 3; + + static const int AUTOMATIC_RETRIES = 3; private: - NexusInterface* m_NexusInterface; - QVector> m_PendingDownloads; + NexusInterface *m_NexusInterface; + + QVector > m_PendingDownloads; - QVector m_ActiveDownloads; + QVector m_ActiveDownloads; - QString m_OutputDirectory; - std::map m_PreferredServers; - QStringList m_SupportedExtensions; - std::set m_RequestIDs; - QVector m_AlphabeticalTranslation; + QString m_OutputDirectory; + std::map m_PreferredServers; + QStringList m_SupportedExtensions; + std::set m_RequestIDs; + QVector m_AlphabeticalTranslation; - QFileSystemWatcher m_DirWatcher; + QFileSystemWatcher m_DirWatcher; - std::map m_DownloadFails; + std::map m_DownloadFails; - bool m_ShowHidden; + bool m_ShowHidden; - QRegExp m_DateExpression; + QRegExp m_DateExpression; - MOBase::IPluginGame const* m_ManagedGame; + MOBase::IPluginGame const *m_ManagedGame; }; + + #endif // DOWNLOADMANAGER_H diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 1c01545a..fde6b397 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -18,282 +18,323 @@ along with Mod Organizer. If not, see . */ #include "editexecutablesdialog.h" +#include "ui_editexecutablesdialog.h" #include "filedialogmemory.h" -#include "modlist.h" #include "stackdata.h" -#include "ui_editexecutablesdialog.h" +#include "modlist.h" #include #include #include + using namespace MOBase; using namespace MOShared; -EditExecutablesDialog::EditExecutablesDialog(const ExecutablesList& executablesList, const ModList& modList, - Profile* profile, QWidget* parent) - : TutorableDialog("EditExecutables", parent), ui(new Ui::EditExecutablesDialog), m_CurrentItem(nullptr), - m_ExecutablesList(executablesList), m_Profile(profile) { - ui->setupUi(this); +EditExecutablesDialog::EditExecutablesDialog( + const ExecutablesList &executablesList, const ModList &modList, + Profile *profile, QWidget *parent) + : TutorableDialog("EditExecutables", parent) + , ui(new Ui::EditExecutablesDialog) + , m_CurrentItem(nullptr) + , m_ExecutablesList(executablesList) + , m_Profile(profile) +{ + ui->setupUi(this); - refreshExecutablesWidget(); + refreshExecutablesWidget(); - ui->newFilesModBox->addItems(modList.allMods()); + ui->newFilesModBox->addItems(modList.allMods()); } -EditExecutablesDialog::~EditExecutablesDialog() { delete ui; } +EditExecutablesDialog::~EditExecutablesDialog() +{ + delete ui; +} -ExecutablesList EditExecutablesDialog::getExecutablesList() const { - ExecutablesList newList; - for (int i = 0; i < ui->executablesListBox->count(); ++i) { - newList.addExecutable(m_ExecutablesList.find(ui->executablesListBox->item(i)->text())); - } - return newList; +ExecutablesList EditExecutablesDialog::getExecutablesList() const +{ + ExecutablesList newList; + for (int i = 0; i < ui->executablesListBox->count(); ++i) { + newList.addExecutable(m_ExecutablesList.find(ui->executablesListBox->item(i)->text())); + } + return newList; } -void EditExecutablesDialog::refreshExecutablesWidget() { - ui->executablesListBox->clear(); - std::vector::const_iterator current, end; - m_ExecutablesList.getExecutables(current, end); +void EditExecutablesDialog::refreshExecutablesWidget() +{ + ui->executablesListBox->clear(); + std::vector::const_iterator current, end; + m_ExecutablesList.getExecutables(current, end); - for (; current != end; ++current) { - QListWidgetItem* newItem = new QListWidgetItem(current->m_Title); - newItem->setTextColor(current->isCustom() ? QColor(Qt::black) : QColor(Qt::darkGray)); - ui->executablesListBox->addItem(newItem); - } + for(; current != end; ++current) { + QListWidgetItem *newItem = new QListWidgetItem(current->m_Title); + newItem->setTextColor(current->isCustom() ? QColor(Qt::black) : QColor(Qt::darkGray)); + ui->executablesListBox->addItem(newItem); + } - ui->addButton->setEnabled(false); - ui->removeButton->setEnabled(false); + ui->addButton->setEnabled(false); + ui->removeButton->setEnabled(false); } -void EditExecutablesDialog::on_binaryEdit_textChanged(const QString& name) { - QFileInfo fileInfo(name); - ui->addButton->setEnabled(fileInfo.exists() && fileInfo.isFile()); + +void EditExecutablesDialog::on_binaryEdit_textChanged(const QString &name) +{ + QFileInfo fileInfo(name); + ui->addButton->setEnabled(fileInfo.exists() && fileInfo.isFile()); } -void EditExecutablesDialog::resetInput() { - ui->binaryEdit->setText(""); - ui->titleEdit->setText(""); - ui->workingDirEdit->clear(); - ui->argumentsEdit->setText(""); - ui->appIDOverwriteEdit->clear(); - ui->overwriteAppIDBox->setChecked(false); - ui->useAppIconCheckBox->setChecked(false); - ui->newFilesModCheckBox->setChecked(false); - m_CurrentItem = nullptr; +void EditExecutablesDialog::resetInput() +{ + ui->binaryEdit->setText(""); + ui->titleEdit->setText(""); + ui->workingDirEdit->clear(); + ui->argumentsEdit->setText(""); + ui->appIDOverwriteEdit->clear(); + ui->overwriteAppIDBox->setChecked(false); + ui->useAppIconCheckBox->setChecked(false); + ui->newFilesModCheckBox->setChecked(false); + m_CurrentItem = nullptr; } -void EditExecutablesDialog::saveExecutable() { - m_ExecutablesList.updateExecutable( - ui->titleEdit->text(), QDir::fromNativeSeparators(ui->binaryEdit->text()), ui->argumentsEdit->text(), + +void EditExecutablesDialog::saveExecutable() +{ + m_ExecutablesList.updateExecutable( + ui->titleEdit->text(), + QDir::fromNativeSeparators(ui->binaryEdit->text()), + ui->argumentsEdit->text(), QDir::fromNativeSeparators(ui->workingDirEdit->text()), - ui->overwriteAppIDBox->isChecked() ? ui->appIDOverwriteEdit->text() : "", + ui->overwriteAppIDBox->isChecked() ? + ui->appIDOverwriteEdit->text() : "", Executable::UseApplicationIcon | Executable::CustomExecutable, - (ui->useAppIconCheckBox->isChecked() ? Executable::UseApplicationIcon : Executable::Flags()) | - Executable::CustomExecutable); - - if (ui->newFilesModCheckBox->isChecked()) { - m_Profile->storeSetting("custom_overwrites", ui->titleEdit->text(), ui->newFilesModBox->currentText()); - } else { - m_Profile->removeSetting("custom_overwrites", ui->titleEdit->text()); - } + (ui->useAppIconCheckBox->isChecked() ? + Executable::UseApplicationIcon : Executable::Flags()) + | Executable::CustomExecutable); + + if (ui->newFilesModCheckBox->isChecked()) { + m_Profile->storeSetting("custom_overwrites", ui->titleEdit->text(), + ui->newFilesModBox->currentText()); + } + else { + m_Profile->removeSetting("custom_overwrites", ui->titleEdit->text()); + } } -void EditExecutablesDialog::delayedRefresh() { - QModelIndex index = ui->executablesListBox->currentIndex(); - resetInput(); - refreshExecutablesWidget(); - on_executablesListBox_clicked(index); + +void EditExecutablesDialog::delayedRefresh() +{ + QModelIndex index = ui->executablesListBox->currentIndex(); + resetInput(); + refreshExecutablesWidget(); + on_executablesListBox_clicked(index); } -void EditExecutablesDialog::on_addButton_clicked() { - if (executableChanged()) { - saveExecutable(); - } - resetInput(); - refreshExecutablesWidget(); +void EditExecutablesDialog::on_addButton_clicked() +{ + if (executableChanged()) { + saveExecutable(); + } + + resetInput(); + refreshExecutablesWidget(); } -void EditExecutablesDialog::on_browseButton_clicked() { - QString binaryName = FileDialogMemory::getOpenFileName("editExecutableBinary", this, tr("Select a binary"), - QString(), tr("Executable (%1)").arg("*.exe *.bat *.jar")); - - if (binaryName.endsWith(".jar", Qt::CaseInsensitive)) { - QString binaryPath; - { // try to find java automatically - std::wstring binaryNameW = ToWString(binaryName); - WCHAR buffer[MAX_PATH]; - if (::FindExecutableW(binaryNameW.c_str(), nullptr, buffer) > reinterpret_cast(32)) { - DWORD binaryType = 0UL; - if (!::GetBinaryTypeW(binaryNameW.c_str(), &binaryType)) { - qDebug("failed to determine binary type of \"%ls\": %lu", binaryNameW.c_str(), ::GetLastError()); - } else if (binaryType == SCS_32BIT_BINARY) { - binaryPath = ToQString(buffer); - } - } +void EditExecutablesDialog::on_browseButton_clicked() +{ + QString binaryName = FileDialogMemory::getOpenFileName( + "editExecutableBinary", this, tr("Select a binary"), QString(), + tr("Executable (%1)").arg("*.exe *.bat *.jar")); + + if (binaryName.endsWith(".jar", Qt::CaseInsensitive)) { + QString binaryPath; + { // try to find java automatically + std::wstring binaryNameW = ToWString(binaryName); + WCHAR buffer[MAX_PATH]; + if (::FindExecutableW(binaryNameW.c_str(), nullptr, buffer) + > reinterpret_cast(32)) { + DWORD binaryType = 0UL; + if (!::GetBinaryTypeW(binaryNameW.c_str(), &binaryType)) { + qDebug("failed to determine binary type of \"%ls\": %lu", binaryNameW.c_str(), ::GetLastError()); + } else if (binaryType == SCS_32BIT_BINARY) { + binaryPath = ToQString(buffer); } - if (binaryPath.isEmpty()) { - QSettings javaReg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", - QSettings::NativeFormat); - if (javaReg.contains("CurrentVersion")) { - QString currentVersion = javaReg.value("CurrentVersion").toString(); - binaryPath = - javaReg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe"); - } - } - if (binaryPath.isEmpty()) { - QMessageBox::information(this, tr("Java (32-bit) required"), - tr("MO requires 32-bit java to run this application. If you already have it " - "installed, select javaw.exe " - "from that installation as the binary.")); - } else { - ui->binaryEdit->setText(binaryPath); - } - - ui->workingDirEdit->setText(QDir::toNativeSeparators(QFileInfo(binaryName).absolutePath())); - ui->argumentsEdit->setText("-jar \"" + QDir::toNativeSeparators(binaryName) + "\""); + } + } + if (binaryPath.isEmpty()) { + QSettings javaReg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat); + if (javaReg.contains("CurrentVersion")) { + QString currentVersion = javaReg.value("CurrentVersion").toString(); + binaryPath = javaReg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe"); + } + } + if (binaryPath.isEmpty()) { + QMessageBox::information(this, tr("Java (32-bit) required"), + tr("MO requires 32-bit java to run this application. If you already have it installed, select javaw.exe " + "from that installation as the binary.")); } else { - ui->binaryEdit->setText(QDir::toNativeSeparators(binaryName)); + ui->binaryEdit->setText(binaryPath); } + + ui->workingDirEdit->setText(QDir::toNativeSeparators(QFileInfo(binaryName).absolutePath())); + ui->argumentsEdit->setText("-jar \"" + QDir::toNativeSeparators(binaryName) + "\""); + } else { + ui->binaryEdit->setText(QDir::toNativeSeparators(binaryName)); + } } -void EditExecutablesDialog::on_browseDirButton_clicked() { - QString dirName = FileDialogMemory::getExistingDirectory("editExecutableDirectory", this, tr("Select a directory")); +void EditExecutablesDialog::on_browseDirButton_clicked() +{ + QString dirName = FileDialogMemory::getExistingDirectory("editExecutableDirectory", this, + tr("Select a directory")); - ui->workingDirEdit->setText(dirName); + ui->workingDirEdit->setText(dirName); } -void EditExecutablesDialog::on_removeButton_clicked() { - if (QMessageBox::question(this, tr("Confirm"), - tr("Really remove \"%1\" from executables?").arg(ui->titleEdit->text()), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_ExecutablesList.remove(ui->titleEdit->text()); - } +void EditExecutablesDialog::on_removeButton_clicked() +{ + if (QMessageBox::question(this, tr("Confirm"), tr("Really remove \"%1\" from executables?").arg(ui->titleEdit->text()), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_ExecutablesList.remove(ui->titleEdit->text()); + } - resetInput(); - refreshExecutablesWidget(); + resetInput(); + refreshExecutablesWidget(); } -void EditExecutablesDialog::on_titleEdit_textChanged(const QString& arg1) { - QPushButton* addButton = findChild("addButton"); - QPushButton* removeButton = findChild("removeButton"); +void EditExecutablesDialog::on_titleEdit_textChanged(const QString &arg1) +{ + QPushButton *addButton = findChild("addButton"); + QPushButton *removeButton = findChild("removeButton"); - QListWidget* executablesWidget = findChild("executablesListBox"); + QListWidget *executablesWidget = findChild("executablesListBox"); - QList existingItems = executablesWidget->findItems(arg1, Qt::MatchFixedString); + QList existingItems = executablesWidget->findItems(arg1, Qt::MatchFixedString); - addButton->setEnabled(arg1.length() != 0); + addButton->setEnabled(arg1.length() != 0); - if (existingItems.count() == 0) { - addButton->setText(tr("Add")); - removeButton->setEnabled(false); - } else { - // existing item. is it a custom one? - addButton->setText(tr("Modify")); - removeButton->setEnabled(true); - } + if (existingItems.count() == 0) { + addButton->setText(tr("Add")); + removeButton->setEnabled(false); + } else { + // existing item. is it a custom one? + addButton->setText(tr("Modify")); + removeButton->setEnabled(true); + } } -bool EditExecutablesDialog::executableChanged() { - if (m_CurrentItem != nullptr) { - Executable const& selectedExecutable(m_ExecutablesList.find(m_CurrentItem->text())); - - QString storedCustomOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.m_Title).toString(); - - return selectedExecutable.m_Title != ui->titleEdit->text() || - selectedExecutable.m_Arguments != ui->argumentsEdit->text() || - selectedExecutable.m_SteamAppID != ui->appIDOverwriteEdit->text() || - !storedCustomOverwrite.isEmpty() != ui->newFilesModCheckBox->isChecked() || - !storedCustomOverwrite.isEmpty() && (storedCustomOverwrite != ui->newFilesModBox->currentText()) || - selectedExecutable.m_WorkingDirectory != QDir::fromNativeSeparators(ui->workingDirEdit->text()) || - selectedExecutable.m_BinaryInfo.absoluteFilePath() != - QDir::fromNativeSeparators(ui->binaryEdit->text()) || - selectedExecutable.usesOwnIcon() != ui->useAppIconCheckBox->isChecked(); - } else { - QFileInfo fileInfo(ui->binaryEdit->text()); - return !ui->binaryEdit->text().isEmpty() && !ui->titleEdit->text().isEmpty() && fileInfo.exists() && - fileInfo.isFile(); - } + +bool EditExecutablesDialog::executableChanged() +{ + if (m_CurrentItem != nullptr) { + Executable const &selectedExecutable(m_ExecutablesList.find(m_CurrentItem->text())); + + QString storedCustomOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.m_Title).toString(); + + return selectedExecutable.m_Title != ui->titleEdit->text() + || selectedExecutable.m_Arguments != ui->argumentsEdit->text() + || selectedExecutable.m_SteamAppID != ui->appIDOverwriteEdit->text() + || !storedCustomOverwrite.isEmpty() != ui->newFilesModCheckBox->isChecked() + || !storedCustomOverwrite.isEmpty() && (storedCustomOverwrite != ui->newFilesModBox->currentText()) + || selectedExecutable.m_WorkingDirectory != QDir::fromNativeSeparators(ui->workingDirEdit->text()) + || selectedExecutable.m_BinaryInfo.absoluteFilePath() != QDir::fromNativeSeparators(ui->binaryEdit->text()) + || selectedExecutable.usesOwnIcon() != ui->useAppIconCheckBox->isChecked(); + } else { + QFileInfo fileInfo(ui->binaryEdit->text()); + return !ui->binaryEdit->text().isEmpty() + && !ui->titleEdit->text().isEmpty() + && fileInfo.exists() + && fileInfo.isFile(); + } } -void EditExecutablesDialog::on_executablesListBox_itemSelectionChanged() { - if (ui->executablesListBox->selectedItems().size() == 0) { - // deselected - resetInput(); - } +void EditExecutablesDialog::on_executablesListBox_itemSelectionChanged() +{ + if (ui->executablesListBox->selectedItems().size() == 0) { + // deselected + resetInput(); + } } -void EditExecutablesDialog::on_overwriteAppIDBox_toggled(bool checked) { ui->appIDOverwriteEdit->setEnabled(checked); } +void EditExecutablesDialog::on_overwriteAppIDBox_toggled(bool checked) +{ + ui->appIDOverwriteEdit->setEnabled(checked); +} -void EditExecutablesDialog::on_closeButton_clicked() { - if (executableChanged()) { - QMessageBox::StandardButton res = QMessageBox::question( - this, tr("Save Changes?"), tr("You made changes to the current executable, do you want to save them?"), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); - if (res == QMessageBox::Cancel) { - return; - } else if (res == QMessageBox::Yes) { - saveExecutable(); - // the executable list returned to callers is generated from the user data in the widgets, - // NOT the list we just saved - refreshExecutablesWidget(); - } +void EditExecutablesDialog::on_closeButton_clicked() +{ + if (executableChanged()) { + QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"), + tr("You made changes to the current executable, do you want to save them?"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (res == QMessageBox::Cancel) { + return; + } else if (res == QMessageBox::Yes) { + saveExecutable(); + // the executable list returned to callers is generated from the user data in the widgets, + // NOT the list we just saved + refreshExecutablesWidget(); } - this->accept(); + } + this->accept(); } -void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex& current) { - if (current.isValid()) { - - if (executableChanged()) { - QMessageBox::StandardButton res = QMessageBox::question( - this, tr("Save Changes?"), tr("You made changes to the current executable, do you want to save them?"), - QMessageBox::Yes | QMessageBox::No); - if (res == QMessageBox::Yes) { - saveExecutable(); - - // This is necessary if we're adding a new item, but it doesn't look very nice. - // Ideally we'd end up with the correct row displayed - ui->executablesListBox->selectionModel()->clearSelection(); - ui->executablesListBox->selectionModel()->select(current, QItemSelectionModel::SelectCurrent); - QTimer::singleShot(50, this, SLOT(delayedRefresh())); - return; - } - } +void EditExecutablesDialog::on_executablesListBox_clicked(const QModelIndex ¤t) +{ + if (current.isValid()) { + if (executableChanged()) { + QMessageBox::StandardButton res = QMessageBox::question(this, tr("Save Changes?"), + tr("You made changes to the current executable, do you want to save them?"), + QMessageBox::Yes | QMessageBox::No); + if (res == QMessageBox::Yes) { + saveExecutable(); + + //This is necessary if we're adding a new item, but it doesn't look very nice. + //Ideally we'd end up with the correct row displayed ui->executablesListBox->selectionModel()->clearSelection(); ui->executablesListBox->selectionModel()->select(current, QItemSelectionModel::SelectCurrent); + QTimer::singleShot(50, this, SLOT(delayedRefresh())); + return; + } + } - m_CurrentItem = ui->executablesListBox->item(current.row()); + ui->executablesListBox->selectionModel()->clearSelection(); + ui->executablesListBox->selectionModel()->select(current, QItemSelectionModel::SelectCurrent); - Executable const& selectedExecutable(m_ExecutablesList.find(m_CurrentItem->text())); + m_CurrentItem = ui->executablesListBox->item(current.row()); - ui->titleEdit->setText(selectedExecutable.m_Title); - ui->binaryEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath())); - ui->argumentsEdit->setText(selectedExecutable.m_Arguments); - ui->workingDirEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_WorkingDirectory)); - ui->removeButton->setEnabled(selectedExecutable.isCustom()); - ui->overwriteAppIDBox->setChecked(!selectedExecutable.m_SteamAppID.isEmpty()); - if (!selectedExecutable.m_SteamAppID.isEmpty()) { - ui->appIDOverwriteEdit->setText(selectedExecutable.m_SteamAppID); - } else { - ui->appIDOverwriteEdit->clear(); - } - ui->useAppIconCheckBox->setChecked(selectedExecutable.usesOwnIcon()); + Executable const &selectedExecutable(m_ExecutablesList.find(m_CurrentItem->text())); + + ui->titleEdit->setText(selectedExecutable.m_Title); + ui->binaryEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath())); + ui->argumentsEdit->setText(selectedExecutable.m_Arguments); + ui->workingDirEdit->setText(QDir::toNativeSeparators(selectedExecutable.m_WorkingDirectory)); + ui->removeButton->setEnabled(selectedExecutable.isCustom()); + ui->overwriteAppIDBox->setChecked(!selectedExecutable.m_SteamAppID.isEmpty()); + if (!selectedExecutable.m_SteamAppID.isEmpty()) { + ui->appIDOverwriteEdit->setText(selectedExecutable.m_SteamAppID); + } else { + ui->appIDOverwriteEdit->clear(); + } + ui->useAppIconCheckBox->setChecked(selectedExecutable.usesOwnIcon()); - int index = -1; + int index = -1; - QString customOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.m_Title).toString(); - if (!customOverwrite.isEmpty()) { - index = ui->newFilesModBox->findText(customOverwrite); - qDebug("find %s -> %d", qPrintable(customOverwrite), index); - } + QString customOverwrite = m_Profile->setting("custom_overwrites", selectedExecutable.m_Title).toString(); + if (!customOverwrite.isEmpty()) { + index = ui->newFilesModBox->findText(customOverwrite); + qDebug("find %s -> %d", qPrintable(customOverwrite), index); + } - ui->newFilesModCheckBox->setChecked(index != -1); - if (index != -1) { - ui->newFilesModBox->setCurrentIndex(index); - } + ui->newFilesModCheckBox->setChecked(index != -1); + if (index != -1) { + ui->newFilesModBox->setCurrentIndex(index); } + } } -void EditExecutablesDialog::on_newFilesModCheckBox_toggled(bool checked) { ui->newFilesModBox->setEnabled(checked); } +void EditExecutablesDialog::on_newFilesModCheckBox_toggled(bool checked) +{ + ui->newFilesModBox->setEnabled(checked); +} diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index 8241ba51..0f3dbaff 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -20,87 +20,94 @@ along with Mod Organizer. If not, see . #ifndef EDITEXECUTABLESDIALOG_H #define EDITEXECUTABLESDIALOG_H -#include "executableslist.h" -#include "profile.h" #include "tutorabledialog.h" #include #include +#include "executableslist.h" +#include "profile.h" namespace Ui { -class EditExecutablesDialog; + class EditExecutablesDialog; } + class ModList; + /** * @brief Dialog to manage the list of executables **/ -class EditExecutablesDialog : public MOBase::TutorableDialog { +class EditExecutablesDialog : public MOBase::TutorableDialog +{ Q_OBJECT public: - /** - * @brief constructor - * - * @param executablesList current list of executables - * @param parent parent widget - **/ - explicit EditExecutablesDialog(const ExecutablesList& executablesList, const ModList& modList, Profile* profile, - QWidget* parent = 0); - - ~EditExecutablesDialog(); - - /** - * @brief retrieve the updated list of executables - * - * @return updated list of executables - **/ - ExecutablesList getExecutablesList() const; - - void saveExecutable(); + + /** + * @brief constructor + * + * @param executablesList current list of executables + * @param parent parent widget + **/ + explicit EditExecutablesDialog(const ExecutablesList &executablesList, + const ModList &modList, + Profile *profile, + QWidget *parent = 0); + + ~EditExecutablesDialog(); + + /** + * @brief retrieve the updated list of executables + * + * @return updated list of executables + **/ + ExecutablesList getExecutablesList() const; + + void saveExecutable(); private slots: - void on_newFilesModCheckBox_toggled(bool checked); + void on_newFilesModCheckBox_toggled(bool checked); private slots: - void on_binaryEdit_textChanged(const QString& arg1); + void on_binaryEdit_textChanged(const QString &arg1); - void on_addButton_clicked(); + void on_addButton_clicked(); - void on_browseButton_clicked(); + void on_browseButton_clicked(); - void on_removeButton_clicked(); + void on_removeButton_clicked(); - void on_titleEdit_textChanged(const QString& arg1); + void on_titleEdit_textChanged(const QString &arg1); - void on_overwriteAppIDBox_toggled(bool checked); + void on_overwriteAppIDBox_toggled(bool checked); - void on_browseDirButton_clicked(); + void on_browseDirButton_clicked(); - void on_closeButton_clicked(); + void on_closeButton_clicked(); - void delayedRefresh(); + void delayedRefresh(); - void on_executablesListBox_itemSelectionChanged(); + void on_executablesListBox_itemSelectionChanged(); - void on_executablesListBox_clicked(const QModelIndex& index); + void on_executablesListBox_clicked(const QModelIndex &index); private: - void resetInput(); - void refreshExecutablesWidget(); + void resetInput(); + + void refreshExecutablesWidget(); - bool executableChanged(); + bool executableChanged(); private: - Ui::EditExecutablesDialog* ui; + Ui::EditExecutablesDialog *ui; - QListWidgetItem* m_CurrentItem; + QListWidgetItem *m_CurrentItem; - ExecutablesList m_ExecutablesList; + ExecutablesList m_ExecutablesList; - Profile* m_Profile; + Profile *m_Profile; }; #endif // EDITEXECUTABLESDIALOG_H diff --git a/src/eventfilter.cpp b/src/eventfilter.cpp index e3195249..69327f79 100644 --- a/src/eventfilter.cpp +++ b/src/eventfilter.cpp @@ -17,9 +17,17 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ + #include "eventfilter.h" -EventFilter::EventFilter(QObject* parent, const EventFilter::HandlerFunc& handler) - : QObject(parent), m_Handler(handler) {} +EventFilter::EventFilter(QObject *parent, + const EventFilter::HandlerFunc &handler) + : QObject(parent) + , m_Handler(handler) +{ +} -bool EventFilter::eventFilter(QObject* obj, QEvent* event) { return m_Handler(obj, event); } +bool EventFilter::eventFilter(QObject *obj, QEvent *event) +{ + return m_Handler(obj, event); +} diff --git a/src/eventfilter.h b/src/eventfilter.h index f2f406db..2400a853 100644 --- a/src/eventfilter.h +++ b/src/eventfilter.h @@ -19,20 +19,26 @@ along with Mod Organizer. If not, see . #pragma once + #include #include + class EventFilter : public QObject { - Q_OBJECT + Q_OBJECT - typedef std::function HandlerFunc; + typedef std::function HandlerFunc; public: - EventFilter(QObject* parent, const HandlerFunc& handler); - virtual bool eventFilter(QObject* obj, QEvent* event) override; + EventFilter(QObject *parent, const HandlerFunc &handler); + + virtual bool eventFilter(QObject *obj , QEvent *event) override; private: - HandlerFunc m_Handler; + + HandlerFunc m_Handler; + }; + diff --git a/src/executableslist.cpp b/src/executableslist.cpp index cde487be..21cb6ed4 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -22,152 +22,187 @@ along with Mod Organizer. If not, see . #include "iplugingame.h" #include "utility.h" -#include -#include #include +#include +#include #include + using namespace MOBase; -ExecutablesList::ExecutablesList() {} -ExecutablesList::~ExecutablesList() {} +ExecutablesList::ExecutablesList() +{ +} + +ExecutablesList::~ExecutablesList() +{ +} -void ExecutablesList::init(IPluginGame const* game) { - Q_ASSERT(game != nullptr); - m_Executables.clear(); - for (const ExecutableInfo& info : game->executables()) { - if (info.isValid()) { - addExecutableInternal(info.title(), info.binary().absoluteFilePath(), info.arguments().join(" "), - info.workingDirectory().absolutePath(), info.steamAppID()); - } +void ExecutablesList::init(IPluginGame const *game) +{ + Q_ASSERT(game != nullptr); + m_Executables.clear(); + for (const ExecutableInfo &info : game->executables()) { + if (info.isValid()) { + addExecutableInternal(info.title(), + info.binary().absoluteFilePath(), + info.arguments().join(" "), + info.workingDirectory().absolutePath(), + info.steamAppID()); } + } } -void ExecutablesList::getExecutables(std::vector::iterator& begin, std::vector::iterator& end) { - begin = m_Executables.begin(); - end = m_Executables.end(); +void ExecutablesList::getExecutables(std::vector::iterator &begin, std::vector::iterator &end) +{ + begin = m_Executables.begin(); + end = m_Executables.end(); } -void ExecutablesList::getExecutables(std::vector::const_iterator& begin, - std::vector::const_iterator& end) const { - begin = m_Executables.begin(); - end = m_Executables.end(); +void ExecutablesList::getExecutables(std::vector::const_iterator &begin, + std::vector::const_iterator &end) const +{ + begin = m_Executables.begin(); + end = m_Executables.end(); } -const Executable& ExecutablesList::find(const QString& title) const { - for (Executable const& exe : m_Executables) { - if (exe.m_Title == title) { - return exe; - } +const Executable &ExecutablesList::find(const QString &title) const +{ + for (Executable const &exe : m_Executables) { + if (exe.m_Title == title) { + return exe; } - throw std::runtime_error(QString("invalid name %1").arg(title).toLocal8Bit().constData()); + } + throw std::runtime_error(QString("invalid name %1").arg(title).toLocal8Bit().constData()); } -Executable& ExecutablesList::find(const QString& title) { - for (Executable& exe : m_Executables) { - if (exe.m_Title == title) { - return exe; - } + +Executable &ExecutablesList::find(const QString &title) +{ + for (Executable &exe : m_Executables) { + if (exe.m_Title == title) { + return exe; } - throw std::runtime_error(QString("invalid executable name %1").arg(title).toLocal8Bit().constData()); + } + throw std::runtime_error(QString("invalid executable name %1").arg(title).toLocal8Bit().constData()); } -Executable& ExecutablesList::findByBinary(const QFileInfo& info) { - for (Executable& exe : m_Executables) { - if (info == exe.m_BinaryInfo) { - return exe; - } + +Executable &ExecutablesList::findByBinary(const QFileInfo &info) +{ + for (Executable &exe : m_Executables) { + if (info == exe.m_BinaryInfo) { + return exe; } - throw std::runtime_error("invalid info"); + } + throw std::runtime_error("invalid info"); } -std::vector::iterator ExecutablesList::findExe(const QString& title) { - for (std::vector::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { - if (iter->m_Title == title) { - return iter; - } + +std::vector::iterator ExecutablesList::findExe(const QString &title) +{ + for (std::vector::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { + if (iter->m_Title == title) { + return iter; } - return m_Executables.end(); + } + return m_Executables.end(); } -bool ExecutablesList::titleExists(const QString& title) const { - auto test = [&](const Executable& exe) { return exe.m_Title == title; }; - return std::find_if(m_Executables.begin(), m_Executables.end(), test) != m_Executables.end(); + +bool ExecutablesList::titleExists(const QString &title) const +{ + auto test = [&] (const Executable &exe) { return exe.m_Title == title; }; + return std::find_if(m_Executables.begin(), m_Executables.end(), test) != m_Executables.end(); } -void ExecutablesList::addExecutable(const Executable& executable) { - auto existingExe = findExe(executable.m_Title); - if (existingExe != m_Executables.end()) { - *existingExe = executable; - } else { - m_Executables.push_back(executable); - } + +void ExecutablesList::addExecutable(const Executable &executable) +{ + auto existingExe = findExe(executable.m_Title); + if (existingExe != m_Executables.end()) { + *existingExe = executable; + } else { + m_Executables.push_back(executable); + } } -void ExecutablesList::updateExecutable(const QString& title, const QString& executableName, const QString& arguments, - const QString& workingDirectory, const QString& steamAppID, - Executable::Flags mask, Executable::Flags flags) { - QFileInfo file(executableName); - auto existingExe = findExe(title); - flags &= mask; - - if (existingExe != m_Executables.end()) { - existingExe->m_Title = title; - existingExe->m_Flags &= ~mask; - existingExe->m_Flags |= flags; - // for pre-configured executables don't overwrite settings we didn't store - if (flags & Executable::CustomExecutable) { - if (file.exists()) { - // don't overwrite a valid binary with an invalid one - existingExe->m_BinaryInfo = file; - } - existingExe->m_Arguments = arguments; - existingExe->m_WorkingDirectory = workingDirectory; - existingExe->m_SteamAppID = steamAppID; - } - } else { - Executable newExe; - newExe.m_Title = title; - newExe.m_BinaryInfo = file; - newExe.m_Arguments = arguments; - newExe.m_WorkingDirectory = workingDirectory; - newExe.m_SteamAppID = steamAppID; - newExe.m_Flags = Executable::CustomExecutable | flags; - m_Executables.push_back(newExe); + +void ExecutablesList::updateExecutable(const QString &title, + const QString &executableName, + const QString &arguments, + const QString &workingDirectory, + const QString &steamAppID, + Executable::Flags mask, + Executable::Flags flags) +{ + QFileInfo file(executableName); + auto existingExe = findExe(title); + flags &= mask; + + if (existingExe != m_Executables.end()) { + existingExe->m_Title = title; + existingExe->m_Flags &= ~mask; + existingExe->m_Flags |= flags; + // for pre-configured executables don't overwrite settings we didn't store + if (flags & Executable::CustomExecutable) { + if (file.exists()) { + // don't overwrite a valid binary with an invalid one + existingExe->m_BinaryInfo = file; + } + existingExe->m_Arguments = arguments; + existingExe->m_WorkingDirectory = workingDirectory; + existingExe->m_SteamAppID = steamAppID; } + } else { + Executable newExe; + newExe.m_Title = title; + newExe.m_BinaryInfo = file; + newExe.m_Arguments = arguments; + newExe.m_WorkingDirectory = workingDirectory; + newExe.m_SteamAppID = steamAppID; + newExe.m_Flags = Executable::CustomExecutable | flags; + m_Executables.push_back(newExe); + } } -void ExecutablesList::remove(const QString& title) { - for (std::vector::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { - if (iter->isCustom() && (iter->m_Title == title)) { - m_Executables.erase(iter); - break; - } + +void ExecutablesList::remove(const QString &title) +{ + for (std::vector::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { + if (iter->isCustom() && (iter->m_Title == title)) { + m_Executables.erase(iter); + break; } + } } -void ExecutablesList::addExecutableInternal(const QString& title, const QString& executableName, - const QString& arguments, const QString& workingDirectory, - const QString& steamAppID) { - QFileInfo file(executableName); - if (file.exists()) { - Executable newExe; - newExe.m_BinaryInfo = file; - newExe.m_Title = title; - newExe.m_Arguments = arguments; - newExe.m_WorkingDirectory = workingDirectory; - newExe.m_SteamAppID = steamAppID; - newExe.m_Flags = Executable::UseApplicationIcon; - m_Executables.push_back(newExe); - } + +void ExecutablesList::addExecutableInternal(const QString &title, const QString &executableName, + const QString &arguments, const QString &workingDirectory, + const QString &steamAppID) +{ + QFileInfo file(executableName); + if (file.exists()) { + Executable newExe; + newExe.m_BinaryInfo = file; + newExe.m_Title = title; + newExe.m_Arguments = arguments; + newExe.m_WorkingDirectory = workingDirectory; + newExe.m_SteamAppID = steamAppID; + newExe.m_Flags = Executable::UseApplicationIcon; + m_Executables.push_back(newExe); + } } -void Executable::showOnToolbar(bool state) { - if (state) { - m_Flags |= ShowInToolbar; - } else { - m_Flags &= ~ShowInToolbar; - } + +void Executable::showOnToolbar(bool state) +{ + if (state) { + m_Flags |= ShowInToolbar; + } else { + m_Flags &= ~ShowInToolbar; + } } diff --git a/src/executableslist.h b/src/executableslist.h index cb1b1913..0534c09e 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -27,160 +27,175 @@ along with Mod Organizer. If not, see . #include #include -namespace MOBase { -class IPluginGame; -} +namespace MOBase { class IPluginGame; } /*! * @brief Information about an executable **/ struct Executable { - QString m_Title; - QFileInfo m_BinaryInfo; - QString m_Arguments; - QString m_SteamAppID; - QString m_WorkingDirectory; + QString m_Title; + QFileInfo m_BinaryInfo; + QString m_Arguments; + QString m_SteamAppID; + QString m_WorkingDirectory; - enum Flag { - CustomExecutable = 0x01, - ShowInToolbar = 0x02, - UseApplicationIcon = 0x04, + enum Flag { + CustomExecutable = 0x01, + ShowInToolbar = 0x02, + UseApplicationIcon = 0x04, - AllFlags = 0xff // I know, I know - }; + AllFlags = 0xff //I know, I know + }; - Q_DECLARE_FLAGS(Flags, Flag) + Q_DECLARE_FLAGS(Flags, Flag) - Flags m_Flags; + Flags m_Flags; - bool isCustom() const { return m_Flags.testFlag(CustomExecutable); } + bool isCustom() const { return m_Flags.testFlag(CustomExecutable); } - bool isShownOnToolbar() const { return m_Flags.testFlag(ShowInToolbar); } + bool isShownOnToolbar() const { return m_Flags.testFlag(ShowInToolbar); } - void showOnToolbar(bool state); + void showOnToolbar(bool state); - bool usesOwnIcon() const { return m_Flags.testFlag(UseApplicationIcon); } + bool usesOwnIcon() const { return m_Flags.testFlag(UseApplicationIcon); } }; + /*! * @brief List of executables configured to by started from MO **/ class ExecutablesList { public: - /** - * @brief constructor - * - **/ - ExecutablesList(); - - ~ExecutablesList(); - - /** - * @brief initialise the list with the executables preconfigured for this game - **/ - void init(MOBase::IPluginGame const* game); - - /** - * @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 - **/ - const Executable& find(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& find(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& findByBinary(const QFileInfo& info); - - /** - * @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 addExecutable(const Executable& executable); - - /** - * @brief add a new executable to the list - * - * @param title name displayed in the UI - * @param executableName the actual filename to execute - * @param arguments arguments to pass to the executable - **/ - void addExecutable(const QString& title, const QString& executableName, const QString& arguments, - const QString& workingDirectory, const QString& steamAppID, Executable::Flags flags) { - updateExecutable(title, executableName, arguments, workingDirectory, steamAppID, Executable::AllFlags, flags); - } - - /** - * @brief Update an executable to the list - * - * @param title name displayed in the UI - * @param executableName the actual filename to execute - * @param arguments arguments to pass to the executable - * @param closeMO if true, MO will be closed when the binary is started - **/ - void updateExecutable(const QString& title, const QString& executableName, const QString& arguments, - const QString& workingDirectory, const QString& steamAppID, Executable::Flags mask, - Executable::Flags flags); - - /** - * @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); - - /** - * @brief retrieve begin and end iterators of the configured executables - * - * @param begin iterator to the first executable - * @param end iterator one past the last executable - **/ - void getExecutables(std::vector::const_iterator& begin, - std::vector::const_iterator& end) const; - - /** - * @brief retrieve begin and end iterators of the configured executables - * - * @param begin iterator to the first executable - * @param end iterator one past the last executable - **/ - void getExecutables(std::vector::iterator& begin, std::vector::iterator& end); - - /** - * @brief get the number of executables (custom or otherwise) - **/ - size_t size() const { return m_Executables.size(); } + + /** + * @brief constructor + * + **/ + ExecutablesList(); + + ~ExecutablesList(); + + /** + * @brief initialise the list with the executables preconfigured for this game + **/ + void init(MOBase::IPluginGame const *game); + + /** + * @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 + **/ + const Executable &find(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 &find(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 &findByBinary(const QFileInfo &info); + + /** + * @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 addExecutable(const Executable &executable); + + /** + * @brief add a new executable to the list + * + * @param title name displayed in the UI + * @param executableName the actual filename to execute + * @param arguments arguments to pass to the executable + **/ + void addExecutable(const QString &title, + const QString &executableName, + const QString &arguments, + const QString &workingDirectory, + const QString &steamAppID, + Executable::Flags flags) + { + updateExecutable(title, executableName, arguments, workingDirectory, + steamAppID, Executable::AllFlags, flags); + } + + /** + * @brief Update an executable to the list + * + * @param title name displayed in the UI + * @param executableName the actual filename to execute + * @param arguments arguments to pass to the executable + * @param closeMO if true, MO will be closed when the binary is started + **/ + void updateExecutable(const QString &title, + const QString &executableName, + const QString &arguments, + const QString &workingDirectory, + const QString &steamAppID, + Executable::Flags mask, + Executable::Flags flags); + + /** + * @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); + + /** + * @brief retrieve begin and end iterators of the configured executables + * + * @param begin iterator to the first executable + * @param end iterator one past the last executable + **/ + void getExecutables(std::vector::const_iterator &begin, std::vector::const_iterator &end) const; + + /** + * @brief retrieve begin and end iterators of the configured executables + * + * @param begin iterator to the first executable + * @param end iterator one past the last executable + **/ + void getExecutables(std::vector::iterator &begin, std::vector::iterator &end); + + /** + * @brief get the number of executables (custom or otherwise) + **/ + size_t size() const { + return m_Executables.size(); + } private: - std::vector::iterator findExe(const QString& title); - void addExecutableInternal(const QString& title, const QString& executableName, const QString& arguments, - const QString& workingDirectory, const QString& steamAppID); + std::vector::iterator findExe(const QString &title); + + void addExecutableInternal(const QString &title, const QString &executableName, const QString &arguments, + const QString &workingDirectory, + const QString &steamAppID); private: - std::vector m_Executables; + + std::vector m_Executables; + }; Q_DECLARE_OPERATORS_FOR_FLAGS(Executable::Flags) diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index d5ce5f24..6b440d0f 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -20,61 +20,75 @@ along with Mod Organizer. If not, see . #include "filedialogmemory.h" #include -FileDialogMemory::FileDialogMemory() {} - -void FileDialogMemory::save(QSettings& settings) { - settings.remove("recentDirectories"); - settings.beginWriteArray("recentDirectories"); - int index = 0; - for (std::map::const_iterator iter = instance().m_Cache.begin(); iter != instance().m_Cache.end(); - ++iter) { - settings.setArrayIndex(index++); - settings.setValue("name", iter->first); - settings.setValue("directory", iter->second); - } - settings.endArray(); + +FileDialogMemory::FileDialogMemory() +{ } -void FileDialogMemory::restore(QSettings& settings) { - int size = settings.beginReadArray("recentDirectories"); - for (int i = 0; i < size; ++i) { - settings.setArrayIndex(i); - QVariant name = settings.value("name"); - QVariant dir = settings.value("directory"); - if (name.isValid() && dir.isValid()) { - instance().m_Cache.insert(std::make_pair(name.toString(), dir.toString())); - } - } - settings.endArray(); + +void FileDialogMemory::save(QSettings &settings) +{ + settings.remove("recentDirectories"); + settings.beginWriteArray("recentDirectories"); + int index = 0; + for (std::map::const_iterator iter = instance().m_Cache.begin(); + iter != instance().m_Cache.end(); ++iter) { + settings.setArrayIndex(index++); + settings.setValue("name", iter->first); + settings.setValue("directory", iter->second); + } + settings.endArray(); } -QString FileDialogMemory::getOpenFileName(const QString& dirID, QWidget* parent, const QString& caption, - const QString& dir, const QString& filter, QString* selectedFilter, - QFileDialog::Options options) { - std::pair::iterator, bool> currentDir = - instance().m_Cache.insert(std::make_pair(dirID, dir)); - QString result = - QFileDialog::getOpenFileName(parent, caption, currentDir.first->second, filter, selectedFilter, options); - if (!result.isNull()) { - instance().m_Cache[dirID] = QFileInfo(result).path(); +void FileDialogMemory::restore(QSettings &settings) +{ + int size = settings.beginReadArray("recentDirectories"); + for (int i = 0; i < size; ++i) { + settings.setArrayIndex(i); + QVariant name = settings.value("name"); + QVariant dir = settings.value("directory"); + if (name.isValid() && dir.isValid()) { + instance().m_Cache.insert(std::make_pair(name.toString(), dir.toString())); } - return result; + } + settings.endArray(); } -QString FileDialogMemory::getExistingDirectory(const QString& dirID, QWidget* parent, const QString& caption, - const QString& dir, QFileDialog::Options options) { - std::pair::iterator, bool> currentDir = - instance().m_Cache.insert(std::make_pair(dirID, dir)); - QString result = QFileDialog::getExistingDirectory(parent, caption, currentDir.first->second, options); - if (!result.isNull()) { - instance().m_Cache[dirID] = QFileInfo(result).path(); - } - return result; +QString FileDialogMemory::getOpenFileName(const QString &dirID, QWidget *parent, + const QString &caption, const QString &dir, + const QString &filter, QString *selectedFilter, + QFileDialog::Options options) +{ + std::pair::iterator, bool> currentDir = + instance().m_Cache.insert(std::make_pair(dirID, dir)); + + QString result = QFileDialog::getOpenFileName(parent, caption, currentDir.first->second, + filter, selectedFilter, options); + if (!result.isNull()) { + instance().m_Cache[dirID] = QFileInfo(result).path(); + } + return result; } -FileDialogMemory& FileDialogMemory::instance() { - static FileDialogMemory instance; - return instance; + +QString FileDialogMemory::getExistingDirectory(const QString &dirID, QWidget *parent, + const QString &caption, const QString &dir, QFileDialog::Options options) +{ + std::pair::iterator, bool> currentDir = + instance().m_Cache.insert(std::make_pair(dirID, dir)); + + QString result = QFileDialog::getExistingDirectory(parent, caption, currentDir.first->second, options); + if (!result.isNull()) { + instance().m_Cache[dirID] = QFileInfo(result).path(); + } + return result; +} + + +FileDialogMemory &FileDialogMemory::instance() +{ + static FileDialogMemory instance; + return instance; } diff --git a/src/filedialogmemory.h b/src/filedialogmemory.h index d853bd51..b2bfdb53 100644 --- a/src/filedialogmemory.h +++ b/src/filedialogmemory.h @@ -20,32 +20,39 @@ along with Mod Organizer. If not, see . #ifndef FILEDIALOGMEMORY_H #define FILEDIALOGMEMORY_H -#include -#include -#include + #include +#include +#include +#include + -class FileDialogMemory { +class FileDialogMemory +{ public: - static void save(QSettings& settings); - static void restore(QSettings& 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 = 0); + static void save(QSettings &settings); + static void restore(QSettings &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 = 0); - static QString getExistingDirectory(const QString& dirID, QWidget* parent = 0, const QString& caption = QString(), - const QString& dir = QString(), - QFileDialog::Options options = QFileDialog::ShowDirsOnly); + static QString getExistingDirectory(const QString &dirID, QWidget *parent = 0, const QString &caption = QString(), + const QString &dir = QString(), + QFileDialog::Options options = QFileDialog::ShowDirsOnly); private: - FileDialogMemory(); - static FileDialogMemory& instance(); + FileDialogMemory(); + + static FileDialogMemory &instance(); private: - std::map m_Cache; + + std::map m_Cache; + }; #endif // FILEDIALOGMEMORY_H diff --git a/src/gameinfoimpl.cpp b/src/gameinfoimpl.cpp index 56f4749c..61a4c523 100644 --- a/src/gameinfoimpl.cpp +++ b/src/gameinfoimpl.cpp @@ -18,36 +18,39 @@ along with Mod Organizer. If not, see . */ #include "gameinfoimpl.h" -#include #include #include +#include + using namespace MOBase; using namespace MOShared; -GameInfoImpl::GameInfoImpl() {} - -IGameInfo::Type GameInfoImpl::type() const { - switch (GameInfo::instance().getType()) { - case GameInfo::TYPE_OBLIVION: - return IGameInfo::TYPE_OBLIVION; - case GameInfo::TYPE_FALLOUT3: - return IGameInfo::TYPE_FALLOUT3; - case GameInfo::TYPE_FALLOUT4: - return IGameInfo::TYPE_FALLOUT4; - case GameInfo::TYPE_FALLOUTNV: - return IGameInfo::TYPE_FALLOUTNV; - case GameInfo::TYPE_SKYRIM: - return IGameInfo::TYPE_SKYRIM; - case GameInfo::TYPE_SKYRIMSE: - return IGameInfo::TYPE_SKYRIMSE; - default: - throw MyException(QObject::tr("invalid game type %1").arg(GameInfo::instance().getType())); - } + +GameInfoImpl::GameInfoImpl() +{ } -QString GameInfoImpl::path() const { - return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); +IGameInfo::Type GameInfoImpl::type() const +{ + switch (GameInfo::instance().getType()) { + case GameInfo::TYPE_OBLIVION: return IGameInfo::TYPE_OBLIVION; + case GameInfo::TYPE_FALLOUT3: return IGameInfo::TYPE_FALLOUT3; + case GameInfo::TYPE_FALLOUT4: return IGameInfo::TYPE_FALLOUT4; + case GameInfo::TYPE_FALLOUTNV: return IGameInfo::TYPE_FALLOUTNV; + case GameInfo::TYPE_SKYRIM: return IGameInfo::TYPE_SKYRIM; + case GameInfo::TYPE_SKYRIMSE: return IGameInfo::TYPE_SKYRIMSE; + default: throw MyException(QObject::tr("invalid game type %1").arg(GameInfo::instance().getType())); + } } -QString GameInfoImpl::binaryName() const { return ToQString(GameInfo::instance().getBinaryName()); } + +QString GameInfoImpl::path() const +{ + return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); +} + +QString GameInfoImpl::binaryName() const +{ + return ToQString(GameInfo::instance().getBinaryName()); +} diff --git a/src/genericicondelegate.cpp b/src/genericicondelegate.cpp index 98917c33..1e00f0e9 100644 --- a/src/genericicondelegate.cpp +++ b/src/genericicondelegate.cpp @@ -3,25 +3,37 @@ #include #include -GenericIconDelegate::GenericIconDelegate(QObject* parent, int role, int logicalIndex, int compactSize) - : IconDelegate(parent), m_Role(role), m_LogicalIndex(logicalIndex), m_CompactSize(compactSize), m_Compact(false) {} -void GenericIconDelegate::columnResized(int logicalIndex, int, int newSize) { - if (logicalIndex == m_LogicalIndex) { - m_Compact = newSize < m_CompactSize; - } +GenericIconDelegate::GenericIconDelegate(QObject *parent, int role, int logicalIndex, int compactSize) + : IconDelegate(parent) + , m_Role(role) + , m_LogicalIndex(logicalIndex) + , m_CompactSize(compactSize) + , m_Compact(false) +{ +} + +void GenericIconDelegate::columnResized(int logicalIndex, int, int newSize) +{ + if (logicalIndex == m_LogicalIndex) { + m_Compact = newSize < m_CompactSize; + } } -QList GenericIconDelegate::getIcons(const QModelIndex& index) const { - QList result; - if (index.isValid()) { - for (const QVariant& var : index.data(m_Role).toList()) { - if (!m_Compact || !var.toString().isEmpty()) { - result.append(var.toString()); - } - } +QList GenericIconDelegate::getIcons(const QModelIndex &index) const +{ + QList result; + if (index.isValid()) { + for (const QVariant &var : index.data(m_Role).toList()) { + if (!m_Compact || !var.toString().isEmpty()) { + result.append(var.toString()); + } } - return result; + } + return result; } -size_t GenericIconDelegate::getNumIcons(const QModelIndex& index) const { return index.data(m_Role).toList().count(); } +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 e6ddddc3..98605250 100644 --- a/src/genericicondelegate.h +++ b/src/genericicondelegate.h @@ -6,34 +6,31 @@ /** * @brief an icon delegate that takes the list of icons from a user-defines data role */ -class GenericIconDelegate : public IconDelegate { - Q_OBJECT +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(QObject* parent = nullptr, int role = Qt::UserRole + 1, int logicalIndex = -1, - int compactSize = 150); + /** + * @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(QObject *parent = nullptr, int role = Qt::UserRole + 1, int logicalIndex = -1, int compactSize = 150); public slots: - void columnResized(int logicalIndex, int oldSize, int newSize); - + void columnResized(int logicalIndex, int oldSize, int newSize); private: - virtual QList getIcons(const QModelIndex& index) const; - virtual size_t getNumIcons(const QModelIndex& index) const; - + 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; + int m_Role; + int m_LogicalIndex; + int m_CompactSize; + bool m_Compact; }; #endif // GENERICICONDELEGATE_H diff --git a/src/helper.cpp b/src/helper.cpp index 172cf2a2..b7fc866c 100644 --- a/src/helper.cpp +++ b/src/helper.cpp @@ -19,8 +19,8 @@ along with Mod Organizer. If not, see . #include "helper.h" #include "utility.h" -#include #include +#include #define WIN32_LEAN_AND_MEAN #include @@ -29,61 +29,71 @@ along with Mod Organizer. If not, see . using MOBase::reportError; + namespace Helper { -static bool helperExec(LPCWSTR moDirectory, LPCWSTR commandLine) { - wchar_t fileName[MAX_PATH]; - _snwprintf(fileName, MAX_PATH, L"%ls\\helper.exe", moDirectory); - SHELLEXECUTEINFOW execInfo = {0}; +static bool helperExec(LPCWSTR moDirectory, LPCWSTR commandLine) +{ + wchar_t fileName[MAX_PATH]; + _snwprintf(fileName, MAX_PATH, L"%ls\\helper.exe", moDirectory); + + SHELLEXECUTEINFOW execInfo = {0}; - execInfo.cbSize = sizeof(SHELLEXECUTEINFOW); - execInfo.fMask = SEE_MASK_NOCLOSEPROCESS; - execInfo.hwnd = nullptr; - execInfo.lpVerb = L"runas"; - execInfo.lpFile = fileName; - execInfo.lpParameters = commandLine; - execInfo.lpDirectory = moDirectory; - execInfo.nShow = SW_SHOW; + execInfo.cbSize = sizeof(SHELLEXECUTEINFOW); + execInfo.fMask = SEE_MASK_NOCLOSEPROCESS; + execInfo.hwnd = nullptr; + execInfo.lpVerb = L"runas"; + execInfo.lpFile = fileName; + execInfo.lpParameters = commandLine; + execInfo.lpDirectory = moDirectory; + execInfo.nShow = SW_SHOW; - ::ShellExecuteExW(&execInfo); + ::ShellExecuteExW(&execInfo); - if ((execInfo.hProcess == 0) || (::WaitForSingleObject(execInfo.hProcess, INFINITE) != WAIT_OBJECT_0)) { - reportError(QObject::tr("helper failed")); - return false; - } + if ((execInfo.hProcess == 0) || (::WaitForSingleObject(execInfo.hProcess, INFINITE) != WAIT_OBJECT_0)) { + reportError(QObject::tr("helper failed")); + return false; + } - DWORD exitCode; - GetExitCodeProcess(execInfo.hProcess, &exitCode); - return exitCode == NOERROR; + DWORD exitCode; + GetExitCodeProcess(execInfo.hProcess, &exitCode); + return exitCode == NOERROR; } -bool init(const std::wstring& moPath, const std::wstring& dataPath) { - DWORD userNameLen = UNLEN + 1; - wchar_t userName[UNLEN + 1]; - if (!GetUserName(userName, &userNameLen)) { - reportError(QObject::tr("failed to determine account name")); - return false; - } - wchar_t* commandLine = new wchar_t[32768]; +bool init(const std::wstring &moPath, const std::wstring &dataPath) +{ + DWORD userNameLen = UNLEN + 1; + wchar_t userName[UNLEN + 1]; - _snwprintf(commandLine, 32768, L"init \"%ls\" \"%ls\"", dataPath.c_str(), userName); + if (!GetUserName(userName, &userNameLen)) { + reportError(QObject::tr("failed to determine account name")); + return false; + } + wchar_t *commandLine = new wchar_t[32768]; - bool res = helperExec(moPath.c_str(), commandLine); - delete[] commandLine; + _snwprintf(commandLine, 32768, L"init \"%ls\" \"%ls\"", + dataPath.c_str(), userName); - return res; + bool res = helperExec(moPath.c_str(), commandLine); + delete [] commandLine; + + return res; } -bool backdateBSAs(const std::wstring& moPath, const std::wstring& dataPath) { - wchar_t* commandLine = new wchar_t[32768]; - _snwprintf(commandLine, 32768, L"backdateBSA \"%ls\"", dataPath.c_str()); - bool res = helperExec(moPath.c_str(), commandLine); - delete[] commandLine; +bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath) +{ + wchar_t *commandLine = new wchar_t[32768]; + _snwprintf(commandLine, 32768, L"backdateBSA \"%ls\"", + dataPath.c_str()); + + bool res = helperExec(moPath.c_str(), commandLine); + delete [] commandLine; - return res; + return res; } -} // namespace Helper + +} // namespace diff --git a/src/helper.h b/src/helper.h index 738950a2..cd4b7883 100644 --- a/src/helper.h +++ b/src/helper.h @@ -20,11 +20,13 @@ along with Mod Organizer. If not, see . #ifndef HELPER_H #define HELPER_H + #include + /** * @brief Convenience functions to work with the external helper program. - * + * * The mo_helper program is used to make changes on the system that require administrative * rights, so that ModOrganizer itself can run without special privileges **/ @@ -32,22 +34,23 @@ namespace Helper { /** * @brief initialise the specified directory for use with mod organizer. - * + * * This will create all required sub-directories and give the user running ModOrganizer * write-access * * @param moPath absolute path to the ModOrganizer base directory * @return true on success **/ -bool init(const std::wstring& moPath, const std::wstring& dataPath); +bool init(const std::wstring &moPath, const std::wstring &dataPath); /** * @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(const std::wstring& moPath, const std::wstring& dataPath); +bool backdateBSAs(const std::wstring &moPath, const std::wstring &dataPath); + +} -} // namespace Helper #endif // HELPER_H diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp index 4125210a..e502dc69 100644 --- a/src/icondelegate.cpp +++ b/src/icondelegate.cpp @@ -18,43 +18,50 @@ along with Mod Organizer. If not, see . */ #include "icondelegate.h" -#include #include #include #include +#include #include -IconDelegate::IconDelegate(QObject* parent) : QStyledItemDelegate(parent) {} - -void IconDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { - QStyledItemDelegate::paint(painter, option, index); - - QList icons = getIcons(index); - - int x = 4; - painter->save(); - - int iconWidth = icons.size() > 0 ? ((option.rect.width() / icons.size()) - 4) : 16; - iconWidth = std::min(16, iconWidth); - - 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()) { - qWarning("failed to load icon %s", qPrintable(iconId)); - } - QPixmapCache::insert(fullIconId, icon); - } - painter->drawPixmap(x, 2, iconWidth, iconWidth, icon); - x += iconWidth + 4; + +IconDelegate::IconDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ +} + + +void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const +{ + QStyledItemDelegate::paint(painter, option, index); + + QList icons = getIcons(index); + + int x = 4; + painter->save(); + + int iconWidth = icons.size() > 0 ? ((option.rect.width() / icons.size()) - 4) : 16; + iconWidth = std::min(16, iconWidth); + + 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()) { + qWarning("failed to load icon %s", qPrintable(iconId)); + } + QPixmapCache::insert(fullIconId, icon); } + painter->drawPixmap(x, 2, iconWidth, iconWidth, icon); + x += iconWidth + 4; + } - painter->restore(); + painter->restore(); } + diff --git a/src/icondelegate.h b/src/icondelegate.h index c0c28388..39694481 100644 --- a/src/icondelegate.h +++ b/src/icondelegate.h @@ -21,25 +21,31 @@ along with Mod Organizer. If not, see . #define ICONDELEGATE_H #include "modinfo.h" -#include #include +#include -class IconDelegate : public QStyledItemDelegate { - Q_OBJECT + +class IconDelegate : public QStyledItemDelegate +{ + Q_OBJECT public: - explicit IconDelegate(QObject* parent = 0); - virtual void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; + explicit IconDelegate(QObject *parent = 0); -signals: + virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; +signals: + public slots: private: - virtual QList getIcons(const QModelIndex& index) const = 0; - virtual size_t getNumIcons(const QModelIndex& index) const = 0; + + virtual QList getIcons(const QModelIndex &index) const = 0; + virtual size_t getNumIcons(const QModelIndex &index) const = 0; + private: + }; #endif // ICONDELEGATE_H diff --git a/src/ilockedwaitingforprocess.h b/src/ilockedwaitingforprocess.h index 5f933417..9475ddb9 100644 --- a/src/ilockedwaitingforprocess.h +++ b/src/ilockedwaitingforprocess.h @@ -3,10 +3,11 @@ class QString; -class ILockedWaitingForProcess { +class ILockedWaitingForProcess +{ public: - virtual bool unlockForced() const = 0; - virtual void setProcessName(QString const&) = 0; + virtual bool unlockForced() const = 0; + virtual void setProcessName(QString const &) = 0; }; #endif // ILOCKEDWAITINGFORPROCESS_H diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 48faecc1..9a70f8bd 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -19,34 +19,35 @@ along with Mod Organizer. If not, see . #include "installationmanager.h" +#include "utility.h" +#include "report.h" #include "categories.h" -#include "iplugininstallercustom.h" -#include "iplugininstallersimple.h" +#include "questionboxmemory.h" +#include "settings.h" +#include "queryoverwritedialog.h" #include "messagedialog.h" -#include "modinfo.h" +#include "iplugininstallersimple.h" +#include "iplugininstallercustom.h" #include "nexusinterface.h" -#include "queryoverwritedialog.h" -#include "questionboxmemory.h" -#include "report.h" #include "selectiondialog.h" -#include "settings.h" -#include "utility.h" -#include +#include "modinfo.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 @@ -54,743 +55,822 @@ along with Mod Organizer. If not, see . #include #include + using namespace MOBase; using namespace MOShared; + typedef Archive* (*CreateArchiveType)(); + + template -static T resolveFunction(QLibrary& lib, const char* name) { - T temp = reinterpret_cast(lib.resolve(name)); - if (temp == nullptr) { - throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData()); - } - return temp; +static T resolveFunction(QLibrary &lib, const char *name) +{ + T temp = reinterpret_cast(lib.resolve(name)); + if (temp == nullptr) { + throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1") + .arg(lib.errorString()) + .toLatin1() + .constData()); + } + return temp; } InstallationManager::InstallationManager() - : m_ParentWidget(nullptr), m_SupportedExtensions({"zip", "rar", "7z", "fomod", "001"}) { - QLibrary archiveLib(QCoreApplication::applicationDirPath() + "\\dlls\\archive.dll"); - if (!archiveLib.load()) { - throw MyException(QObject::tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString())); - } - - CreateArchiveType CreateArchiveFunc = resolveFunction(archiveLib, "CreateArchive"); - - m_ArchiveHandler = CreateArchiveFunc(); - if (!m_ArchiveHandler->isValid()) { - throw MyException(getErrorString(m_ArchiveHandler->getLastError())); - } + : m_ParentWidget(nullptr), + m_SupportedExtensions({"zip", "rar", "7z", "fomod", "001"}) { + QLibrary archiveLib(QCoreApplication::applicationDirPath() + + "\\dlls\\archive.dll"); + if (!archiveLib.load()) { + throw MyException(QObject::tr("archive.dll not loaded: \"%1\"") + .arg(archiveLib.errorString())); + } + + CreateArchiveType CreateArchiveFunc + = resolveFunction(archiveLib, "CreateArchive"); + + m_ArchiveHandler = CreateArchiveFunc(); + if (!m_ArchiveHandler->isValid()) { + throw MyException(getErrorString(m_ArchiveHandler->getLastError())); + } } -InstallationManager::~InstallationManager() { delete m_ArchiveHandler; } +InstallationManager::~InstallationManager() +{ + delete m_ArchiveHandler; +} -void InstallationManager::setParentWidget(QWidget* widget) { - for (IPluginInstaller* installer : m_Installers) { - installer->setParentWidget(widget); - } +void InstallationManager::setParentWidget(QWidget *widget) +{ + for (IPluginInstaller *installer : m_Installers) { + installer->setParentWidget(widget); + } } -void InstallationManager::setURL(QString const& url) { m_URL = url; } +void InstallationManager::setURL(QString const &url) +{ + m_URL = url; +} -void InstallationManager::queryPassword(QString* password) { - *password = QInputDialog::getText(nullptr, tr("Password required"), tr("Password"), QLineEdit::Password); +void InstallationManager::queryPassword(QString *password) +{ + *password = QInputDialog::getText(nullptr, tr("Password required"), + tr("Password"), QLineEdit::Password); } -void InstallationManager::mapToArchive(const DirectoryTree::Node* node, QString path, FileData* 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. - if (path == ".") { - path.clear(); - } else { - path.append("\\"); - } +void InstallationManager::mapToArchive(const DirectoryTree::Node *node, QString path, FileData * 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. + if (path == ".") { + path.clear(); + } else { + path.append("\\"); } + } - for (DirectoryTree::const_leaf_iterator iter = node->leafsBegin(); iter != node->leafsEnd(); ++iter) { - data[iter->getIndex()]->addOutputFileName(path + iter->getName().toQString()); - } + for (DirectoryTree::const_leaf_iterator iter = node->leafsBegin(); iter != node->leafsEnd(); ++iter) { + data[iter->getIndex()]->addOutputFileName(path + iter->getName().toQString()); + } - for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) { - QString temp = path + (*iter)->getData().name.toQString(); - if ((*iter)->getData().index != -1) { - data[(*iter)->getData().index]->addOutputFileName(temp); - } - mapToArchive(*iter, temp, data); + for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) { + QString temp = path + (*iter)->getData().name.toQString(); + if ((*iter)->getData().index != -1) { + data[(*iter)->getData().index]->addOutputFileName(temp); } + mapToArchive(*iter, temp, data); + } } -void InstallationManager::mapToArchive(const DirectoryTree::Node* baseNode) { - FileData* const* data; - size_t size; - m_ArchiveHandler->getFileList(data, size); - mapToArchive(baseNode, "", data); +void InstallationManager::mapToArchive(const DirectoryTree::Node *baseNode) +{ + FileData* const *data; + size_t size; + m_ArchiveHandler->getFileList(data, size); + + mapToArchive(baseNode, "", data); } -bool InstallationManager::unpackSingleFile(const QString& fileName) { - FileData* const* data; - size_t size; - m_ArchiveHandler->getFileList(data, size); - QString baseName = QFileInfo(fileName).fileName(); +bool InstallationManager::unpackSingleFile(const QString &fileName) +{ + FileData* const *data; + size_t size; + m_ArchiveHandler->getFileList(data, size); - bool available = false; - for (size_t i = 0; i < size; ++i) { - if (data[i]->getFileName().compare(fileName, Qt::CaseInsensitive) == 0) { - available = true; - data[i]->addOutputFileName(baseName); - m_TempFilesToDelete.insert(baseName); - } - } + QString baseName = QFileInfo(fileName).fileName(); - if (!available) { - return false; + bool available = false; + for (size_t i = 0; i < size; ++i) { + if (data[i]->getFileName().compare(fileName, Qt::CaseInsensitive) == 0) { + available = true; + data[i]->addOutputFileName(baseName); + m_TempFilesToDelete.insert(baseName); } + } - m_InstallationProgress = new QProgressDialog(m_ParentWidget); - ON_BLOCK_EXIT([this]() { - m_InstallationProgress->hide(); - m_InstallationProgress->deleteLater(); - m_InstallationProgress = nullptr; - }); - m_InstallationProgress->setWindowFlags(m_InstallationProgress->windowFlags() & ~Qt::WindowContextHelpButtonHint); - - m_InstallationProgress->setWindowTitle(tr("Extracting files")); - m_InstallationProgress->setWindowModality(Qt::WindowModal); - m_InstallationProgress->setFixedSize(600, 100); - m_InstallationProgress->show(); - - bool res = m_ArchiveHandler->extract( - QDir::tempPath(), - new MethodCallback(this, &InstallationManager::updateProgress), nullptr, - new MethodCallback(this, &InstallationManager::report7ZipError)); - - return res; -} - -QString InstallationManager::extractFile(const QString& fileName) { - if (unpackSingleFile(fileName)) { - return QDir::tempPath() + "/" + QFileInfo(fileName).fileName(); - } else { - return QString(); - } + if (!available) { + return false; + } + + m_InstallationProgress = new QProgressDialog(m_ParentWidget); + ON_BLOCK_EXIT([this] () { + m_InstallationProgress->hide(); + m_InstallationProgress->deleteLater(); + m_InstallationProgress = nullptr; + }); + m_InstallationProgress->setWindowFlags( + m_InstallationProgress->windowFlags() & ~Qt::WindowContextHelpButtonHint); + + m_InstallationProgress->setWindowTitle(tr("Extracting files")); + m_InstallationProgress->setWindowModality(Qt::WindowModal); + m_InstallationProgress->setFixedSize(600, 100); + m_InstallationProgress->show(); + + bool res = m_ArchiveHandler->extract(QDir::tempPath(), + new MethodCallback(this, &InstallationManager::updateProgress), + nullptr, + new MethodCallback(this, &InstallationManager::report7ZipError)); + + return res; } -static QString canonicalize(const QString& name) { - QString result(name); - if ((result.startsWith('/')) || (result.startsWith('\\'))) { - result.remove(0, 1); - } - result.replace('/', '\\'); - return result; +QString InstallationManager::extractFile(const QString &fileName) +{ + if (unpackSingleFile(fileName)) { + return QDir::tempPath() + "/" + QFileInfo(fileName).fileName(); + } else { + return QString(); + } } -QStringList InstallationManager::extractFiles(const QStringList& filesOrig, bool flatten) { - QStringList files; - for (const QString& file : filesOrig) { - files.append(canonicalize(file)); - } +static QString canonicalize(const QString &name) +{ + QString result(name); + if ((result.startsWith('/')) || + (result.startsWith('\\'))) { + result.remove(0, 1); + } + result.replace('/', '\\'); - QStringList result; - - FileData* const* data; - size_t size; - m_ArchiveHandler->getFileList(data, size); - - for (size_t i = 0; i < size; ++i) { - // FIXME Use qstring all the way through - if (files.contains(data[i]->getFileName(), Qt::CaseInsensitive)) { - std::wstring temp = data[i]->getFileName().toStdWString(); - wchar_t const* const origFile = temp.c_str(); - const wchar_t* targetFile = origFile; - // Note: I don't think 'flatten' is ever set to true. so this code - // might never be executed - if (flatten) { - targetFile = wcsrchr(origFile /*data[i]->getFileName()*/, '\\'); - if (targetFile == nullptr) { - targetFile = wcsrchr(origFile /*data[i]->getFileName()*/, '/'); - } - if (targetFile == nullptr) { - qCritical() << "Failed to find backslash in " << data[i]->getFileName(); - continue; - } else { - // skip the slash - ++targetFile; - } - } - data[i]->addOutputFileName(ToQString(targetFile)); + return result; +} - result.append(QDir::tempPath().append("/").append(ToQString(targetFile))); - m_TempFilesToDelete.insert(ToQString(targetFile)); +QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool flatten) +{ + QStringList files; + + for (const QString &file : filesOrig) { + files.append(canonicalize(file)); + } + + QStringList result; + + FileData* const *data; + size_t size; + m_ArchiveHandler->getFileList(data, size); + + for (size_t i = 0; i < size; ++i) { + //FIXME Use qstring all the way through + if (files.contains(data[i]->getFileName(), Qt::CaseInsensitive)) { + std::wstring temp = data[i]->getFileName().toStdWString(); + wchar_t const * const origFile = temp.c_str(); + const wchar_t *targetFile = origFile; + //Note: I don't think 'flatten' is ever set to true. so this code + //might never be executed + if (flatten) { + targetFile = wcsrchr(origFile/*data[i]->getFileName()*/, '\\'); + if (targetFile == nullptr) { + targetFile = wcsrchr(origFile/*data[i]->getFileName()*/, '/'); } - } - - m_InstallationProgress = new QProgressDialog(m_ParentWidget); - ON_BLOCK_EXIT([this]() { - m_InstallationProgress->hide(); - m_InstallationProgress->deleteLater(); - m_InstallationProgress = nullptr; - }); - m_InstallationProgress->setWindowFlags(m_InstallationProgress->windowFlags() & (~Qt::WindowContextHelpButtonHint)); - m_InstallationProgress->setWindowTitle(tr("Extracting files")); - m_InstallationProgress->setWindowModality(Qt::WindowModal); - m_InstallationProgress->setFixedSize(600, 100); - m_InstallationProgress->show(); - - // unpack only the files we need for the installer - if (!m_ArchiveHandler->extract( - QDir::tempPath(), - new MethodCallback(this, &InstallationManager::updateProgress), nullptr, - new MethodCallback(this, - &InstallationManager::report7ZipError))) { - throw MyException(QString("extracting failed (%1)").arg(m_ArchiveHandler->getLastError())); - } - - return result; + if (targetFile == nullptr) { + qCritical() << "Failed to find backslash in " << data[i]->getFileName(); + continue; + } else { + // skip the slash + ++targetFile; + } + } + data[i]->addOutputFileName(ToQString(targetFile)); + + result.append(QDir::tempPath().append("/").append(ToQString(targetFile))); + + m_TempFilesToDelete.insert(ToQString(targetFile)); + } + } + + m_InstallationProgress = new QProgressDialog(m_ParentWidget); + ON_BLOCK_EXIT([this] () { + m_InstallationProgress->hide(); + m_InstallationProgress->deleteLater(); + m_InstallationProgress = nullptr; + }); + m_InstallationProgress->setWindowFlags( + m_InstallationProgress->windowFlags() & (~Qt::WindowContextHelpButtonHint)); + m_InstallationProgress->setWindowTitle(tr("Extracting files")); + m_InstallationProgress->setWindowModality(Qt::WindowModal); + m_InstallationProgress->setFixedSize(600, 100); + m_InstallationProgress->show(); + + // unpack only the files we need for the installer + if (!m_ArchiveHandler->extract(QDir::tempPath(), + new MethodCallback(this, &InstallationManager::updateProgress), + nullptr, + new MethodCallback(this, &InstallationManager::report7ZipError))) { + throw MyException(QString("extracting failed (%1)").arg(m_ArchiveHandler->getLastError())); + } + + return result; } -IPluginInstaller::EInstallResult InstallationManager::installArchive(GuessedValue& modName, - const QString& archiveName) { - // 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. - bool iniTweaks; - if (install(archiveName, modName, iniTweaks)) { - return IPluginInstaller::RESULT_SUCCESS; - } else { - return IPluginInstaller::RESULT_FAILED; - } +IPluginInstaller::EInstallResult InstallationManager::installArchive(GuessedValue &modName, const QString &archiveName) +{ + // 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. + bool iniTweaks; + if (install(archiveName, modName, iniTweaks)) { + return IPluginInstaller::RESULT_SUCCESS; + } else { + return IPluginInstaller::RESULT_FAILED; + } } -DirectoryTree* InstallationManager::createFilesTree() { - FileData* const* data; - size_t size; - m_ArchiveHandler->getFileList(data, size); - - QScopedPointer result(new DirectoryTree); - - for (size_t i = 0; i < size; ++i) { - // the files are in a flat list where each file has a a full path relative to the archive root - // to create a tree, we have to iterate over each path component of each. This could be sped up by - // grouping the filenames first, but so far there doesn't seem to be an actual performance problem - DirectoryTree::Node* currentNode = result.data(); - - QString fileName = data[i]->getFileName(); - QStringList components = fileName.split("\\"); - - // iterate over all path-components of this filename (including the filename itself) - for (QStringList::iterator componentIter = components.begin(); componentIter != components.end(); - ++componentIter) { - if (componentIter->size() == 0) { - // empty string indicates fileName is actually only a directory name and we have - // completely processed it already. - break; - } - - bool exists = false; - // test if this path is already in the tree - for (DirectoryTree::node_iterator nodeIter = currentNode->nodesBegin(); nodeIter != currentNode->nodesEnd(); - ++nodeIter) { - if ((*nodeIter)->getData().name == *componentIter) { - currentNode = *nodeIter; - exists = true; - break; - } - } - if (!exists) { - if (componentIter + 1 == components.end()) { - // last path component. directory or file? - if (data[i]->isDirectory()) { - // this is a bit problematic. archives will often only list directories if they are empty, - // otherwise the dir only appears in the path of a file. In the UI however we allow the user - // to uncheck all files in a directory while keeping the dir checked. Those directories are - // currently not installed. - DirectoryTree::Node* newNode = new DirectoryTree::Node; - newNode->setData(DirectoryTreeInformation(*componentIter, static_cast(i))); - currentNode->addNode(newNode, false); - currentNode = newNode; - } else { - currentNode->addLeaf(FileTreeInformation(*componentIter, i)); - } - } else { - DirectoryTree::Node* newNode = new DirectoryTree::Node; - newNode->setData(DirectoryTreeInformation(*componentIter, -1)); - currentNode->addNode(newNode, false); - currentNode = newNode; - } - } +DirectoryTree *InstallationManager::createFilesTree() +{ + FileData* const *data; + size_t size; + m_ArchiveHandler->getFileList(data, size); + + QScopedPointer result(new DirectoryTree); + + for (size_t i = 0; i < size; ++i) { + // the files are in a flat list where each file has a a full path relative to the archive root + // to create a tree, we have to iterate over each path component of each. This could be sped up by + // grouping the filenames first, but so far there doesn't seem to be an actual performance problem + DirectoryTree::Node *currentNode = result.data(); + + QString fileName = data[i]->getFileName(); + QStringList components = fileName.split("\\"); + + // iterate over all path-components of this filename (including the filename itself) + for (QStringList::iterator componentIter = components.begin(); componentIter != components.end(); ++componentIter) { + if (componentIter->size() == 0) { + // empty string indicates fileName is actually only a directory name and we have + // completely processed it already. + break; + } + + bool exists = false; + // test if this path is already in the tree + for (DirectoryTree::node_iterator nodeIter = currentNode->nodesBegin(); nodeIter != currentNode->nodesEnd(); ++nodeIter) { + if ((*nodeIter)->getData().name == *componentIter) { + currentNode = *nodeIter; + exists = true; + break; } + } + + if (!exists) { + if (componentIter + 1 == components.end()) { + // last path component. directory or file? + if (data[i]->isDirectory()) { + // this is a bit problematic. archives will often only list directories if they are empty, + // otherwise the dir only appears in the path of a file. In the UI however we allow the user + // to uncheck all files in a directory while keeping the dir checked. Those directories are + // currently not installed. + DirectoryTree::Node *newNode = new DirectoryTree::Node; + newNode->setData( + DirectoryTreeInformation(*componentIter, static_cast(i))); + currentNode->addNode(newNode, false); + currentNode = newNode; + } else { + currentNode->addLeaf(FileTreeInformation(*componentIter, i)); + } + } else { + DirectoryTree::Node *newNode = new DirectoryTree::Node; + newNode->setData(DirectoryTreeInformation(*componentIter, -1)); + currentNode->addNode(newNode, false); + currentNode = newNode; + } + } } + } - return result.take(); + return result.take(); } -bool InstallationManager::isSimpleArchiveTopLayer(const DirectoryTree::Node* node, bool bainStyle) { - // see if there is at least one directory that makes sense on the top level - for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) { - if ((bainStyle && InstallationTester::isTopLevelDirectoryBain((*iter)->getData().name)) || - (!bainStyle && InstallationTester::isTopLevelDirectory((*iter)->getData().name))) { - qDebug("%s on the top level", (*iter)->getData().name.toUtf8().constData()); - return true; - } + +bool InstallationManager::isSimpleArchiveTopLayer(const DirectoryTree::Node *node, bool bainStyle) +{ + // see if there is at least one directory that makes sense on the top level + for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) { + if ((bainStyle && InstallationTester::isTopLevelDirectoryBain((*iter)->getData().name)) || + (!bainStyle && InstallationTester::isTopLevelDirectory((*iter)->getData().name))) { + qDebug("%s on the top level", (*iter)->getData().name.toUtf8().constData()); + return true; } + } - // see if there is a file that makes sense on the top level - for (DirectoryTree::const_leaf_iterator iter = node->leafsBegin(); iter != node->leafsEnd(); ++iter) { - if (InstallationTester::isTopLevelSuffix(iter->getName())) { - return true; - } + // see if there is a file that makes sense on the top level + for (DirectoryTree::const_leaf_iterator iter = node->leafsBegin(); iter != node->leafsEnd(); ++iter) { + if (InstallationTester::isTopLevelSuffix(iter->getName())) { + return true; } - return false; + } + return false; } -DirectoryTree::Node* InstallationManager::getSimpleArchiveBase(DirectoryTree* dataTree) { - DirectoryTree::Node* currentNode = dataTree; - while (true) { - if (isSimpleArchiveTopLayer(currentNode, false)) { - return currentNode; - } else if ((currentNode->numLeafs() == 0) && (currentNode->numNodes() == 1)) { - currentNode = *currentNode->nodesBegin(); - } else { - qDebug("not a simple archive"); - return nullptr; - } - } -} +DirectoryTree::Node *InstallationManager::getSimpleArchiveBase(DirectoryTree *dataTree) +{ + DirectoryTree::Node *currentNode = dataTree; -void InstallationManager::updateProgress(float percentage) { - if (m_InstallationProgress != nullptr) { - m_InstallationProgress->setValue(static_cast(percentage * 100.0)); - if (m_InstallationProgress->wasCanceled()) { - m_ArchiveHandler->cancel(); - m_InstallationProgress->reset(); - } + while (true) { + if (isSimpleArchiveTopLayer(currentNode, false)) { + return currentNode; + } else if ((currentNode->numLeafs() == 0) && + (currentNode->numNodes() == 1)) { + currentNode = *currentNode->nodesBegin(); + } else { + qDebug("not a simple archive"); + return nullptr; } + } } -void InstallationManager::updateProgressFile(QString const& fileName) { - if (m_InstallationProgress != nullptr) { - m_InstallationProgress->setLabelText(fileName); - QCoreApplication::processEvents(); + +void InstallationManager::updateProgress(float percentage) +{ + if (m_InstallationProgress != nullptr) { + m_InstallationProgress->setValue(static_cast(percentage * 100.0)); + if (m_InstallationProgress->wasCanceled()) { + m_ArchiveHandler->cancel(); + m_InstallationProgress->reset(); } + } } -void InstallationManager::report7ZipError(QString const& errorMessage) { - reportError(errorMessage); - m_ArchiveHandler->cancel(); -} -QString InstallationManager::generateBackupName(const QString& directoryName) const { - QString backupName = directoryName + "_backup"; - if (QDir(backupName).exists()) { - int idx = 2; - QString temp = backupName + QString::number(idx); - while (QDir(temp).exists()) { - ++idx; - temp = backupName + QString::number(idx); - } - backupName = temp; - } - return backupName; +void InstallationManager::updateProgressFile(QString const &fileName) +{ + if (m_InstallationProgress != nullptr) { + m_InstallationProgress->setLabelText(fileName); + QCoreApplication::processEvents(); + } } -bool InstallationManager::testOverwrite(GuessedValue& modName, bool* merge) const { - QString targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory + "\\" + modName); - while (QDir(targetDirectory).exists()) { - Settings& settings(Settings::instance()); - bool backup = settings.directInterface().value("backup_install", false).toBool(); - QueryOverwriteDialog overwriteDialog(m_ParentWidget, backup ? QueryOverwriteDialog::BACKUP_YES - : QueryOverwriteDialog::BACKUP_NO); - if (overwriteDialog.exec()) { - settings.directInterface().setValue("backup_install", overwriteDialog.backup()); - if (overwriteDialog.backup()) { - QString backupDirectory = generateBackupName(targetDirectory); - if (!copyDir(targetDirectory, backupDirectory, false)) { - reportError(tr("failed to create backup")); - return false; - } - } - if (merge != nullptr) { - *merge = (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE); - } - if (overwriteDialog.action() == QueryOverwriteDialog::ACT_RENAME) { - 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 false; - } - targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory) + "/" + modName; - } - } else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_REPLACE) { - // save original settings like categories. Because it makes sense - QString metaFilename = targetDirectory + "/meta.ini"; - QFile settingsFile(metaFilename); - QByteArray originalSettings; - if (settingsFile.open(QIODevice::ReadOnly)) { - originalSettings = settingsFile.readAll(); - settingsFile.close(); - } - - // 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 - Sleep(100); - QDir().mkdir(targetDirectory); - } - // restore the saved settings - if (settingsFile.open(QIODevice::WriteOnly)) { - settingsFile.write(originalSettings); - settingsFile.close(); - } else { - qCritical("failed to restore original settings: %s", metaFilename.toUtf8().constData()); - } - return true; - } else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE) { - return true; - } - } else { - return false; - } - } - - QDir().mkdir(targetDirectory); - return true; +void InstallationManager::report7ZipError(QString const &errorMessage) +{ + reportError(errorMessage); + m_ArchiveHandler->cancel(); } -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); - if (!ok) { - return false; - } - } - return true; -} -bool InstallationManager::doInstall(GuessedValue& modName, int modID, const QString& version, - const QString& newestVersion, int categoryID, const QString& repository) { - if (!ensureValidModName(modName)) { - return false; +QString InstallationManager::generateBackupName(const QString &directoryName) const +{ + QString backupName = directoryName + "_backup"; + if (QDir(backupName).exists()) { + int idx = 2; + QString temp = backupName + QString::number(idx); + while (QDir(temp).exists()) { + ++idx; + temp = backupName + QString::number(idx); } + backupName = temp; + } + return backupName; +} - bool merge = false; - // determine target directory - if (!testOverwrite(modName, &merge)) { - return false; - } - QString targetDirectory = QDir(m_ModsDirectory + "/" + modName).canonicalPath(); - QString targetDirectoryNative = QDir::toNativeSeparators(targetDirectory); - - qDebug("installing to \"%s\"", targetDirectoryNative.toUtf8().constData()); - - m_InstallationProgress = new QProgressDialog(m_ParentWidget); - ON_BLOCK_EXIT([this]() { - m_InstallationProgress->hide(); - m_InstallationProgress->deleteLater(); - m_InstallationProgress = nullptr; - }); - - m_InstallationProgress->setWindowFlags(m_InstallationProgress->windowFlags() & (~Qt::WindowContextHelpButtonHint)); - m_InstallationProgress->setWindowModality(Qt::WindowModal); - m_InstallationProgress->setFixedSize(600, 100); - m_InstallationProgress->show(); - if (!m_ArchiveHandler->extract( - targetDirectory, - new MethodCallback(this, &InstallationManager::updateProgress), - new MethodCallback(this, - &InstallationManager::updateProgressFile), - new MethodCallback(this, - &InstallationManager::report7ZipError))) { - if (m_ArchiveHandler->getLastError() == Archive::ERROR_EXTRACT_CANCELLED) { +bool InstallationManager::testOverwrite(GuessedValue &modName, bool *merge) const +{ + QString targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory + "\\" + modName); + while (QDir(targetDirectory).exists()) { + Settings &settings(Settings::instance()); + bool backup = settings.directInterface().value("backup_install", false).toBool(); + QueryOverwriteDialog overwriteDialog(m_ParentWidget, + backup ? QueryOverwriteDialog::BACKUP_YES : QueryOverwriteDialog::BACKUP_NO); + if (overwriteDialog.exec()) { + settings.directInterface().setValue("backup_install", overwriteDialog.backup()); + if (overwriteDialog.backup()) { + QString backupDirectory = generateBackupName(targetDirectory); + if (!copyDir(targetDirectory, backupDirectory, false)) { + reportError(tr("failed to create backup")); + return false; + } + } + if (merge != nullptr) { + *merge = (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE); + } + if (overwriteDialog.action() == QueryOverwriteDialog::ACT_RENAME) { + 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 false; + } + targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory) + "/" + modName; + } + } else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_REPLACE) { + // save original settings like categories. Because it makes sense + QString metaFilename = targetDirectory + "/meta.ini"; + QFile settingsFile(metaFilename); + QByteArray originalSettings; + if (settingsFile.open(QIODevice::ReadOnly)) { + originalSettings = settingsFile.readAll(); + settingsFile.close(); + } + + // 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 + Sleep(100); + QDir().mkdir(targetDirectory); + } + // restore the saved settings + if (settingsFile.open(QIODevice::WriteOnly)) { + settingsFile.write(originalSettings); + settingsFile.close(); } else { - throw MyException(QString("extracting failed (%1)").arg(m_ArchiveHandler->getLastError())); + qCritical("failed to restore original settings: %s", metaFilename.toUtf8().constData()); } + return true; + } else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_MERGE) { + return true; + } + } else { + return false; } + } - QSettings settingsFile(targetDirectory + "/meta.ini", QSettings::IniFormat); + QDir().mkdir(targetDirectory); - // overwrite settings only if they are actually are available or haven't been set before - if ((modID != 0) || !settingsFile.contains("modid")) { - settingsFile.setValue("modid", modID); - } - if (!settingsFile.contains("version") || - (!version.isEmpty() && - (!merge || (VersionInfo(version) >= VersionInfo(settingsFile.value("version").toString()))))) { - settingsFile.setValue("version", version); - } - if (!newestVersion.isEmpty() || !settingsFile.contains("newestVersion")) { - settingsFile.setValue("newestVersion", newestVersion); - } - // issue #51 used to overwrite the manually set categories - if (!settingsFile.contains("category")) { - settingsFile.setValue("category", QString::number(categoryID)); - } - settingsFile.setValue("installationFile", m_CurrentFile); - settingsFile.setValue("repository", repository); - settingsFile.setValue("url", m_URL); - - 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 - settingsFile.beginGroup("installedFiles"); - settingsFile.remove(""); - settingsFile.endGroup(); - } - - return true; + return true; } -bool InstallationManager::wasCancelled() { - return m_ArchiveHandler->getLastError() == Archive::ERROR_EXTRACT_CANCELLED; -} -void InstallationManager::postInstallCleanup() { - 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; - }; - - 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) { - QFileInfo fileInfo(QDir::tempPath() + "/" + tempFile); - QFile::remove(fileInfo.absoluteFilePath()); - directoriesToRemove.insert(fileInfo.absolutePath()); +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); + if (!ok) { + return false; } + } + return true; +} - m_TempFilesToDelete.clear(); +bool InstallationManager::doInstall(GuessedValue &modName, int modID, + const QString &version, const QString &newestVersion, + int categoryID, const QString &repository) +{ + if (!ensureValidModName(modName)) { + return false; + } - // 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); - } + bool merge = false; + // determine target directory + if (!testOverwrite(modName, &merge)) { + return false; + } + + QString targetDirectory = QDir(m_ModsDirectory + "/" + modName).canonicalPath(); + QString targetDirectoryNative = QDir::toNativeSeparators(targetDirectory); + + qDebug("installing to \"%s\"", targetDirectoryNative.toUtf8().constData()); + + m_InstallationProgress = new QProgressDialog(m_ParentWidget); + ON_BLOCK_EXIT([this] () { + m_InstallationProgress->hide(); + m_InstallationProgress->deleteLater(); + m_InstallationProgress = nullptr; + }); + + m_InstallationProgress->setWindowFlags( + m_InstallationProgress->windowFlags() & (~Qt::WindowContextHelpButtonHint)); + m_InstallationProgress->setWindowModality(Qt::WindowModal); + m_InstallationProgress->setFixedSize(600, 100); + m_InstallationProgress->show(); + if (!m_ArchiveHandler->extract(targetDirectory, + new MethodCallback(this, &InstallationManager::updateProgress), + new MethodCallback(this, &InstallationManager::updateProgressFile), + new MethodCallback(this, &InstallationManager::report7ZipError))) { + if (m_ArchiveHandler->getLastError() == Archive::ERROR_EXTRACT_CANCELLED) { + return false; + } else { + throw MyException(QString("extracting failed (%1)").arg(m_ArchiveHandler->getLastError())); + } + } + + QSettings settingsFile(targetDirectory + "/meta.ini", QSettings::IniFormat); + + // overwrite settings only if they are actually are available or haven't been set before + if ((modID != 0) || !settingsFile.contains("modid")) { + settingsFile.setValue("modid", modID); + } + if (!settingsFile.contains("version") || + (!version.isEmpty() && + (!merge || (VersionInfo(version) >= VersionInfo(settingsFile.value("version").toString()))))) { + settingsFile.setValue("version", version); + } + if (!newestVersion.isEmpty() || !settingsFile.contains("newestVersion")) { + settingsFile.setValue("newestVersion", newestVersion); + } + // issue #51 used to overwrite the manually set categories + if (!settingsFile.contains("category")) { + settingsFile.setValue("category", QString::number(categoryID)); + } + settingsFile.setValue("installationFile", m_CurrentFile); + settingsFile.setValue("repository", repository); + settingsFile.setValue("url", m_URL); + + 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 + settingsFile.beginGroup("installedFiles"); + settingsFile.remove(""); + settingsFile.endGroup(); + } + + return true; } -bool InstallationManager::install(const QString& fileName, GuessedValue& modName, bool& hasIniTweaks) { - QFileInfo fileInfo(fileName); - if (m_SupportedExtensions.find(fileInfo.suffix()) == m_SupportedExtensions.end()) { - reportError(tr("File format \"%1\" not supported").arg(fileInfo.suffix())); - return false; - } - modName.setFilter(&fixDirectoryName); - - modName.update(QFileInfo(fileName).completeBaseName(), GUESS_FALLBACK); - - // read out meta information from the download if available - int modID = 0; - QString version = ""; - QString newestVersion = ""; - int categoryID = 0; - QString repository = "Nexus"; - - QString metaName = fileName + ".meta"; - if (QFile(metaName).exists()) { - QSettings metaFile(metaName, QSettings::IniFormat); - 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(); - unsigned int categoryIndex = CategoryFactory::instance().resolveNexusID(metaFile.value("category", 0).toInt()); - categoryID = CategoryFactory::instance().getCategoryID(categoryIndex); - repository = metaFile.value("repository", "").toString(); - } +bool InstallationManager::wasCancelled() +{ + return m_ArchiveHandler->getLastError() == Archive::ERROR_EXTRACT_CANCELLED; +} - if (version.isEmpty()) { - QDateTime lastMod = fileInfo.lastModified(); - version = "d" + lastMod.toString("yyyy.M.d"); - } - { // 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); - if ((modID == 0) && (guessedModID != -1)) { - modID = guessedModID; - } else if (modID != guessedModID) { - qDebug("passed mod id: %d, guessed id: %d", modID, guessedModID); - } +void InstallationManager::postInstallCleanup() +{ + m_ArchiveHandler->close(); - modName.update(guessedModName, GUESS_GOOD); - } + // 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; + }; - m_CurrentFile = fileInfo.absoluteFilePath(); - if (fileInfo.dir() == QDir(m_DownloadsDirectory)) { - m_CurrentFile = fileInfo.fileName(); - } - qDebug("using mod name \"%s\" (id %d) -> %s", modName->toUtf8().constData(), modID, qPrintable(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. - m_ArchiveHandler->close(); - - // open the archive and construct the directory tree the installers work on - bool archiveOpen = m_ArchiveHandler->open( - fileName, new MethodCallback(this, &InstallationManager::queryPassword)); - if (!archiveOpen) { - qDebug("integrated archiver can't open %s: %s (%d)", qPrintable(fileName), - qPrintable(getErrorString(m_ArchiveHandler->getLastError())), m_ArchiveHandler->getLastError()); - } - ON_BLOCK_EXIT(std::bind(&InstallationManager::postInstallCleanup, this)); + std::set> directoriesToRemove(longestFirst); - QScopedPointer filesTree(archiveOpen ? createFilesTree() : nullptr); - IPluginInstaller::EInstallResult installResult = IPluginInstaller::RESULT_NOTATTEMPTED; + // 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) { + QFileInfo fileInfo(QDir::tempPath() + "/" + tempFile); + QFile::remove(fileInfo.absoluteFilePath()); + directoriesToRemove.insert(fileInfo.absolutePath()); + } - std::sort(m_Installers.begin(), m_Installers.end(), - [](IPluginInstaller* LHS, IPluginInstaller* RHS) { return LHS->priority() > RHS->priority(); }); + m_TempFilesToDelete.clear(); - for (IPluginInstaller* installer : m_Installers) { - // don't use inactive installers (installer can't be null here but vc static code analysis thinks it could) - if ((installer == nullptr) || !installer->isActive()) { - continue; - } + // 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); + } +} - // try only manual installers if that was requested - if (installResult == IPluginInstaller::RESULT_MANUALREQUESTED) { - if (!installer->isManualInstaller()) { - continue; +bool InstallationManager::install(const QString &fileName, + GuessedValue &modName, + bool &hasIniTweaks) +{ + QFileInfo fileInfo(fileName); + if (m_SupportedExtensions.find(fileInfo.suffix()) == m_SupportedExtensions.end()) { + reportError(tr("File format \"%1\" not supported").arg(fileInfo.suffix())); + return false; + } + + modName.setFilter(&fixDirectoryName); + + modName.update(QFileInfo(fileName).completeBaseName(), GUESS_FALLBACK); + + // read out meta information from the download if available + int modID = 0; + QString version = ""; + QString newestVersion = ""; + int categoryID = 0; + QString repository = "Nexus"; + + QString metaName = fileName + ".meta"; + if (QFile(metaName).exists()) { + QSettings metaFile(metaName, QSettings::IniFormat); + 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(); + unsigned int categoryIndex = CategoryFactory::instance().resolveNexusID( + metaFile.value("category", 0).toInt()); + categoryID = CategoryFactory::instance().getCategoryID(categoryIndex); + repository = metaFile.value("repository", "").toString(); + } + + if (version.isEmpty()) { + QDateTime lastMod = fileInfo.lastModified(); + version = "d" + lastMod.toString("yyyy.M.d"); + } + + { // 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); + if ((modID == 0) && (guessedModID != -1)) { + modID = guessedModID; + } else if (modID != guessedModID) { + qDebug("passed mod id: %d, guessed id: %d", modID, guessedModID); + } + + modName.update(guessedModName, GUESS_GOOD); + } + + m_CurrentFile = fileInfo.absoluteFilePath(); + if (fileInfo.dir() == QDir(m_DownloadsDirectory)) { + m_CurrentFile = fileInfo.fileName(); + } + qDebug("using mod name \"%s\" (id %d) -> %s", modName->toUtf8().constData(), modID, qPrintable(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. + m_ArchiveHandler->close(); + + // open the archive and construct the directory tree the installers work on + bool archiveOpen = m_ArchiveHandler->open(fileName, + new MethodCallback(this, &InstallationManager::queryPassword)); + if (!archiveOpen) { + qDebug("integrated archiver can't open %s: %s (%d)", + qPrintable(fileName), + qPrintable(getErrorString(m_ArchiveHandler->getLastError())), + m_ArchiveHandler->getLastError()); + } + ON_BLOCK_EXIT(std::bind(&InstallationManager::postInstallCleanup, this)); + + QScopedPointer filesTree(archiveOpen ? createFilesTree() : nullptr); + IPluginInstaller::EInstallResult installResult = IPluginInstaller::RESULT_NOTATTEMPTED; + + std::sort(m_Installers.begin(), m_Installers.end(), [] (IPluginInstaller *LHS, IPluginInstaller *RHS) { + return LHS->priority() > RHS->priority(); + }); + + for (IPluginInstaller *installer : m_Installers) { + // don't use inactive installers (installer can't be null here but vc static code analysis thinks it could) + if ((installer == nullptr) || !installer->isActive()) { + continue; + } + + // try only manual installers if that was requested + if (installResult == IPluginInstaller::RESULT_MANUALREQUESTED) { + if (!installer->isManualInstaller()) { + continue; + } + } else if (installResult != IPluginInstaller::RESULT_NOTATTEMPTED) { + break; + } + + try { + { // simple case + IPluginInstallerSimple *installerSimple + = dynamic_cast(installer); + if ((installerSimple != nullptr) && (filesTree != nullptr) + && (installer->isArchiveSupported(*filesTree))) { + installResult + = installerSimple->install(modName, *filesTree, version, modID); + if (installResult == IPluginInstaller::RESULT_SUCCESS) { + mapToArchive(filesTree.data()); + // the simple installer only prepares the installation, the rest + // works the same for all installers + if (!doInstall(modName, modID, version, newestVersion, categoryID, + repository)) { + installResult = IPluginInstaller::RESULT_FAILED; } - } else if (installResult != IPluginInstaller::RESULT_NOTATTEMPTED) { - break; + } } - - try { - { // simple case - IPluginInstallerSimple* installerSimple = dynamic_cast(installer); - if ((installerSimple != nullptr) && (filesTree != nullptr) && - (installer->isArchiveSupported(*filesTree))) { - installResult = installerSimple->install(modName, *filesTree, version, modID); - if (installResult == IPluginInstaller::RESULT_SUCCESS) { - mapToArchive(filesTree.data()); - // the simple installer only prepares the installation, the rest - // works the same for all installers - if (!doInstall(modName, modID, version, newestVersion, categoryID, repository)) { - installResult = IPluginInstaller::RESULT_FAILED; - } - } - } - } - - { // 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 = installerCustom->install(modName, fileName, version, modID); - unsigned int idx = ModInfo::getIndex(modName); - if (idx != UINT_MAX) { - ModInfo::Ptr info = ModInfo::getByIndex(idx); - info->setRepository(repository); - } - } - } + } + + { // 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 + = installerCustom->install(modName, fileName, version, modID); + unsigned int idx = ModInfo::getIndex(modName); + if (idx != UINT_MAX) { + ModInfo::Ptr info = ModInfo::getByIndex(idx); + info->setRepository(repository); } - } catch (const IncompatibilityException& e) { - qCritical("plugin \"%s\" incompatible: %s", qPrintable(installer->name()), e.what()); + } } + } + } catch (const IncompatibilityException &e) { + qCritical("plugin \"%s\" incompatible: %s", + qPrintable(installer->name()), e.what()); + } - // act upon the installation result. at this point the files have already been - // extracted to the correct location - switch (installResult) { - case IPluginInstaller::RESULT_CANCELED: - case IPluginInstaller::RESULT_FAILED: { - return false; - } break; - case IPluginInstaller::RESULT_SUCCESS: - case IPluginInstaller::RESULT_SUCCESSCANCEL: { - if (filesTree != nullptr) { - DirectoryTree::node_iterator iniTweakNode = filesTree->nodeFind(DirectoryTreeInformation("INI Tweaks")); - hasIniTweaks = (iniTweakNode != filesTree->nodesEnd()) && ((*iniTweakNode)->numLeafs() != 0); - return true; - } else { - return false; - } - } break; + // act upon the installation result. at this point the files have already been + // extracted to the correct location + switch (installResult) { + case IPluginInstaller::RESULT_CANCELED: + case IPluginInstaller::RESULT_FAILED: { + return false; + } break; + case IPluginInstaller::RESULT_SUCCESS: + case IPluginInstaller::RESULT_SUCCESSCANCEL: { + if (filesTree != nullptr) { + DirectoryTree::node_iterator iniTweakNode = filesTree->nodeFind(DirectoryTreeInformation("INI Tweaks")); + hasIniTweaks = (iniTweakNode != filesTree->nodesEnd()) && + ((*iniTweakNode)->numLeafs() != 0); + return true; + } else { + return false; } + } break; } + } - reportError(tr("None of the available installer plugins were able to handle that archive")); - return false; + reportError(tr("None of the available installer plugins were able to handle that archive")); + return false; } -QString InstallationManager::getErrorString(Archive::Error errorCode) { - switch (errorCode) { + + +QString InstallationManager::getErrorString(Archive::Error errorCode) +{ + switch (errorCode) { case Archive::ERROR_NONE: { - return tr("no error"); + return tr("no error"); } break; case Archive::ERROR_LIBRARY_NOT_FOUND: { - return tr("7z.dll not found"); + return tr("7z.dll not found"); } break; case Archive::ERROR_LIBRARY_INVALID: { - return tr("7z.dll isn't valid"); + return tr("7z.dll isn't valid"); } break; case Archive::ERROR_ARCHIVE_NOT_FOUND: { - return tr("archive not found"); + return tr("archive not found"); } break; case Archive::ERROR_FAILED_TO_OPEN_ARCHIVE: { - return tr("failed to open archive"); + return tr("failed to open archive"); } break; case Archive::ERROR_INVALID_ARCHIVE_FORMAT: { - return tr("unsupported archive type"); + return tr("unsupported archive type"); } break; case Archive::ERROR_LIBRARY_ERROR: { - return tr("internal library error"); + return tr("internal library error"); } break; case Archive::ERROR_ARCHIVE_INVALID: { - return tr("archive invalid"); + return tr("archive invalid"); } break; default: { - // this probably means the archiver.dll is newer than this - return tr("unknown archive error"); + // this probably means the archiver.dll is newer than this + return tr("unknown archive error"); } break; - } + } } -void InstallationManager::registerInstaller(IPluginInstaller* installer) { - m_Installers.push_back(installer); - installer->setInstallationManager(this); - IPluginInstallerCustom* installerCustom = dynamic_cast(installer); - if (installerCustom != nullptr) { - std::set extensions = installerCustom->supportedExtensions(); - m_SupportedExtensions.insert(extensions.begin(), extensions.end()); - } + +void InstallationManager::registerInstaller(IPluginInstaller *installer) +{ + m_Installers.push_back(installer); + installer->setInstallationManager(this); + IPluginInstallerCustom *installerCustom = dynamic_cast(installer); + if (installerCustom != nullptr) { + std::set extensions = installerCustom->supportedExtensions(); + m_SupportedExtensions.insert(extensions.begin(), extensions.end()); + } } -QStringList InstallationManager::getSupportedExtensions() const { - QStringList result; - foreach (const QString& extension, m_SupportedExtensions) { result.append(extension); } - return result; +QStringList InstallationManager::getSupportedExtensions() const +{ + QStringList result; + foreach (const QString &extension, m_SupportedExtensions) { + result.append(extension); + } + return result; } diff --git a/src/installationmanager.h b/src/installationmanager.h index fdb056fa..e1ebb519 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -20,193 +20,198 @@ along with Mod Organizer. If not, see . #ifndef INSTALLATIONMANAGER_H #define INSTALLATIONMANAGER_H -#include + #include #include +#include #include #define WIN32_LEAN_AND_MEAN -#include #include #include -#include +#include #include +#include + /** * @brief manages the installation of mod archives * This currently supports two special kind of archives: - * - "simple" archives: properly packaged archives without options, so they can be extracted to the (virtual) data - *directory directly + * - "simple" archives: properly packaged archives without options, so they can be extracted to the (virtual) data directory directly * - "complex" bain archives: archives with options for the bain system. * All other archives are managed through the manual "InstallDialog" * @todo this may be a good place to support plugins **/ -class InstallationManager : public QObject, public MOBase::IInstallationManager { - Q_OBJECT +class InstallationManager : public QObject, public MOBase::IInstallationManager +{ + Q_OBJECT public: - /** - * @brief constructor - * - * @param parent parent object. - **/ - explicit InstallationManager(); - - virtual ~InstallationManager(); - - void setParentWidget(QWidget* widget); - - void setURL(const QString& url); - - /** - * @brief update the directory where mods are to be installed - * @param modsDirectory the mod directory - * @note this is called a lot, probably redundantly - */ - void setModsDirectory(const QString& modsDirectory) { m_ModsDirectory = modsDirectory; } - - /** - * @brief update the directory where downloads are stored - * @param downloadDirectory the download directory - */ - 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) - **/ - bool install(const QString& fileName, MOBase::GuessedValue& modName, bool& hasIniTweaks); - - /** - * @return true if the installation was canceled - **/ - bool wasCancelled(); - - /** - * @brief retrieve a string describing the specified error code - * - * @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 - **/ - static QString getErrorString(Archive::Error errorCode); - - /** - * @brief register an installer-plugin - * @param the installer to register - */ - void registerInstaller(MOBase::IPluginInstaller* installer); - - /** - * @return list of file extensions we can install - */ - QStringList getSupportedExtensions() const; - - /** - * @brief extract the specified file from the currently open archive to a temporary location - * @param (relative) name of the file within the archive - * @return the absolute name of the temporary file - * @note the call will fail with an exception if no archive is open (plugins deriving - * from IPluginInstallerSimple can rely on that, custom installers shouldn't) - * @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" - */ - virtual QString extractFile(const QString& fileName); - - /** - * @brief extract the specified files from the currently open archive to a temporary location - * @param (relative) names of files within the archive - * @return the absolute names of the temporary files - * @note the call will fail with an exception if no archive is open (plugins deriving - * from IPluginInstallerSimple can rely on that, custom installers shouldn't) - * @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" - */ - virtual QStringList extractFiles(const QStringList& files, bool flatten); - - /** - * @brief installs an archive - * @param modName suggested name of the mod - * @param archiveFile path to the archive to install - * @return the installation result - */ - virtual MOBase::IPluginInstaller::EInstallResult installArchive(MOBase::GuessedValue& modName, - const QString& archiveName); - - /** - * @brief test if the specified mod name is free. If not, query the user how to proceed - * @param modName current possible names for the mod - * @param merge if this value is not null, the value will be set to whether the use chose to merge or replace - * @return true if we can proceed with the installation, false if the user canceled or in case of an unrecoverable - * error - */ - virtual bool testOverwrite(MOBase::GuessedValue& modName, bool* merge = nullptr) const; + + /** + * @brief constructor + * + * @param parent parent object. + **/ + explicit InstallationManager(); + + virtual ~InstallationManager(); + + void setParentWidget(QWidget *widget); + + void setURL(const QString &url); + + /** + * @brief update the directory where mods are to be installed + * @param modsDirectory the mod directory + * @note this is called a lot, probably redundantly + */ + void setModsDirectory(const QString &modsDirectory) { m_ModsDirectory = modsDirectory; } + + /** + * @brief update the directory where downloads are stored + * @param downloadDirectory the download directory + */ + 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) + **/ + bool install(const QString &fileName, MOBase::GuessedValue &modName, bool &hasIniTweaks); + + /** + * @return true if the installation was canceled + **/ + bool wasCancelled(); + + /** + * @brief retrieve a string describing the specified error code + * + * @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 + **/ + static QString getErrorString(Archive::Error errorCode); + + /** + * @brief register an installer-plugin + * @param the installer to register + */ + void registerInstaller(MOBase::IPluginInstaller *installer); + + /** + * @return list of file extensions we can install + */ + QStringList getSupportedExtensions() const; + + /** + * @brief extract the specified file from the currently open archive to a temporary location + * @param (relative) name of the file within the archive + * @return the absolute name of the temporary file + * @note the call will fail with an exception if no archive is open (plugins deriving + * from IPluginInstallerSimple can rely on that, custom installers shouldn't) + * @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" + */ + virtual QString extractFile(const QString &fileName); + + /** + * @brief extract the specified files from the currently open archive to a temporary location + * @param (relative) names of files within the archive + * @return the absolute names of the temporary files + * @note the call will fail with an exception if no archive is open (plugins deriving + * from IPluginInstallerSimple can rely on that, custom installers shouldn't) + * @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" + */ + virtual QStringList extractFiles(const QStringList &files, bool flatten); + + /** + * @brief installs an archive + * @param modName suggested name of the mod + * @param archiveFile path to the archive to install + * @return the installation result + */ + virtual MOBase::IPluginInstaller::EInstallResult installArchive(MOBase::GuessedValue &modName, const QString &archiveName); + + /** + * @brief test if the specified mod name is free. If not, query the user how to proceed + * @param modName current possible names for the mod + * @param merge if this value is not null, the value will be set to whether the use chose to merge or replace + * @return true if we can proceed with the installation, false if the user canceled or in case of an unrecoverable error + */ + virtual bool testOverwrite(MOBase::GuessedValue &modName, bool *merge = nullptr) const; private: - void queryPassword(QString* password); - void updateProgress(float percentage); - void updateProgressFile(const QString& fileName); - void report7ZipError(const QString& errorMessage); - MOBase::DirectoryTree* createFilesTree(); + void queryPassword(QString *password); + void updateProgress(float percentage); + void updateProgressFile(const QString &fileName); + void report7ZipError(const QString &errorMessage); + + MOBase::DirectoryTree *createFilesTree(); - // remap all files in the archive to the directory structure represented by baseNode - // files not present in baseNode are disabled - void mapToArchive(const MOBase::DirectoryTree::Node* baseNode); + // remap all files in the archive to the directory structure represented by baseNode + // files not present in baseNode are disabled + void mapToArchive(const MOBase::DirectoryTree::Node *baseNode); - // recursive worker function for mapToArchive - void mapToArchive(const MOBase::DirectoryTree::Node* node, QString path, FileData* const* data); - bool unpackSingleFile(const QString& fileName); + // recursive worker function for mapToArchive + void mapToArchive(const MOBase::DirectoryTree::Node *node, QString path, FileData * const *data); + bool unpackSingleFile(const QString &fileName); - bool isSimpleArchiveTopLayer(const MOBase::DirectoryTree::Node* node, bool bainStyle); - MOBase::DirectoryTree::Node* getSimpleArchiveBase(MOBase::DirectoryTree* dataTree); - bool doInstall(MOBase::GuessedValue& modName, int modID, const QString& version, - const QString& newestVersion, int categoryID, const QString& repository); + bool isSimpleArchiveTopLayer(const MOBase::DirectoryTree::Node *node, bool bainStyle); + MOBase::DirectoryTree::Node *getSimpleArchiveBase(MOBase::DirectoryTree *dataTree); - QString generateBackupName(const QString& directoryName) const; + bool doInstall(MOBase::GuessedValue &modName, + int modID, const QString &version, const QString &newestVersion, int categoryID, const QString &repository); - bool ensureValidModName(MOBase::GuessedValue& name) const; + QString generateBackupName(const QString &directoryName) const; - void postInstallCleanup(); + bool ensureValidModName(MOBase::GuessedValue &name) const; + + void postInstallCleanup(); private: - struct ByPriority { - bool operator()(MOBase::IPluginInstaller* LHS, MOBase::IPluginInstaller* RHS) const { - return LHS->priority() > RHS->priority(); - } - }; - - struct CaseInsensitive { - bool operator()(const QString& LHS, const QString& RHS) const { - return QString::compare(LHS, RHS, Qt::CaseInsensitive) < 0; - } - }; + + struct ByPriority { + bool operator()(MOBase::IPluginInstaller *LHS, MOBase::IPluginInstaller *RHS) const + { + return LHS->priority() > RHS->priority(); + } + }; + + struct CaseInsensitive { + bool operator() (const QString &LHS, const QString &RHS) const + { + return QString::compare(LHS, RHS, Qt::CaseInsensitive) < 0; + } + }; private: - QWidget* m_ParentWidget; - QString m_ModsDirectory; - QString m_DownloadsDirectory; + QWidget *m_ParentWidget; + + QString m_ModsDirectory; + QString m_DownloadsDirectory; - std::vector m_Installers; - std::set m_SupportedExtensions; + std::vector m_Installers; + std::set m_SupportedExtensions; - Archive* m_ArchiveHandler; - QString m_CurrentFile; + Archive *m_ArchiveHandler; + QString m_CurrentFile; - QProgressDialog* m_InstallationProgress{nullptr}; + QProgressDialog *m_InstallationProgress { nullptr }; - std::set m_TempFilesToDelete; + std::set m_TempFilesToDelete; - QString m_URL; + QString m_URL; }; + #endif // INSTALLATIONMANAGER_H diff --git a/src/instancemanager.cpp b/src/instancemanager.cpp index d1232809..29068c5b 100644 --- a/src/instancemanager.cpp +++ b/src/instancemanager.cpp @@ -17,244 +17,282 @@ 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 +#include #include #include +#include #include #include -#include -#include #include -#include -static const char COMPANY_NAME[] = "Tannin"; + +static const char COMPANY_NAME[] = "Tannin"; static const char APPLICATION_NAME[] = "Mod Organizer"; -static const char INSTANCE_KEY[] = "CurrentInstance"; +static const char INSTANCE_KEY[] = "CurrentInstance"; -InstanceManager::InstanceManager() : m_AppSettings(COMPANY_NAME, APPLICATION_NAME) {} -InstanceManager& InstanceManager::instance() { - static InstanceManager s_Instance; - return s_Instance; -} -void InstanceManager::overrideInstance(const QString& instanceName) { - m_overrideInstanceName = instanceName; - m_overrideInstance = true; +InstanceManager::InstanceManager() + : m_AppSettings(COMPANY_NAME, APPLICATION_NAME) +{ } -QString InstanceManager::currentInstance() const { - if (m_overrideInstance) - return m_overrideInstanceName; - else - return m_AppSettings.value(INSTANCE_KEY, "").toString(); +InstanceManager &InstanceManager::instance() +{ + static InstanceManager s_Instance; + return s_Instance; } -void InstanceManager::clearCurrentInstance() { - setCurrentInstance(""); - m_Reset = true; +void InstanceManager::overrideInstance(const QString& instanceName) +{ + m_overrideInstanceName = instanceName; + m_overrideInstance = true; } -void InstanceManager::setCurrentInstance(const QString& name) { m_AppSettings.setValue(INSTANCE_KEY, name); } -bool InstanceManager::deleteLocalInstance(const QString& instanceId) const { - bool result = true; - QString instancePath = - QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + instanceId); - - if (QMessageBox::warning( - nullptr, QObject::tr("Deleting folder"), - QObject::tr("I'm about to delete the following folder: \"%1\". Proceed?").arg(instancePath), - QMessageBox::No | QMessageBox::Yes, QMessageBox::No) == QMessageBox::No) { - return false; - } +QString InstanceManager::currentInstance() const +{ + if (m_overrideInstance) + return m_overrideInstanceName; + else + return m_AppSettings.value(INSTANCE_KEY, "").toString(); +} - if (!MOBase::shellDelete(QStringList(instancePath), true)) { - qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qPrintable(instancePath), - ::GetLastError()); - if (!MOBase::removeDir(instancePath)) { - qWarning("regular delete failed too"); - result = false; - } - } +void InstanceManager::clearCurrentInstance() +{ + setCurrentInstance(""); + m_Reset = true; +} - return result; +void InstanceManager::setCurrentInstance(const QString &name) +{ + m_AppSettings.setValue(INSTANCE_KEY, name); } -QString InstanceManager::manageInstances(const QStringList& instanceList) const { - SelectionDialog selection( - QString("

    %1


    %2") - .arg(QObject::tr("Choose Instance to Delete")) - .arg(QObject::tr("Be Carefull! Deleting an Instance will remove all your files for that Instance (mods, " - "downloads, profiles, configuration, ...). Custom paths outside of the instance folder " - "for downloads, mods, etc. will be left untoched.")), - nullptr); - for (const QString& instance : instanceList) { - selection.addChoice(instance, "", instance); +bool InstanceManager::deleteLocalInstance(const QString &instanceId) const +{ + bool result = true; + QString instancePath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + instanceId); + + if (QMessageBox::warning(nullptr, QObject::tr("Deleting folder"), + QObject::tr("I'm about to delete the following folder: \"%1\". Proceed?").arg(instancePath), QMessageBox::No | QMessageBox::Yes, QMessageBox::No) == QMessageBox::No ){ + return false; + } + + if (!MOBase::shellDelete(QStringList(instancePath),true)) + { + qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qPrintable(instancePath), ::GetLastError()); + if (!MOBase::removeDir(instancePath)) + { + qWarning("regular delete failed too"); + result = false; } + } - if (selection.exec() == QDialog::Rejected) { - return (chooseInstance(instances())); - } else { - QString choice = selection.getChoiceData().toString(); - { - if (QMessageBox::warning( - nullptr, QObject::tr("Are you sure?"), - QObject::tr("Are you really sure you want to delete the Instance \"%1\" with all its files?") - .arg(choice), - QMessageBox::No | QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) { - if (!deleteLocalInstance(choice)) { - QMessageBox::warning(nullptr, QObject::tr("Failed to delete Instance"), - QObject::tr("Could not delete Instance \"%1\"").arg(choice), QMessageBox::Ok); - } - } - } - } - return (manageInstances(instances())); + return result; } -QString InstanceManager::queryInstanceName(const QStringList& instanceList) const { - QString instanceId; - while (instanceId.isEmpty()) { - QInputDialog dialog; - - dialog.setWindowTitle(QObject::tr("Enter a Name for the new Instance")); - dialog.setLabelText( - QObject::tr("Enter a new name or select one from the sugested list (only letters and numbers allowed):")); - // would be neat if we could take the names from the game plugins but - // the required initialization order requires the ini file to be - // available *before* we load plugins - dialog.setComboBoxItems( - {"NewName", "Fallout 4", "SkyrimSE", "Skyrim", "Fallout 3", "Fallout NV", "FO4VR", "Oblivion"}); - dialog.setComboBoxEditable(true); - - if (dialog.exec() == QDialog::Rejected) { - throw MOBase::MyException(QObject::tr("Canceled")); - } - instanceId = dialog.textValue().replace(QRegExp("[^0-9a-zA-Z ]"), ""); - - bool alreadyExists = false; - for (const QString& instance : instanceList) { - if (instanceId == instance) - alreadyExists = true; - } - if (alreadyExists) { - QMessageBox msgBox; - msgBox.setText(QObject::tr("The instance \"%1\" already exists.").arg(instanceId)); - msgBox.setInformativeText( - QObject::tr("Please choose a different instance name, like: \"%1 1\" .").arg(instanceId)); - msgBox.exec(); - instanceId = ""; - } - } - return instanceId; +QString InstanceManager::manageInstances(const QStringList &instanceList) const +{ + SelectionDialog selection( + QString("

    %1


    %2") + .arg(QObject::tr("Choose Instance to Delete")) + .arg(QObject::tr("Be Carefull! Deleting an Instance will remove all your files for that Instance (mods, downloads, profiles, configuration, ...). Custom paths outside of the instance folder for downloads, mods, etc. will be left untoched.")), + nullptr); + for (const QString &instance : instanceList) + { + selection.addChoice(instance, "", instance); + } + + if (selection.exec() == QDialog::Rejected) { + return(chooseInstance(instances())); + } + else { + QString choice = selection.getChoiceData().toString(); + { + if (QMessageBox::warning(nullptr, QObject::tr("Are you sure?"), + QObject::tr("Are you really sure you want to delete the Instance \"%1\" with all its files?").arg(choice), QMessageBox::No | QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) + { + if (!deleteLocalInstance(choice)) + { + QMessageBox::warning(nullptr, QObject::tr("Failed to delete Instance"), + QObject::tr("Could not delete Instance \"%1\"").arg(choice), QMessageBox::Ok); + } + } + } + } + return(manageInstances(instances())); } -QString InstanceManager::chooseInstance(const QStringList& instanceList) const { - enum class Special : uint8_t { NewInstance, Portable, Manage }; - - SelectionDialog selection( - QString("

    %1


    %2") - .arg(QObject::tr("Choose Instance")) - .arg(QObject::tr("Each Instance is a full set of MO data files (mods, " - "downloads, profiles, configuration, ...). You can use multiple " - "instances for different games. Instances are stored in Appdata and can be accessed by " - "all MO installations. " - "If your MO folder is writable, you can also store a single instance locally (called " - "a Portable install, and all the MO data files will be inside the installation folder).")), - nullptr); - selection.disableCancel(); - for (const QString& instance : instanceList) { - selection.addChoice(instance, "", instance); +QString InstanceManager::queryInstanceName(const QStringList &instanceList) const +{ + QString instanceId; + while (instanceId.isEmpty()) { + QInputDialog dialog; + + dialog.setWindowTitle(QObject::tr("Enter a Name for the new Instance")); + dialog.setLabelText(QObject::tr("Enter a new name or select one from the sugested list (only letters and numbers allowed):")); + // would be neat if we could take the names from the game plugins but + // the required initialization order requires the ini file to be + // available *before* we load plugins + dialog.setComboBoxItems({ "NewName", "Fallout 4", "SkyrimSE", "Skyrim", "Fallout 3", + "Fallout NV", "FO4VR", "Oblivion" }); + dialog.setComboBoxEditable(true); + + if (dialog.exec() == QDialog::Rejected) { + throw MOBase::MyException(QObject::tr("Canceled")); } + instanceId = dialog.textValue().replace(QRegExp("[^0-9a-zA-Z ]"), ""); - selection.addChoice(QIcon(":/MO/gui/add"), QObject::tr("New"), QObject::tr("Create a new instance."), - static_cast(Special::NewInstance)); - - if (QFileInfo(qApp->applicationDirPath()).isWritable()) { - selection.addChoice(QIcon(":/MO/gui/package"), QObject::tr("Portable"), QObject::tr("Use MO folder for data."), - static_cast(Special::Portable)); + bool alreadyExists=false; + for (const QString &instance : instanceList) { + if(instanceId==instance) + alreadyExists=true; } + if(alreadyExists) + { + QMessageBox msgBox; + msgBox.setText( QObject::tr("The instance \"%1\" already exists.").arg(instanceId) ); + msgBox.setInformativeText(QObject::tr("Please choose a different instance name, like: \"%1 1\" .").arg(instanceId)); + msgBox.exec(); + instanceId=""; + } + } + return instanceId; +} + - selection.addChoice(QIcon(":/MO/gui/remove"), QObject::tr("Manage Instances"), QObject::tr("Delete an Instance."), +QString InstanceManager::chooseInstance(const QStringList &instanceList) const +{ + enum class Special : uint8_t { + NewInstance, + Portable, + Manage + }; + + SelectionDialog selection( + QString("

    %1


    %2") + .arg(QObject::tr("Choose Instance")) + .arg(QObject::tr( + "Each Instance is a full set of MO data files (mods, " + "downloads, profiles, configuration, ...). You can use multiple " + "instances for different games. Instances are stored in Appdata and can be accessed by all MO installations. " + "If your MO folder is writable, you can also store a single instance locally (called " + "a Portable install, and all the MO data files will be inside the installation folder).")), + nullptr); + selection.disableCancel(); + for (const QString &instance : instanceList) { + selection.addChoice(instance, "", instance); + } + + selection.addChoice(QIcon(":/MO/gui/add"), QObject::tr("New"), + QObject::tr("Create a new instance."), + static_cast(Special::NewInstance)); + + if (QFileInfo(qApp->applicationDirPath()).isWritable()) { + selection.addChoice(QIcon(":/MO/gui/package"), QObject::tr("Portable"), + QObject::tr("Use MO folder for data."), + static_cast(Special::Portable)); + } + + selection.addChoice(QIcon(":/MO/gui/remove"), QObject::tr("Manage Instances"), + QObject::tr("Delete an Instance."), static_cast(Special::Manage)); - if (selection.exec() == QDialog::Rejected) { - qDebug("rejected"); - throw MOBase::MyException(QObject::tr("Canceled")); - } + if (selection.exec() == QDialog::Rejected) { + qDebug("rejected"); + throw MOBase::MyException(QObject::tr("Canceled")); + } - QVariant choice = selection.getChoiceData(); + QVariant choice = selection.getChoiceData(); - if (choice.type() == QVariant::String) { - return choice.toString(); - } else { - switch (choice.value()) { - case Special::NewInstance: - return queryInstanceName(instanceList); - case Special::Portable: - return QString(); - case Special::Manage: { - - return (manageInstances(instances())); - } - default: - throw std::runtime_error("invalid selection"); - } + if (choice.type() == QVariant::String) { + return choice.toString(); + } else { + switch (choice.value()) { + case Special::NewInstance: return queryInstanceName(instanceList); + case Special::Portable: return QString(); + case Special::Manage: { + + return(manageInstances(instances())); + } + default: throw std::runtime_error("invalid selection"); } + } } -QString InstanceManager::instancePath() const { - return QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation)); + +QString InstanceManager::instancePath() const +{ + return QDir::fromNativeSeparators( + QStandardPaths::writableLocation(QStandardPaths::DataLocation)); } -QStringList InstanceManager::instances() const { - return QDir(instancePath()).entryList(QDir::Dirs | QDir::NoDotAndDotDot); + +QStringList InstanceManager::instances() const +{ + return QDir(instancePath()).entryList(QDir::Dirs | QDir::NoDotAndDotDot); } -bool InstanceManager::portableInstall() const { - return QFile::exists(qApp->applicationDirPath() + "/" + QString::fromStdWString(AppConfig::iniFileName())); + +bool InstanceManager::portableInstall() const +{ + return QFile::exists(qApp->applicationDirPath() + "/" + + QString::fromStdWString(AppConfig::iniFileName())); } -void InstanceManager::createDataPath(const QString& dataPath) const { - if (!QDir(dataPath).exists()) { - if (!QDir().mkpath(dataPath)) { - throw MOBase::MyException(QObject::tr("failed to create %1").arg(dataPath)); - } else { - QMessageBox::information(nullptr, QObject::tr("Data directory created"), - QObject::tr("New data directory created at %1. If you don't want to " - "store a lot of data there, reconfigure the storage " - "directories via settings.") - .arg(dataPath)); - } + +void InstanceManager::createDataPath(const QString &dataPath) const +{ + if (!QDir(dataPath).exists()) { + if (!QDir().mkpath(dataPath)) { + throw MOBase::MyException( + QObject::tr("failed to create %1").arg(dataPath)); + } else { + QMessageBox::information( + nullptr, QObject::tr("Data directory created"), + QObject::tr("New data directory created at %1. If you don't want to " + "store a lot of data there, reconfigure the storage " + "directories via settings.").arg(dataPath)); } + } } -QString InstanceManager::determineDataPath() { - QString instanceId = currentInstance(); - if (instanceId.isEmpty() && !m_Reset && (m_overrideInstance || portableInstall())) { - // startup, apparently using portable mode before - return qApp->applicationDirPath(); - } - QString dataPath = - QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + instanceId); +QString InstanceManager::determineDataPath() +{ + QString instanceId = currentInstance(); + if (instanceId.isEmpty() && !m_Reset && (m_overrideInstance || portableInstall())) + { + // startup, apparently using portable mode before + return qApp->applicationDirPath(); + } - if (!m_overrideInstance && (instanceId.isEmpty() || !QFileInfo::exists(dataPath))) { - instanceId = chooseInstance(instances()); - setCurrentInstance(instanceId); - if (!instanceId.isEmpty()) { - dataPath = QDir::fromNativeSeparators(QStandardPaths::writableLocation(QStandardPaths::DataLocation) + "/" + - instanceId); - } - } + QString dataPath = QDir::fromNativeSeparators( + QStandardPaths::writableLocation(QStandardPaths::DataLocation) + + "/" + instanceId); - if (instanceId.isEmpty()) { - return qApp->applicationDirPath(); - } else { - createDataPath(dataPath); - return dataPath; + if (!m_overrideInstance && (instanceId.isEmpty() || !QFileInfo::exists(dataPath))) { + instanceId = chooseInstance(instances()); + setCurrentInstance(instanceId); + if (!instanceId.isEmpty()) { + dataPath = QDir::fromNativeSeparators( + QStandardPaths::writableLocation(QStandardPaths::DataLocation) + + "/" + instanceId); } + } + + if (instanceId.isEmpty()) { + return qApp->applicationDirPath(); + } else { + createDataPath(dataPath); + + return dataPath; + } } + diff --git a/src/instancemanager.h b/src/instancemanager.h index 9222872f..adedd78f 100644 --- a/src/instancemanager.h +++ b/src/instancemanager.h @@ -17,45 +17,51 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ + #pragma once -#include + #include +#include + class InstanceManager { public: - static InstanceManager& instance(); - QString determineDataPath(); - void clearCurrentInstance(); + static InstanceManager &instance(); + + QString determineDataPath(); + void clearCurrentInstance(); - void overrideInstance(const QString& instanceName); + void overrideInstance(const QString& instanceName); - QString currentInstance() const; + QString currentInstance() const; private: - InstanceManager(); - QString instancePath() const; + InstanceManager(); - QStringList instances() const; + QString instancePath() const; - bool deleteLocalInstance(const QString& instanceId) const; + QStringList instances() const; - QString manageInstances(const QStringList& instanceList) const; + bool deleteLocalInstance(const QString &instanceId) const; - void setCurrentInstance(const QString& name); + QString manageInstances(const QStringList &instanceList) const; - QString queryInstanceName(const QStringList& instanceList) const; - QString chooseInstance(const QStringList& instanceList) const; + void setCurrentInstance(const QString &name); - void createDataPath(const QString& dataPath) const; - bool portableInstall() const; + QString queryInstanceName(const QStringList &instanceList) const; + QString chooseInstance(const QStringList &instanceList) const; + + void createDataPath(const QString &dataPath) const; + bool portableInstall() const; private: - QSettings m_AppSettings; - bool m_Reset{false}; - bool m_overrideInstance{false}; - QString m_overrideInstanceName; + + QSettings m_AppSettings; + bool m_Reset {false}; + bool m_overrideInstance{false}; + QString m_overrideInstanceName; }; diff --git a/src/iuserinterface.h b/src/iuserinterface.h index 800d8b10..7ce71c24 100644 --- a/src/iuserinterface.h +++ b/src/iuserinterface.h @@ -1,36 +1,39 @@ #ifndef IUSERINTERFACE_H #define IUSERINTERFACE_H -#include "ilockedwaitingforprocess.h" + #include "modinfo.h" -#include -#include +#include "ilockedwaitingforprocess.h" #include +#include +#include class QSettings; -class IUserInterface { +class IUserInterface +{ public: - virtual void storeSettings(QSettings& settings) = 0; - virtual void registerPluginTool(MOBase::IPluginTool* tool) = 0; - virtual void registerModPage(MOBase::IPluginModPage* modPage) = 0; + virtual void storeSettings(QSettings &settings) = 0; + + virtual void registerPluginTool(MOBase::IPluginTool *tool) = 0; + virtual void registerModPage(MOBase::IPluginModPage *modPage) = 0; - virtual void installTranslator(const QString& name) = 0; + virtual void installTranslator(const QString &name) = 0; - virtual void disconnectPlugins() = 0; + virtual void disconnectPlugins() = 0; - virtual bool closeWindow() = 0; - virtual void setWindowEnabled(bool enabled) = 0; + virtual bool closeWindow() = 0; + virtual void setWindowEnabled(bool enabled) = 0; - virtual void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab) = 0; + virtual void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab) = 0; - virtual void updateBSAList(const QStringList& defaultArchives, const QStringList& activeArchives) = 0; + virtual void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives) = 0; - virtual MOBase::DelayedFileWriterBase& archivesWriter() = 0; + virtual MOBase::DelayedFileWriterBase &archivesWriter() = 0; - virtual ILockedWaitingForProcess* lock() = 0; - virtual void unlock() = 0; + virtual ILockedWaitingForProcess* lock() = 0; + virtual void unlock() = 0; }; #endif // IUSERINTERFACE_H diff --git a/src/json.cpp b/src/json.cpp index 704b60d8..05a2665a 100644 --- a/src/json.cpp +++ b/src/json.cpp @@ -23,319 +23,317 @@ #include "json.h" namespace QtJson { -static QString sanitizeString(QString str); -static QByteArray join(const QList& list, const QByteArray& sep); -static QVariant parseValue(const QString& json, int& index, bool& success); -static QVariant parseObject(const QString& json, int& index, bool& success); -static QVariant parseArray(const QString& json, int& index, bool& success); -static QVariant parseString(const QString& json, int& index, bool& success); -static QVariant parseNumber(const QString& json, int& index); -static int lastIndexOfNumber(const QString& json, int index); -static void eatWhitespace(const QString& json, int& index); -static int lookAhead(const QString& json, int index); -static int nextToken(const QString& json, int& index); - -template -QByteArray serializeMap(const T& map, bool& success) { - QByteArray str = "{ "; - QList pairs; - for (typename T::const_iterator it = map.begin(), itend = map.end(); it != itend; ++it) { - QByteArray serializedValue = serialize(it.value()); - if (serializedValue.isNull()) { - success = false; - break; + static QString sanitizeString(QString str); + static QByteArray join(const QList &list, const QByteArray &sep); + static QVariant parseValue(const QString &json, int &index, bool &success); + static QVariant parseObject(const QString &json, int &index, bool &success); + static QVariant parseArray(const QString &json, int &index, bool &success); + static QVariant parseString(const QString &json, int &index, bool &success); + static QVariant parseNumber(const QString &json, int &index); + static int lastIndexOfNumber(const QString &json, int index); + static void eatWhitespace(const QString &json, int &index); + static int lookAhead(const QString &json, int index); + static int nextToken(const QString &json, int &index); + + template + QByteArray serializeMap(const T &map, bool &success) { + QByteArray str = "{ "; + QList pairs; + for (typename T::const_iterator it = map.begin(), itend = map.end(); it != itend; ++it) { + QByteArray serializedValue = serialize(it.value()); + if (serializedValue.isNull()) { + success = false; + break; + } + pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue; } - pairs << sanitizeString(it.key()).toUtf8() + " : " + serializedValue; + + str += join(pairs, ", "); + str += " }"; + return str; } - str += join(pairs, ", "); - str += " }"; - return str; -} -/** - * parse - */ -QVariant parse(const QString& json) { - bool success = true; - return parse(json, success); -} + /** + * parse + */ + QVariant parse(const QString &json) { + bool success = true; + return parse(json, success); + } -/** - * parse - */ -QVariant parse(const QString& json, bool& success) { - success = true; - - // Return an empty QVariant if the JSON data is either null or empty - if (!json.isNull() || !json.isEmpty()) { - QString data = json; - // We'll start from index 0 - int index = 0; - - // Parse the first value - QVariant value = parseValue(data, index, success); - - // Return the parsed value - return value; - } else { - // Return the empty QVariant - return QVariant(); + /** + * parse + */ + QVariant parse(const QString &json, bool &success) { + success = true; + + // Return an empty QVariant if the JSON data is either null or empty + if (!json.isNull() || !json.isEmpty()) { + QString data = json; + // We'll start from index 0 + int index = 0; + + // Parse the first value + QVariant value = parseValue(data, index, success); + + // Return the parsed value + return value; + } else { + // Return the empty QVariant + return QVariant(); + } } -} - -QByteArray serialize(const QVariant& data) { - bool success = true; - return serialize(data, success); -} - -QByteArray serialize(const QVariant& data, bool& success) { - QByteArray str; - success = true; - - if (!data.isValid()) { // invalid or null? - str = "null"; - } else if ((data.type() == QVariant::List) || (data.type() == QVariant::StringList)) { // variant is a list? - QList values; - const QVariantList list = data.toList(); - Q_FOREACH (const QVariant& v, list) { - QByteArray serializedValue = serialize(v); - if (serializedValue.isNull()) { - success = false; - break; + + QByteArray serialize(const QVariant &data) { + bool success = true; + return serialize(data, success); + } + + QByteArray serialize(const QVariant &data, bool &success) { + QByteArray str; + success = true; + + if (!data.isValid()) { // invalid or null? + str = "null"; + } else if ((data.type() == QVariant::List) || + (data.type() == QVariant::StringList)) { // variant is a list? + QList values; + const QVariantList list = data.toList(); + Q_FOREACH(const QVariant& v, list) { + QByteArray serializedValue = serialize(v); + if (serializedValue.isNull()) { + success = false; + break; + } + values << serializedValue; } - values << serializedValue; - } - str = "[ " + join(values, ", ") + " ]"; - } else if (data.type() == QVariant::Hash) { // variant is a hash? - str = serializeMap<>(data.toHash(), success); - } else if (data.type() == QVariant::Map) { // variant is a map? - str = serializeMap<>(data.toMap(), success); - } else if ((data.type() == QVariant::String) || (data.type() == QVariant::ByteArray)) { // a string or a byte array? - str = sanitizeString(data.toString()).toUtf8(); - } else if (data.type() == QVariant::Double) { // double? - double value = data.toDouble(); - if ((value - value) == 0.0) { - str = QByteArray::number(value, 'g'); - if (!str.contains(".") && !str.contains("e")) { - str += ".0"; + str = "[ " + join( values, ", " ) + " ]"; + } else if (data.type() == QVariant::Hash) { // variant is a hash? + str = serializeMap<>(data.toHash(), success); + } else if (data.type() == QVariant::Map) { // variant is a map? + str = serializeMap<>(data.toMap(), success); + } else if ((data.type() == QVariant::String) || + (data.type() == QVariant::ByteArray)) {// a string or a byte array? + str = sanitizeString(data.toString()).toUtf8(); + } else if (data.type() == QVariant::Double) { // double? + double value = data.toDouble(); + if ((value - value) == 0.0) { + str = QByteArray::number(value, 'g'); + if (!str.contains(".") && ! str.contains("e")) { + str += ".0"; + } + } else { + success = false; } + } else if (data.type() == QVariant::Bool) { // boolean value? + str = data.toBool() ? "true" : "false"; + } else if (data.type() == QVariant::ULongLong) { // large unsigned number? + str = QByteArray::number(data.value()); + } else if (data.canConvert()) { // any signed number? + str = QByteArray::number(data.value()); + } else if (data.canConvert()) { //TODO: this code is never executed + str = QString::number(data.value()).toUtf8(); + } else if (data.canConvert()) { // can value be converted to string? + // this will catch QDate, QDateTime, QUrl, ... + str = sanitizeString(data.toString()).toUtf8(); } else { success = false; } - } else if (data.type() == QVariant::Bool) { // boolean value? - str = data.toBool() ? "true" : "false"; - } else if (data.type() == QVariant::ULongLong) { // large unsigned number? - str = QByteArray::number(data.value()); - } else if (data.canConvert()) { // any signed number? - str = QByteArray::number(data.value()); - } else if (data.canConvert()) { // TODO: this code is never executed - str = QString::number(data.value()).toUtf8(); - } else if (data.canConvert()) { // can value be converted to string? - // this will catch QDate, QDateTime, QUrl, ... - str = sanitizeString(data.toString()).toUtf8(); - } else { - success = false; + + if (success) { + return str; + } else { + return QByteArray(); + } } - if (success) { - return str; - } else { - return QByteArray(); + QString serializeStr(const QVariant &data) { + return QString::fromUtf8(serialize(data)); } -} -QString serializeStr(const QVariant& data) { return QString::fromUtf8(serialize(data)); } + QString serializeStr(const QVariant &data, bool &success) { + return QString::fromUtf8(serialize(data, success)); + } -QString serializeStr(const QVariant& data, bool& success) { return QString::fromUtf8(serialize(data, success)); } -/** - * \enum JsonToken - */ -enum JsonToken { - JsonTokenNone = 0, - JsonTokenCurlyOpen = 1, - JsonTokenCurlyClose = 2, - JsonTokenSquaredOpen = 3, - JsonTokenSquaredClose = 4, - JsonTokenColon = 5, - JsonTokenComma = 6, - JsonTokenString = 7, - JsonTokenNumber = 8, - JsonTokenTrue = 9, - JsonTokenFalse = 10, - JsonTokenNull = 11 -}; - -static QString sanitizeString(QString str) { - str.replace(QLatin1String("\\"), QLatin1String("\\\\")); - str.replace(QLatin1String("\""), QLatin1String("\\\"")); - str.replace(QLatin1String("\b"), QLatin1String("\\b")); - str.replace(QLatin1String("\f"), QLatin1String("\\f")); - str.replace(QLatin1String("\n"), QLatin1String("\\n")); - str.replace(QLatin1String("\r"), QLatin1String("\\r")); - str.replace(QLatin1String("\t"), QLatin1String("\\t")); - return QString(QLatin1String("\"%1\"")).arg(str); -} - -static QByteArray join(const QList& list, const QByteArray& sep) { - QByteArray res; - Q_FOREACH (const QByteArray& i, list) { - if (!res.isEmpty()) { - res += sep; - } - res += i; + /** + * \enum JsonToken + */ + enum JsonToken { + JsonTokenNone = 0, + JsonTokenCurlyOpen = 1, + JsonTokenCurlyClose = 2, + JsonTokenSquaredOpen = 3, + JsonTokenSquaredClose = 4, + JsonTokenColon = 5, + JsonTokenComma = 6, + JsonTokenString = 7, + JsonTokenNumber = 8, + JsonTokenTrue = 9, + JsonTokenFalse = 10, + JsonTokenNull = 11 + }; + + static QString sanitizeString(QString str) { + str.replace(QLatin1String("\\"), QLatin1String("\\\\")); + str.replace(QLatin1String("\""), QLatin1String("\\\"")); + str.replace(QLatin1String("\b"), QLatin1String("\\b")); + str.replace(QLatin1String("\f"), QLatin1String("\\f")); + str.replace(QLatin1String("\n"), QLatin1String("\\n")); + str.replace(QLatin1String("\r"), QLatin1String("\\r")); + str.replace(QLatin1String("\t"), QLatin1String("\\t")); + return QString(QLatin1String("\"%1\"")).arg(str); } - return res; -} -/** - * parseValue - */ -static QVariant parseValue(const QString& json, int& index, bool& success) { - // Determine what kind of data we should parse by - // checking out the upcoming token - switch (lookAhead(json, index)) { - case JsonTokenString: - return parseString(json, index, success); - case JsonTokenNumber: - return parseNumber(json, index); - case JsonTokenCurlyOpen: - return parseObject(json, index, success); - case JsonTokenSquaredOpen: - return parseArray(json, index, success); - case JsonTokenTrue: - nextToken(json, index); - return QVariant(true); - case JsonTokenFalse: - nextToken(json, index); - return QVariant(false); - case JsonTokenNull: - nextToken(json, index); - return QVariant(); - case JsonTokenNone: - break; + static QByteArray join(const QList &list, const QByteArray &sep) { + QByteArray res; + Q_FOREACH(const QByteArray &i, list) { + if (!res.isEmpty()) { + res += sep; + } + res += i; + } + return res; } - // If there were no tokens, flag the failure and return an empty QVariant - success = false; - return QVariant(); -} + /** + * parseValue + */ + static QVariant parseValue(const QString &json, int &index, bool &success) { + // Determine what kind of data we should parse by + // checking out the upcoming token + switch(lookAhead(json, index)) { + case JsonTokenString: + return parseString(json, index, success); + case JsonTokenNumber: + return parseNumber(json, index); + case JsonTokenCurlyOpen: + return parseObject(json, index, success); + case JsonTokenSquaredOpen: + return parseArray(json, index, success); + case JsonTokenTrue: + nextToken(json, index); + return QVariant(true); + case JsonTokenFalse: + nextToken(json, index); + return QVariant(false); + case JsonTokenNull: + nextToken(json, index); + return QVariant(); + case JsonTokenNone: + break; + } -/** - * parseObject - */ -static QVariant parseObject(const QString& json, int& index, bool& success) { - QVariantMap map; - int token; + // If there were no tokens, flag the failure and return an empty QVariant + success = false; + return QVariant(); + } - // Get rid of the whitespace and increment index - nextToken(json, index); + /** + * parseObject + */ + static QVariant parseObject(const QString &json, int &index, bool &success) { + QVariantMap map; + int token; - // Loop through all of the key/value pairs of the object - bool done = false; - while (!done) { - // Get the upcoming token - token = lookAhead(json, index); + // Get rid of the whitespace and increment index + nextToken(json, index); - if (token == JsonTokenNone) { - success = false; - return QVariantMap(); - } else if (token == JsonTokenComma) { - nextToken(json, index); - } else if (token == JsonTokenCurlyClose) { - nextToken(json, index); - return map; - } else { - // Parse the key/value pair's name - QString name = parseString(json, index, success).toString(); + // Loop through all of the key/value pairs of the object + bool done = false; + while (!done) { + // Get the upcoming token + token = lookAhead(json, index); - if (!success) { + if (token == JsonTokenNone) { + success = false; return QVariantMap(); - } + } else if (token == JsonTokenComma) { + nextToken(json, index); + } else if (token == JsonTokenCurlyClose) { + nextToken(json, index); + return map; + } else { + // Parse the key/value pair's name + QString name = parseString(json, index, success).toString(); + + if (!success) { + return QVariantMap(); + } - // Get the next token - token = nextToken(json, index); + // Get the next token + token = nextToken(json, index); - // If the next token is not a colon, flag the failure - // return an empty QVariant - if (token != JsonTokenColon) { - success = false; - return QVariant(QVariantMap()); - } + // If the next token is not a colon, flag the failure + // return an empty QVariant + if (token != JsonTokenColon) { + success = false; + return QVariant(QVariantMap()); + } - // Parse the key/value pair's value - QVariant value = parseValue(json, index, success); + // Parse the key/value pair's value + QVariant value = parseValue(json, index, success); - if (!success) { - return QVariantMap(); - } + if (!success) { + return QVariantMap(); + } - // Assign the value to the key in the map - map[name] = value; + // Assign the value to the key in the map + map[name] = value; + } } - } - // Return the map successfully - return QVariant(map); -} + // Return the map successfully + return QVariant(map); + } -/** - * parseArray - */ -static QVariant parseArray(const QString& json, int& index, bool& success) { - QVariantList list; + /** + * parseArray + */ + static QVariant parseArray(const QString &json, int &index, bool &success) { + QVariantList list; - nextToken(json, index); + nextToken(json, index); - bool done = false; - while (!done) { - int token = lookAhead(json, index); + bool done = false; + while(!done) { + int token = lookAhead(json, index); - if (token == JsonTokenNone) { - success = false; - return QVariantList(); - } else if (token == JsonTokenComma) { - nextToken(json, index); - } else if (token == JsonTokenSquaredClose) { - nextToken(json, index); - break; - } else { - QVariant value = parseValue(json, index, success); - if (!success) { + if (token == JsonTokenNone) { + success = false; return QVariantList(); + } else if (token == JsonTokenComma) { + nextToken(json, index); + } else if (token == JsonTokenSquaredClose) { + nextToken(json, index); + break; + } else { + QVariant value = parseValue(json, index, success); + if (!success) { + return QVariantList(); + } + list.push_back(value); } - list.push_back(value); } - } - - return QVariant(list); -} -/** - * parseString - */ -static QVariant parseString(const QString& json, int& index, bool& success) { - QString s; - QChar c; + return QVariant(list); + } - eatWhitespace(json, index); + /** + * parseString + */ + static QVariant parseString(const QString &json, int &index, bool &success) { + QString s; + QChar c; - c = json[index++]; - - bool complete = false; - while (!complete) { - if (index == json.size()) { - break; - } + eatWhitespace(json, index); c = json[index++]; - if (c == '\"') { - complete = true; - break; - } else if (c == '\\') { + bool complete = false; + while(!complete) { if (index == json.size()) { break; } @@ -343,185 +341,182 @@ static QVariant parseString(const QString& json, int& index, bool& success) { c = json[index++]; if (c == '\"') { - s.append('\"'); + complete = true; + break; } else if (c == '\\') { - s.append('\\'); - } else if (c == '/') { - s.append('/'); - } else if (c == 'b') { - s.append('\b'); - } else if (c == 'f') { - s.append('\f'); - } else if (c == 'n') { - s.append('\n'); - } else if (c == 'r') { - s.append('\r'); - } else if (c == 't') { - s.append('\t'); - } else if (c == 'u') { - int remainingLength = json.size() - index; - if (remainingLength >= 4) { - QString unicodeStr = json.mid(index, 4); - - int symbol = unicodeStr.toInt(0, 16); - - s.append(QChar(symbol)); - - index += 4; - } else { + if (index == json.size()) { break; } + + c = json[index++]; + + if (c == '\"') { + s.append('\"'); + } else if (c == '\\') { + s.append('\\'); + } else if (c == '/') { + s.append('/'); + } else if (c == 'b') { + s.append('\b'); + } else if (c == 'f') { + s.append('\f'); + } else if (c == 'n') { + s.append('\n'); + } else if (c == 'r') { + s.append('\r'); + } else if (c == 't') { + s.append('\t'); + } else if (c == 'u') { + int remainingLength = json.size() - index; + if (remainingLength >= 4) { + QString unicodeStr = json.mid(index, 4); + + int symbol = unicodeStr.toInt(0, 16); + + s.append(QChar(symbol)); + + index += 4; + } else { + break; + } + } + } else { + s.append(c); } - } else { - s.append(c); } - } - if (!complete) { - success = false; - return QVariant(); - } + if (!complete) { + success = false; + return QVariant(); + } - return QVariant(s); -} + return QVariant(s); + } -/** - * parseNumber - */ -static QVariant parseNumber(const QString& json, int& index) { - eatWhitespace(json, index); + /** + * parseNumber + */ + static QVariant parseNumber(const QString &json, int &index) { + eatWhitespace(json, index); - int lastIndex = lastIndexOfNumber(json, index); - int charLength = (lastIndex - index) + 1; - QString numberStr; + int lastIndex = lastIndexOfNumber(json, index); + int charLength = (lastIndex - index) + 1; + QString numberStr; - numberStr = json.mid(index, charLength); + numberStr = json.mid(index, charLength); - index = lastIndex + 1; - bool ok; + index = lastIndex + 1; + bool ok; - if (numberStr.contains('.')) { - return QVariant(numberStr.toDouble(nullptr)); - } else if (numberStr.startsWith('-')) { - int i = numberStr.toInt(&ok); - if (!ok) { - qlonglong ll = numberStr.toLongLong(&ok); - return ok ? ll : QVariant(numberStr); - } - return i; - } else { - uint u = numberStr.toUInt(&ok); - if (!ok) { - qulonglong ull = numberStr.toULongLong(&ok); - return ok ? ull : QVariant(numberStr); + if (numberStr.contains('.')) { + return QVariant(numberStr.toDouble(nullptr)); + } else if (numberStr.startsWith('-')) { + int i = numberStr.toInt(&ok); + if (!ok) { + qlonglong ll = numberStr.toLongLong(&ok); + return ok ? ll : QVariant(numberStr); + } + return i; + } else { + uint u = numberStr.toUInt(&ok); + if (!ok) { + qulonglong ull = numberStr.toULongLong(&ok); + return ok ? ull : QVariant(numberStr); + } + return u; } - return u; } -} -/** - * lastIndexOfNumber - */ -static int lastIndexOfNumber(const QString& json, int index) { - int lastIndex; + /** + * lastIndexOfNumber + */ + static int lastIndexOfNumber(const QString &json, int index) { + int lastIndex; - for (lastIndex = index; lastIndex < json.size(); lastIndex++) { - if (QString("0123456789+-.eE").indexOf(json[lastIndex]) == -1) { - break; + for(lastIndex = index; lastIndex < json.size(); lastIndex++) { + if (QString("0123456789+-.eE").indexOf(json[lastIndex]) == -1) { + break; + } } - } - return lastIndex - 1; -} + return lastIndex -1; + } -/** - * eatWhitespace - */ -static void eatWhitespace(const QString& json, int& index) { - for (; index < json.size(); index++) { - if (QString(" \t\n\r").indexOf(json[index]) == -1) { - break; + /** + * eatWhitespace + */ + static void eatWhitespace(const QString &json, int &index) { + for(; index < json.size(); index++) { + if (QString(" \t\n\r").indexOf(json[index]) == -1) { + break; + } } } -} -/** - * lookAhead - */ -static int lookAhead(const QString& json, int index) { - int saveIndex = index; - return nextToken(json, saveIndex); -} + /** + * lookAhead + */ + static int lookAhead(const QString &json, int index) { + int saveIndex = index; + return nextToken(json, saveIndex); + } -/** - * nextToken - */ -static int nextToken(const QString& json, int& index) { - eatWhitespace(json, index); + /** + * nextToken + */ + static int nextToken(const QString &json, int &index) { + eatWhitespace(json, index); - if (index == json.size()) { - return JsonTokenNone; - } + if (index == json.size()) { + return JsonTokenNone; + } - QChar c = json[index]; - index++; - switch (c.toLatin1()) { - case '{': - return JsonTokenCurlyOpen; - case '}': - return JsonTokenCurlyClose; - case '[': - return JsonTokenSquaredOpen; - case ']': - return JsonTokenSquaredClose; - case ',': - return JsonTokenComma; - case '"': - return JsonTokenString; - case '0': - case '1': - case '2': - case '3': - case '4': - case '5': - case '6': - case '7': - case '8': - case '9': - case '-': - return JsonTokenNumber; - case ':': - return JsonTokenColon; - } - index--; // ^ WTF? + QChar c = json[index]; + index++; + switch(c.toLatin1()) { + case '{': return JsonTokenCurlyOpen; + case '}': return JsonTokenCurlyClose; + case '[': return JsonTokenSquaredOpen; + case ']': return JsonTokenSquaredClose; + case ',': return JsonTokenComma; + case '"': return JsonTokenString; + case '0': case '1': case '2': case '3': case '4': + case '5': case '6': case '7': case '8': case '9': + case '-': return JsonTokenNumber; + case ':': return JsonTokenColon; + } + index--; // ^ WTF? - int remainingLength = json.size() - index; + int remainingLength = json.size() - index; - // True - if (remainingLength >= 4) { - if (json[index] == 't' && json[index + 1] == 'r' && json[index + 2] == 'u' && json[index + 3] == 'e') { - index += 4; - return JsonTokenTrue; + // True + if (remainingLength >= 4) { + if (json[index] == 't' && json[index + 1] == 'r' && + json[index + 2] == 'u' && json[index + 3] == 'e') { + index += 4; + return JsonTokenTrue; + } } - } - // False - if (remainingLength >= 5) { - if (json[index] == 'f' && json[index + 1] == 'a' && json[index + 2] == 'l' && json[index + 3] == 's' && - json[index + 4] == 'e') { - index += 5; - return JsonTokenFalse; + // False + if (remainingLength >= 5) { + if (json[index] == 'f' && json[index + 1] == 'a' && + json[index + 2] == 'l' && json[index + 3] == 's' && + json[index + 4] == 'e') { + index += 5; + return JsonTokenFalse; + } } - } - // Null - if (remainingLength >= 4) { - if (json[index] == 'n' && json[index + 1] == 'u' && json[index + 2] == 'l' && json[index + 3] == 'l') { - index += 4; - return JsonTokenNull; + // Null + if (remainingLength >= 4) { + if (json[index] == 'n' && json[index + 1] == 'u' && + json[index + 2] == 'l' && json[index + 3] == 'l') { + index += 4; + return JsonTokenNull; + } } - } - return JsonTokenNone; -} -} // namespace QtJson + return JsonTokenNone; + } +} //end namespace diff --git a/src/json.h b/src/json.h index 2247bd53..57842e37 100644 --- a/src/json.h +++ b/src/json.h @@ -23,8 +23,9 @@ #ifndef JSON_H #define JSON_H -#include #include +#include + /** * \namespace QtJson @@ -33,61 +34,61 @@ * Json parses a JSON data into a QVariant hierarchy. */ namespace QtJson { -typedef QVariantMap JsonObject; -typedef QVariantList JsonArray; + typedef QVariantMap JsonObject; + typedef QVariantList JsonArray; -/** - * Parse a JSON string - * - * \param json The JSON data - */ -QVariant parse(const QString& json); + /** + * Parse a JSON string + * + * \param json The JSON data + */ + QVariant parse(const QString &json); -/** - * Parse a JSON string - * - * \param json The JSON data - * \param success The success of the parsing - */ -QVariant parse(const QString& json, bool& success); + /** + * Parse a JSON string + * + * \param json The JSON data + * \param success The success of the parsing + */ + QVariant parse(const QString &json, bool &success); -/** - * This method generates a textual JSON representation - * - * \param data The JSON data generated by the parser. - * - * \return QByteArray Textual JSON representation in UTF-8 - */ -QByteArray serialize(const QVariant& data); + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * + * \return QByteArray Textual JSON representation in UTF-8 + */ + QByteArray serialize(const QVariant &data); -/** - * This method generates a textual JSON representation - * - * \param data The JSON data generated by the parser. - * \param success The success of the serialization - * - * \return QByteArray Textual JSON representation in UTF-8 - */ -QByteArray serialize(const QVariant& data, bool& success); + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * \param success The success of the serialization + * + * \return QByteArray Textual JSON representation in UTF-8 + */ + QByteArray serialize(const QVariant &data, bool &success); -/** - * This method generates a textual JSON representation - * - * \param data The JSON data generated by the parser. - * - * \return QString Textual JSON representation - */ -QString serializeStr(const QVariant& data); + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * + * \return QString Textual JSON representation + */ + QString serializeStr(const QVariant &data); -/** - * This method generates a textual JSON representation - * - * \param data The JSON data generated by the parser. - * \param success The success of the serialization - * - * \return QString Textual JSON representation - */ -QString serializeStr(const QVariant& data, bool& success); -} // namespace QtJson + /** + * This method generates a textual JSON representation + * + * \param data The JSON data generated by the parser. + * \param success The success of the serialization + * + * \return QString Textual JSON representation + */ + QString serializeStr(const QVariant &data, bool &success); +} -#endif // JSON_H +#endif //JSON_H diff --git a/src/loadmechanism.cpp b/src/loadmechanism.cpp index 1aef11dd..7ad36ac7 100644 --- a/src/loadmechanism.cpp +++ b/src/loadmechanism.cpp @@ -18,273 +18,299 @@ along with Mod Organizer. If not, see . */ #include "loadmechanism.h" -#include "util.h" #include "utility.h" -#include -#include -#include +#include "util.h" +#include +#include +#include #include #include -#include +#include #include -#include -#include -#include +#include +#include +#include + using namespace MOBase; using namespace MOShared; -LoadMechanism::LoadMechanism() : m_SelectedMechanism(LOAD_MODORGANIZER) {} -void LoadMechanism::writeHintFile(const QDir& targetDirectory) { - QString hintFilePath = targetDirectory.absoluteFilePath("mo_path.txt"); - QFile hintFile(hintFilePath); - if (hintFile.exists()) { - hintFile.remove(); - } - if (!hintFile.open(QIODevice::WriteOnly)) { - throw MyException(QObject::tr("failed to open %1: %2").arg(hintFilePath).arg(hintFile.errorString())); - } - hintFile.write(qApp->applicationDirPath().toUtf8().constData()); - hintFile.close(); +LoadMechanism::LoadMechanism() + : m_SelectedMechanism(LOAD_MODORGANIZER) +{ } -void LoadMechanism::removeHintFile(QDir targetDirectory) { targetDirectory.remove("mo_path.txt"); } +void LoadMechanism::writeHintFile(const QDir &targetDirectory) +{ + QString hintFilePath = targetDirectory.absoluteFilePath("mo_path.txt"); + QFile hintFile(hintFilePath); + if (hintFile.exists()) { + hintFile.remove(); + } + if (!hintFile.open(QIODevice::WriteOnly)) { + throw MyException(QObject::tr("failed to open %1: %2").arg(hintFilePath).arg(hintFile.errorString())); + } + hintFile.write(qApp->applicationDirPath().toUtf8().constData()); + hintFile.close(); +} -bool LoadMechanism::isDirectLoadingSupported() { - // FIXME: Seriously? isn't there a 'do i need steam' thing? - IPluginGame const* game = qApp->property("managed_game").value(); - if (game->gameName().compare("oblivion", Qt::CaseInsensitive) == 0) { - // oblivion can be loaded directly if it's not the steam variant - return !game->gameDirectory().exists("steam_api.dll"); - } else { - // all other games work afaik - return true; - } + +void LoadMechanism::removeHintFile(QDir targetDirectory) +{ + targetDirectory.remove("mo_path.txt"); } -bool LoadMechanism::isScriptExtenderSupported() { - IPluginGame const* game = qApp->property("managed_game").value(); - ScriptExtender* extender = game->feature(); - // test if there even is an extender for the managed game and if so whether it's installed - return extender != nullptr && extender->isInstalled(); +bool LoadMechanism::isDirectLoadingSupported() +{ + //FIXME: Seriously? isn't there a 'do i need steam' thing? + IPluginGame const *game = qApp->property("managed_game").value(); + if (game->gameName().compare("oblivion", Qt::CaseInsensitive) == 0) { + // oblivion can be loaded directly if it's not the steam variant + return !game->gameDirectory().exists("steam_api.dll"); + } else { + // all other games work afaik + return true; + } } -bool LoadMechanism::isProxyDLLSupported() { - // using steam_api.dll as the proxy is way too game specific as many games will have different - // versions of that dll. - // plus: the proxy dll hasn't been working for at least the whole 1.12.x versions of MO and - // noone reported it so why maintain an unused feature? - return false; - /* IPluginGame const *game = qApp->property("managed_game").value(); - return game->gameDirectory().exists(QString::fromStdWString(AppConfig::proxyDLLTarget()));*/ +bool LoadMechanism::isScriptExtenderSupported() +{ + IPluginGame const *game = qApp->property("managed_game").value(); + ScriptExtender *extender = game->feature(); + + // test if there even is an extender for the managed game and if so whether it's installed + return extender != nullptr && extender->isInstalled(); } -bool LoadMechanism::hashIdentical(const QString& fileNameLHS, const QString& fileNameRHS) { - QFile fileLHS(fileNameLHS); - if (!fileLHS.open(QIODevice::ReadOnly)) { - throw MyException(QObject::tr("%1 not found").arg(fileNameLHS)); - } - QByteArray dataLHS = fileLHS.readAll(); - QByteArray hashLHS = QCryptographicHash::hash(dataLHS, QCryptographicHash::Md5); +bool LoadMechanism::isProxyDLLSupported() +{ + // using steam_api.dll as the proxy is way too game specific as many games will have different + // versions of that dll. + // plus: the proxy dll hasn't been working for at least the whole 1.12.x versions of MO and + // noone reported it so why maintain an unused feature? + return false; +/* IPluginGame const *game = qApp->property("managed_game").value(); + return game->gameDirectory().exists(QString::fromStdWString(AppConfig::proxyDLLTarget()));*/ +} - fileLHS.close(); - QFile fileRHS(fileNameRHS); - if (!fileRHS.open(QIODevice::ReadOnly)) { - throw MyException(QObject::tr("%1 not found").arg(fileNameRHS)); - } - QByteArray dataRHS = fileRHS.readAll(); - QByteArray hashRHS = QCryptographicHash::hash(dataRHS, QCryptographicHash::Md5); +bool LoadMechanism::hashIdentical(const QString &fileNameLHS, const QString &fileNameRHS) +{ + QFile fileLHS(fileNameLHS); + if (!fileLHS.open(QIODevice::ReadOnly)) { + throw MyException(QObject::tr("%1 not found").arg(fileNameLHS)); + } + QByteArray dataLHS = fileLHS.readAll(); + QByteArray hashLHS = QCryptographicHash::hash(dataLHS, QCryptographicHash::Md5); + + fileLHS.close(); + + QFile fileRHS(fileNameRHS); + if (!fileRHS.open(QIODevice::ReadOnly)) { + throw MyException(QObject::tr("%1 not found").arg(fileNameRHS)); + } + QByteArray dataRHS = fileRHS.readAll(); + QByteArray hashRHS = QCryptographicHash::hash(dataRHS, QCryptographicHash::Md5); - fileRHS.close(); + fileRHS.close(); - return hashLHS == hashRHS; + return hashLHS == hashRHS; } -void LoadMechanism::deactivateScriptExtender() { - try { - IPluginGame const* game = qApp->property("managed_game").value(); - ScriptExtender* extender = game->feature(); - if (extender == nullptr) { - throw MyException(QObject::tr("game doesn't support a script extender")); - } - QDir pluginsDir(game->gameDirectory().absolutePath() + "/data/" + extender->PluginPath()); +void LoadMechanism::deactivateScriptExtender() +{ + try { + IPluginGame const *game = qApp->property("managed_game").value(); + ScriptExtender *extender = game->feature(); + if (extender == nullptr) { + throw MyException(QObject::tr("game doesn't support a script extender")); + } -#pragma message("implement this for usvfs") + QDir pluginsDir(game->gameDirectory().absolutePath() + "/data/" + extender->PluginPath()); - QString vfsDLLName = ""; - if (extender->getArch() == IMAGE_FILE_MACHINE_I386) { - vfsDLLName = ToQString(AppConfig::vfs32DLLName()); - } else if (extender->getArch() == IMAGE_FILE_MACHINE_AMD64) { - vfsDLLName = ToQString(AppConfig::vfs64DLLName()); - } - qDebug("USVFS DLL Name: " + vfsDLLName.toLatin1()); - if (vfsDLLName != "") { - if (QFile(pluginsDir.absoluteFilePath(vfsDLLName)).exists()) { - // remove dll from SE plugins directory - if (!pluginsDir.remove(vfsDLLName)) { - throw MyException(QObject::tr("Failed to delete %1").arg(pluginsDir.absoluteFilePath(vfsDLLName))); - } - } - } +#pragma message("implement this for usvfs") - removeHintFile(pluginsDir); - } catch (const std::exception& e) { - QMessageBox::critical(nullptr, QObject::tr("Failed to deactivate script extender loading"), e.what()); - } + QString vfsDLLName = ""; + if (extender->getArch() == IMAGE_FILE_MACHINE_I386) { + vfsDLLName = ToQString(AppConfig::vfs32DLLName()); + } + else if (extender->getArch() == IMAGE_FILE_MACHINE_AMD64) + { + vfsDLLName = ToQString(AppConfig::vfs64DLLName()); + } + qDebug("USVFS DLL Name: " + vfsDLLName.toLatin1()); + if (vfsDLLName != "") { + if (QFile(pluginsDir.absoluteFilePath(vfsDLLName)).exists()) { + // remove dll from SE plugins directory + if (!pluginsDir.remove(vfsDLLName)) { + throw MyException(QObject::tr("Failed to delete %1").arg(pluginsDir.absoluteFilePath(vfsDLLName))); + } + } + } + + removeHintFile(pluginsDir); + } catch (const std::exception &e) { + QMessageBox::critical(nullptr, QObject::tr("Failed to deactivate script extender loading"), e.what()); + } } -void LoadMechanism::deactivateProxyDLL() { - try { - IPluginGame const* game = qApp->property("managed_game").value(); - - QString targetPath = - game->gameDirectory().absoluteFilePath(QString::fromStdWString(AppConfig::proxyDLLTarget())); - - QFile targetDLL(targetPath); - if (targetDLL.exists()) { - QString origFile = - game->gameDirectory().absoluteFilePath(QString::fromStdWString(AppConfig::proxyDLLOrig())); - // determine if a proxy-dll is installed - // this is a very crude way of making this decision but it should be good enough - if ((targetDLL.size() < 24576) && (QFile(origFile).exists())) { - // remove proxy-dll - if (!targetDLL.remove()) { - throw MyException( - QObject::tr("Failed to remove %1: %2").arg(targetPath).arg(targetDLL.errorString())); - } else if (!QFile::rename(origFile, targetPath)) { - throw MyException(QObject::tr("Failed to rename %1 to %2").arg(origFile, targetPath)); - } - } - } - removeHintFile(game->gameDirectory()); - } catch (const std::exception& e) { - QMessageBox::critical(nullptr, QObject::tr("Failed to deactivate proxy-dll loading"), e.what()); +void LoadMechanism::deactivateProxyDLL() +{ + try { + IPluginGame const *game = qApp->property("managed_game").value(); + + QString targetPath = game->gameDirectory().absoluteFilePath(QString::fromStdWString(AppConfig::proxyDLLTarget())); + + QFile targetDLL(targetPath); + if (targetDLL.exists()) { + QString origFile = game->gameDirectory().absoluteFilePath(QString::fromStdWString(AppConfig::proxyDLLOrig())); + // determine if a proxy-dll is installed + // this is a very crude way of making this decision but it should be good enough + if ((targetDLL.size() < 24576) && (QFile(origFile).exists())) { + // remove proxy-dll + if (!targetDLL.remove()) { + throw MyException(QObject::tr("Failed to remove %1: %2").arg(targetPath).arg(targetDLL.errorString())); + } else if (!QFile::rename(origFile, targetPath)) { + throw MyException(QObject::tr("Failed to rename %1 to %2").arg(origFile, targetPath)); + } + } } + + removeHintFile(game->gameDirectory()); + } catch (const std::exception &e) { + QMessageBox::critical(nullptr, QObject::tr("Failed to deactivate proxy-dll loading"), e.what()); + } } -void LoadMechanism::activateScriptExtender() { - try { - IPluginGame const* game = qApp->property("managed_game").value(); - ScriptExtender* extender = game->feature(); - if (extender == nullptr) { - throw MyException(QObject::tr("game doesn't support a script extender")); - } - QDir pluginsDir(game->gameDirectory().absolutePath() + "/data/" + extender->PluginPath()); +void LoadMechanism::activateScriptExtender() +{ + try { + IPluginGame const *game = qApp->property("managed_game").value(); + ScriptExtender *extender = game->feature(); + if (extender == nullptr) { + throw MyException(QObject::tr("game doesn't support a script extender")); + } - if (!pluginsDir.exists()) { - pluginsDir.mkpath("."); - } + QDir pluginsDir(game->gameDirectory().absolutePath() + "/data/" + extender->PluginPath()); -#pragma message("implement this for usvfs") - std::wstring vfsDLL = L""; - if (extender->getArch() == IMAGE_FILE_MACHINE_I386) { - vfsDLL = AppConfig::vfs32DLLName(); - } else if (extender->getArch() == IMAGE_FILE_MACHINE_AMD64) { - vfsDLL = AppConfig::vfs64DLLName(); - } - if (vfsDLL != L"") { - QString targetPath = pluginsDir.absoluteFilePath(ToQString(vfsDLL)); - QString vfsDLLPath = qApp->applicationDirPath() + "/" + QString::fromStdWString(vfsDLL); - - qDebug("DLL USVFS Target Path: " + targetPath.toLatin1()); - qDebug("DLL USVFS VFS DLL Path: " + vfsDLLPath.toLatin1()); - - QFile dllFile(targetPath); - - if (dllFile.exists()) { - // may be outdated - if (!hashIdentical(targetPath, vfsDLLPath)) { - dllFile.remove(); - } - } - - if (!dllFile.exists()) { - // install dll to SE plugins - if (!QFile::copy(vfsDLLPath, targetPath)) { - throw MyException(QObject::tr("Failed to copy %1 to %2").arg(vfsDLLPath, targetPath)); - } - } - } - writeHintFile(pluginsDir); - } catch (const std::exception& e) { - QMessageBox::critical(nullptr, QObject::tr("Failed to set up script extender loading"), e.what()); + if (!pluginsDir.exists()) { + pluginsDir.mkpath("."); } + +#pragma message("implement this for usvfs") + std::wstring vfsDLL = L""; + if (extender->getArch() == IMAGE_FILE_MACHINE_I386) { + vfsDLL = AppConfig::vfs32DLLName(); + } + else if (extender->getArch() == IMAGE_FILE_MACHINE_AMD64) + { + vfsDLL = AppConfig::vfs64DLLName(); + } + if (vfsDLL != L"") { + QString targetPath = pluginsDir.absoluteFilePath(ToQString(vfsDLL)); + QString vfsDLLPath = qApp->applicationDirPath() + "/" + QString::fromStdWString(vfsDLL); + + qDebug("DLL USVFS Target Path: " + targetPath.toLatin1()); + qDebug("DLL USVFS VFS DLL Path: " + vfsDLLPath.toLatin1()); + + QFile dllFile(targetPath); + + if (dllFile.exists()) { + // may be outdated + if (!hashIdentical(targetPath, vfsDLLPath)) { + dllFile.remove(); + } + } + + if (!dllFile.exists()) { + // install dll to SE plugins + if (!QFile::copy(vfsDLLPath, targetPath)) { + throw MyException(QObject::tr("Failed to copy %1 to %2").arg(vfsDLLPath, targetPath)); + } + } + } + writeHintFile(pluginsDir); + } catch (const std::exception &e) { + QMessageBox::critical(nullptr, QObject::tr("Failed to set up script extender loading"), e.what()); + } } -void LoadMechanism::activateProxyDLL() { - try { - IPluginGame const* game = qApp->property("managed_game").value(); - QString targetPath = - game->gameDirectory().absoluteFilePath(QString::fromStdWString(AppConfig::proxyDLLTarget())); +void LoadMechanism::activateProxyDLL() +{ + try { + IPluginGame const *game = qApp->property("managed_game").value(); + + QString targetPath = game->gameDirectory().absoluteFilePath(QString::fromStdWString(AppConfig::proxyDLLTarget())); + + QFile targetDLL(targetPath); + if (!targetDLL.exists()) { + return; + } + + QString sourcePath = qApp->applicationDirPath() + "/" + ToQString(AppConfig::proxyDLLSource()); - QFile targetDLL(targetPath); - if (!targetDLL.exists()) { - return; + // this is a very crude way of making this decision but it should be good enough + if (targetDLL.size() < 24576) { + // determine if a proxy-dll is already installed and if so, if it's the right one + if (!hashIdentical(targetPath, sourcePath)) { + // wrong proxy dll, probably outdated. delete and install the new one + if (!QFile::remove(targetPath)) { + throw MyException(QObject::tr("Failed to delete old proxy-dll %1").arg(targetPath)); + } + if (!QFile::copy(sourcePath, targetPath)) { + throw MyException(QObject::tr("Failed to copy %1 to %2").arg(sourcePath).arg(targetPath)); } + } // otherwise the proxy-dll is already the right one + } else { + // no proxy dll installed yet. move the original and insert proxy-dll + + QString origFile = game->gameDirectory().absoluteFilePath(QString::fromStdWString(AppConfig::proxyDLLOrig())); - QString sourcePath = qApp->applicationDirPath() + "/" + ToQString(AppConfig::proxyDLLSource()); - - // this is a very crude way of making this decision but it should be good enough - if (targetDLL.size() < 24576) { - // determine if a proxy-dll is already installed and if so, if it's the right one - if (!hashIdentical(targetPath, sourcePath)) { - // wrong proxy dll, probably outdated. delete and install the new one - if (!QFile::remove(targetPath)) { - throw MyException(QObject::tr("Failed to delete old proxy-dll %1").arg(targetPath)); - } - if (!QFile::copy(sourcePath, targetPath)) { - throw MyException(QObject::tr("Failed to copy %1 to %2").arg(sourcePath).arg(targetPath)); - } - } // otherwise the proxy-dll is already the right one - } else { - // no proxy dll installed yet. move the original and insert proxy-dll - - QString origFile = - game->gameDirectory().absoluteFilePath(QString::fromStdWString(AppConfig::proxyDLLOrig())); - - if (QFile(origFile).exists()) { - // orig-file exists. this may happen if the steam-api was updated or the user messed with the - // dlls. - if (!QFile::remove(origFile)) { - throw MyException(QObject::tr("Failed to overwrite %1").arg(origFile)); - } - } - if (!QFile::rename(targetPath, origFile)) { - throw MyException(QObject::tr("Failed to rename %1 to %2").arg(targetPath).arg(origFile)); - } - if (!QFile::copy(sourcePath, targetPath)) { - throw MyException(QObject::tr("Failed to copy %1 to %2").arg(sourcePath).arg(targetPath)); - } + if (QFile(origFile).exists()) { + // orig-file exists. this may happen if the steam-api was updated or the user messed with the + // dlls. + if (!QFile::remove(origFile)) { + throw MyException(QObject::tr("Failed to overwrite %1").arg(origFile)); } - writeHintFile(game->gameDirectory()); - } catch (const std::exception& e) { - QMessageBox::critical(nullptr, QObject::tr("Failed to set up proxy-dll loading"), e.what()); + } + if (!QFile::rename(targetPath, origFile)) { + throw MyException(QObject::tr("Failed to rename %1 to %2").arg(targetPath).arg(origFile)); + } + if (!QFile::copy(sourcePath, targetPath)) { + throw MyException(QObject::tr("Failed to copy %1 to %2").arg(sourcePath).arg(targetPath)); + } } + writeHintFile(game->gameDirectory()); + } catch (const std::exception &e) { + QMessageBox::critical(nullptr, QObject::tr("Failed to set up proxy-dll loading"), e.what()); + } } -void LoadMechanism::activate(EMechanism mechanism) { - switch (mechanism) { + +void LoadMechanism::activate(EMechanism mechanism) +{ + switch (mechanism) { case LOAD_MODORGANIZER: { - qDebug("Load Mechanism: Mod Organizer"); - deactivateProxyDLL(); - deactivateScriptExtender(); + qDebug("Load Mechanism: Mod Organizer"); + deactivateProxyDLL(); + deactivateScriptExtender(); } break; case LOAD_SCRIPTEXTENDER: { - qDebug("Load Mechanism: ScriptExtender"); - deactivateProxyDLL(); - activateScriptExtender(); + qDebug("Load Mechanism: ScriptExtender"); + deactivateProxyDLL(); + activateScriptExtender(); } break; case LOAD_PROXYDLL: { - qDebug("Load Mechanism: Proxy DLL"); - deactivateScriptExtender(); - activateProxyDLL(); + qDebug("Load Mechanism: Proxy DLL"); + deactivateScriptExtender(); + activateProxyDLL(); } break; - } + } } + diff --git a/src/loadmechanism.h b/src/loadmechanism.h index dfa43d85..c04473ab 100644 --- a/src/loadmechanism.h +++ b/src/loadmechanism.h @@ -20,9 +20,11 @@ along with Mod Organizer. If not, see . #ifndef LOADMECHANISM_H #define LOADMECHANISM_H + #include #include + /** * @brief manages the various load mechanisms supported by Mod Organizer * the load mechanisms is the means by which the mo-dll is injected into the target @@ -36,72 +38,83 @@ along with Mod Organizer. If not, see . * chain-load the original dll. This currently only works with steam-versions of games and * is intended as a last resort solution. **/ -class LoadMechanism { +class LoadMechanism +{ public: - enum EMechanism { LOAD_MODORGANIZER = 0, LOAD_SCRIPTEXTENDER, LOAD_PROXYDLL }; + + enum EMechanism { + LOAD_MODORGANIZER = 0, + LOAD_SCRIPTEXTENDER, + LOAD_PROXYDLL + }; public: - /** - * @brief constructor - * - **/ - LoadMechanism(); - - /** - * activate the specified mechanism. This automatically disables any other mechanism active - * - * @param mechanism the mechanism to activate - **/ - void activate(EMechanism mechanism); - - /** - * @brief test whether the "Mod Organizer" load mechanism is supported for the current game - * - * @return true if the load mechanism is supported - **/ - bool isDirectLoadingSupported(); - - /** - * @brief test whether the "Script Extender" load mechanism is supported for the current game - * - * @return true if the load mechanism is supported - **/ - bool isScriptExtenderSupported(); - - /** - * @brief test whether the "Proxy DLL" load mechanism is supported for the current game - * - * @return true if the load mechanism is supported - **/ - bool isProxyDLLSupported(); + + /** + * @brief constructor + * + **/ + LoadMechanism(); + + /** + * activate the specified mechanism. This automatically disables any other mechanism active + * + * @param mechanism the mechanism to activate + **/ + void activate(EMechanism mechanism); + + /** + * @brief test whether the "Mod Organizer" load mechanism is supported for the current game + * + * @return true if the load mechanism is supported + **/ + bool isDirectLoadingSupported(); + + /** + * @brief test whether the "Script Extender" load mechanism is supported for the current game + * + * @return true if the load mechanism is supported + **/ + bool isScriptExtenderSupported(); + + /** + * @brief test whether the "Proxy DLL" load mechanism is supported for the current game + * + * @return true if the load mechanism is supported + **/ + bool isProxyDLLSupported(); private: - // write a hint file that is required for certain loading mechanisms for the dll to find - // the mod organizer installation - void writeHintFile(const QDir& targetDirectory); - // remove the hint file if it exists. does nothing if the file doesn't exist - void removeHintFile(QDir targetDirectory); + // write a hint file that is required for certain loading mechanisms for the dll to find + // the mod organizer installation + void writeHintFile(const QDir &targetDirectory); - // compare the two files by md5-hash, returns true if they are identical - bool hashIdentical(const QString& fileNameLHS, const QString& fileNameRHS); + // remove the hint file if it exists. does nothing if the file doesn't exist + void removeHintFile(QDir targetDirectory); - // deactivate loading through script extender. does nothing if se-loading wasn't active - void deactivateScriptExtender(); + // compare the two files by md5-hash, returns true if they are identical + bool hashIdentical(const QString &fileNameLHS, const QString &fileNameRHS); - // deactivate loading through proxy-dll. does nothing if se-loading wasn't active - void deactivateProxyDLL(); + // deactivate loading through script extender. does nothing if se-loading wasn't active + void deactivateScriptExtender(); - // activate loading through script extender. does nothing if already active. updates - // the dll if necessary - void activateScriptExtender(); + // deactivate loading through proxy-dll. does nothing if se-loading wasn't active + void deactivateProxyDLL(); - // activate loading through proxy-dll. does nothing if already active. updates - // the dll if necessary - void activateProxyDLL(); + // activate loading through script extender. does nothing if already active. updates + // the dll if necessary + void activateScriptExtender(); + + // activate loading through proxy-dll. does nothing if already active. updates + // the dll if necessary + void activateProxyDLL(); private: - EMechanism m_SelectedMechanism; + + EMechanism m_SelectedMechanism; + }; + #endif // LOADMECHANISM_H diff --git a/src/lockeddialog.cpp b/src/lockeddialog.cpp index 8883cd56..143d5838 100644 --- a/src/lockeddialog.cpp +++ b/src/lockeddialog.cpp @@ -23,35 +23,49 @@ along with Mod Organizer. If not, see . #include #include #include -#include // for Qt::FramelessWindowHint, etc - -LockedDialog::LockedDialog(QWidget* parent, bool unlockByButton) - : LockedDialogBase(parent, !unlockByButton), ui(new Ui::LockedDialog) { - ui->setupUi(this); - - // Supposedly the Qt::CustomizeWindowHint should use a customized window - // allowing us to select if there is a close button. In practice this doesn't - // seem to work. We will ignore pressing the close button if unlockByButton == true - Qt::WindowFlags flags = - this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint; - if (m_allowClose) - flags |= Qt::WindowCloseButtonHint; - this->setWindowFlags(flags); - - if (!unlockByButton) { - ui->unlockButton->hide(); - ui->verticalLayout->addItem(new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding)); - } +#include // for Qt::FramelessWindowHint, etc + +LockedDialog::LockedDialog(QWidget *parent, bool unlockByButton) + : LockedDialogBase(parent, !unlockByButton) + , ui(new Ui::LockedDialog) +{ + ui->setupUi(this); + + // Supposedly the Qt::CustomizeWindowHint should use a customized window + // allowing us to select if there is a close button. In practice this doesn't + // seem to work. We will ignore pressing the close button if unlockByButton == true + Qt::WindowFlags flags = + this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint; + if (m_allowClose) + flags |= Qt::WindowCloseButtonHint; + this->setWindowFlags(flags); + + if (!unlockByButton) + { + ui->unlockButton->hide(); + ui->verticalLayout->addItem( + new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding)); + } } -LockedDialog::~LockedDialog() { delete ui; } +LockedDialog::~LockedDialog() +{ + delete ui; +} -void LockedDialog::setProcessName(const QString& name) { ui->processLabel->setText(name); } -void LockedDialog::on_unlockButton_clicked() { unlock(); } +void LockedDialog::setProcessName(const QString &name) +{ + ui->processLabel->setText(name); +} + +void LockedDialog::on_unlockButton_clicked() +{ + unlock(); +} void LockedDialog::unlock() { - LockedDialogBase::unlock(); - ui->label->setText("unlocking may take a few seconds"); - ui->unlockButton->setEnabled(false); + LockedDialogBase::unlock(); + ui->label->setText("unlocking may take a few seconds"); + ui->unlockButton->setEnabled(false); } diff --git a/src/lockeddialog.h b/src/lockeddialog.h index fae01600..36c16429 100644 --- a/src/lockeddialog.h +++ b/src/lockeddialog.h @@ -22,33 +22,36 @@ along with Mod Organizer. If not, see . #include "lockeddialogbase.h" namespace Ui { -class LockedDialog; + class LockedDialog; } /** * a small borderless dialog displayed while the Mod Organizer UI is locked * The dialog contains only a label and a button to force the UI to be unlocked - * + * * The UI gets locked while running external applications since they may modify the * data on which Mod Organizer works. After the UI is unlocked (manually or after the * external application closed) MO will refresh all of its data sources **/ -class LockedDialog : public LockedDialogBase { +class LockedDialog : public LockedDialogBase +{ Q_OBJECT public: - explicit LockedDialog(QWidget* parent = 0, bool unlockByButton = false); - ~LockedDialog(); + explicit LockedDialog(QWidget *parent = 0, bool unlockByButton = false); + ~LockedDialog(); - void setProcessName(const QString& name) override; + void setProcessName(const QString &name) override; protected: - void unlock() override; + + void unlock() override; private slots: - void on_unlockButton_clicked(); + void on_unlockButton_clicked(); private: - Ui::LockedDialog* ui; + + Ui::LockedDialog *ui; }; diff --git a/src/lockeddialogbase.cpp b/src/lockeddialogbase.cpp index 4cf158ea..b18f7429 100644 --- a/src/lockeddialogbase.cpp +++ b/src/lockeddialogbase.cpp @@ -22,37 +22,52 @@ along with Mod Organizer. If not, see . #include #include #include -#include // for Qt::FramelessWindowHint, etc - -LockedDialogBase::LockedDialogBase(QWidget* parent, bool allowClose) - : QDialog(parent), m_Unlocked(false), m_Canceled(false), m_allowClose(allowClose) { - if (parent != nullptr) { - QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); - position.rx() -= this->width() / 2; - position.ry() -= this->height() / 2; - move(position); - } +#include // for Qt::FramelessWindowHint, etc + +LockedDialogBase::LockedDialogBase(QWidget *parent, bool allowClose) + : QDialog(parent) + , m_Unlocked(false) + , m_Canceled(false) + , m_allowClose(allowClose) +{ + if (parent != nullptr) { + QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); + position.rx() -= this->width() / 2; + position.ry() -= this->height() / 2; + move(position); + } +} + +void LockedDialogBase::resizeEvent(QResizeEvent *event) +{ + QWidget *par = parentWidget(); + if (par != nullptr) { + QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); + position.rx() -= event->size().width() / 2; + position.ry() -= event->size().height() / 2; + move(position); + } } -void LockedDialogBase::resizeEvent(QResizeEvent* event) { - QWidget* par = parentWidget(); - if (par != nullptr) { - QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); - position.rx() -= event->size().width() / 2; - position.ry() -= event->size().height() / 2; - move(position); - } +void LockedDialogBase::reject() +{ + if (m_allowClose) + unlock(); } -void LockedDialogBase::reject() { - if (m_allowClose) - unlock(); +bool LockedDialogBase::unlockForced() const { + return m_Unlocked; } -bool LockedDialogBase::unlockForced() const { return m_Unlocked; } +bool LockedDialogBase::canceled() const { + return m_Canceled; +} -bool LockedDialogBase::canceled() const { return m_Canceled; } +void LockedDialogBase::unlock() { + m_Unlocked = true; +} -void LockedDialogBase::unlock() { m_Unlocked = true; } +void LockedDialogBase::cancel() { + m_Canceled = true; +} -void LockedDialogBase::cancel() { m_Canceled = true; } diff --git a/src/lockeddialogbase.h b/src/lockeddialogbase.h index 43f5d1ad..3c974a38 100644 --- a/src/lockeddialogbase.h +++ b/src/lockeddialogbase.h @@ -20,9 +20,9 @@ along with Mod Organizer. If not, see . #pragma once #include "ilockedwaitingforprocess.h" -#include // for QDialog -#include // for Q_OBJECT, slots -#include // for QString +#include // for QDialog +#include // for Q_OBJECT, slots +#include // for QString class QResizeEvent; class QWidget; @@ -30,31 +30,33 @@ class QWidget; /** * a small borderless dialog displayed while the Mod Organizer UI is locked * The dialog contains only a label and a button to force the UI to be unlocked - * + * * The UI gets locked while running external applications since they may modify the * data on which Mod Organizer works. After the UI is unlocked (manually or after the * external application closed) MO will refresh all of its data sources **/ -class LockedDialogBase : public QDialog, public ILockedWaitingForProcess { +class LockedDialogBase : public QDialog, public ILockedWaitingForProcess +{ Q_OBJECT public: - explicit LockedDialogBase(QWidget* parent, bool allowClose); + explicit LockedDialogBase(QWidget *parent, bool allowClose); - bool unlockForced() const override; + bool unlockForced() const override; - virtual bool canceled() const; + virtual bool canceled() const; protected: - virtual void resizeEvent(QResizeEvent* event); - virtual void reject(); + virtual void resizeEvent(QResizeEvent *event); - virtual void unlock(); + virtual void reject(); - virtual void cancel(); + virtual void unlock(); - bool m_Unlocked; - bool m_Canceled; - bool m_allowClose; + virtual void cancel(); + + bool m_Unlocked; + bool m_Canceled; + bool m_allowClose; }; diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp index 5a74d5f5..522ce3c8 100644 --- a/src/logbuffer.cpp +++ b/src/logbuffer.cpp @@ -18,212 +18,253 @@ along with Mod Organizer. If not, see . */ #include "logbuffer.h" -#include +#include +#include +#include #include #include -#include +#include #include -#include -#include using MOBase::reportError; QScopedPointer LogBuffer::s_Instance; QMutex LogBuffer::s_Mutex; -LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType, const QString& outputFileName) - : QAbstractItemModel(nullptr), m_OutFileName(outputFileName), m_ShutDown(false), m_MinMsgType(minMsgType), - m_NumMessages(0) { - m_Messages.resize(messageCount); +LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType, + const QString &outputFileName) + : QAbstractItemModel(nullptr) + , m_OutFileName(outputFileName) + , m_ShutDown(false) + , m_MinMsgType(minMsgType) + , m_NumMessages(0) +{ + m_Messages.resize(messageCount); } -LogBuffer::~LogBuffer() { - qInstallMessageHandler(0); - write(); +LogBuffer::~LogBuffer() +{ + qInstallMessageHandler(0); + write(); } -void LogBuffer::logMessage(QtMsgType type, const QString& message) { - if (type >= m_MinMsgType) { - Message msg = {type, QTime::currentTime(), message}; - if (m_NumMessages < m_Messages.size()) { - beginInsertRows(QModelIndex(), static_cast(m_NumMessages), static_cast(m_NumMessages) + 1); - } - m_Messages.at(m_NumMessages % m_Messages.size()) = msg; - if (m_NumMessages < m_Messages.size()) { - endInsertRows(); - } else { - emit dataChanged(createIndex(0, 0), createIndex(static_cast(m_Messages.size()), 0)); - } - ++m_NumMessages; - if (type >= QtCriticalMsg) { - write(); - } +void LogBuffer::logMessage(QtMsgType type, const QString &message) +{ + if (type >= m_MinMsgType) { + Message msg = {type, QTime::currentTime(), message}; + if (m_NumMessages < m_Messages.size()) { + beginInsertRows(QModelIndex(), static_cast(m_NumMessages), + static_cast(m_NumMessages) + 1); } -} - -void LogBuffer::write() const { - if (m_NumMessages == 0) { - return; + m_Messages.at(m_NumMessages % m_Messages.size()) = msg; + if (m_NumMessages < m_Messages.size()) { + endInsertRows(); + } else { + emit dataChanged(createIndex(0, 0), + createIndex(static_cast(m_Messages.size()), 0)); } - - DWORD lastError = ::GetLastError(); - - QFile file(m_OutFileName); - if (!file.open(QIODevice::WriteOnly)) { - reportError(tr("failed to write log to %1: %2").arg(m_OutFileName).arg(file.errorString())); - return; + ++m_NumMessages; + if (type >= QtCriticalMsg) { + write(); } + } +} - unsigned int i = - (m_NumMessages > m_Messages.size()) ? static_cast(m_NumMessages - m_Messages.size()) : 0U; - for (; i < m_NumMessages; ++i) { - file.write(m_Messages.at(i % m_Messages.size()).toString().toUtf8()); - file.write("\r\n"); - } - ::SetLastError(lastError); +void LogBuffer::write() const +{ + if (m_NumMessages == 0) { + return; + } + + DWORD lastError = ::GetLastError(); + + QFile file(m_OutFileName); + if (!file.open(QIODevice::WriteOnly)) { + reportError(tr("failed to write log to %1: %2") + .arg(m_OutFileName) + .arg(file.errorString())); + return; + } + + unsigned int i + = (m_NumMessages > m_Messages.size()) + ? static_cast(m_NumMessages - m_Messages.size()) + : 0U; + for (; i < m_NumMessages; ++i) { + file.write(m_Messages.at(i % m_Messages.size()).toString().toUtf8()); + file.write("\r\n"); + } + ::SetLastError(lastError); } -void LogBuffer::init(int messageCount, QtMsgType minMsgType, const QString& outputFileName) { - QMutexLocker guard(&s_Mutex); +void LogBuffer::init(int messageCount, QtMsgType minMsgType, + const QString &outputFileName) +{ + QMutexLocker guard(&s_Mutex); - s_Instance.reset(new LogBuffer(messageCount, minMsgType, outputFileName)); - qInstallMessageHandler(LogBuffer::log); + s_Instance.reset(new LogBuffer(messageCount, minMsgType, outputFileName)); + qInstallMessageHandler(LogBuffer::log); } -char LogBuffer::msgTypeID(QtMsgType type) { - switch (type) { +char LogBuffer::msgTypeID(QtMsgType type) +{ + switch (type) { case QtDebugMsg: - return 'D'; + return 'D'; case QtWarningMsg: - return 'W'; + return 'W'; case QtCriticalMsg: - return 'C'; + return 'C'; case QtFatalMsg: - return 'F'; + return 'F'; default: - return '?'; - } + return '?'; + } } -void LogBuffer::log(QtMsgType type, const QMessageLogContext& context, const QString& message) { - // QMutexLocker doesn't support timeout... - if (!s_Mutex.tryLock(100)) { - fprintf(stderr, "failed to log: %s", qPrintable(message)); - return; - } - ON_BLOCK_EXIT([]() { s_Mutex.unlock(); }); - - if (!s_Instance.isNull()) { - s_Instance->logMessage(type, message); - } - - if (type == QtDebugMsg) { - fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), - qPrintable(message)); +void LogBuffer::log(QtMsgType type, const QMessageLogContext &context, + const QString &message) +{ + // QMutexLocker doesn't support timeout... + if (!s_Mutex.tryLock(100)) { + fprintf(stderr, "failed to log: %s", qPrintable(message)); + return; + } + ON_BLOCK_EXIT([]() { s_Mutex.unlock(); }); + + if (!s_Instance.isNull()) { + s_Instance->logMessage(type, message); + } + + if (type == QtDebugMsg) { + fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), + msgTypeID(type), qPrintable(message)); + } else { + if (context.line != 0) { + fprintf(stdout, "%s [%c] (%s:%u) %s\n", + qPrintable(QTime::currentTime().toString()), msgTypeID(type), + context.file, context.line, qPrintable(message)); } else { - if (context.line != 0) { - fprintf(stdout, "%s [%c] (%s:%u) %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), - context.file, context.line, qPrintable(message)); - } else { - fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), - qPrintable(message)); - } + fprintf(stdout, "%s [%c] %s\n", + qPrintable(QTime::currentTime().toString()), msgTypeID(type), + qPrintable(message)); } - fflush(stdout); + } + fflush(stdout); } -QModelIndex LogBuffer::index(int row, int column, const QModelIndex&) const { return createIndex(row, column, row); } +QModelIndex LogBuffer::index(int row, int column, const QModelIndex &) const +{ + return createIndex(row, column, row); +} -QModelIndex LogBuffer::parent(const QModelIndex&) const { return QModelIndex(); } +QModelIndex LogBuffer::parent(const QModelIndex &) const +{ + return QModelIndex(); +} -int LogBuffer::rowCount(const QModelIndex& parent) const { - if (parent.isValid()) - return 0; - else - return static_cast(std::min(m_NumMessages, m_Messages.size())); +int LogBuffer::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) + return 0; + else + return static_cast(std::min(m_NumMessages, m_Messages.size())); } -int LogBuffer::columnCount(const QModelIndex&) const { return 2; } +int LogBuffer::columnCount(const QModelIndex &) const +{ + return 2; +} -QVariant LogBuffer::data(const QModelIndex& index, int role) const { - unsigned int offset = - m_NumMessages < m_Messages.size() ? 0 : static_cast(m_NumMessages - m_Messages.size()); - unsigned int msgIndex = (offset + index.row() + 1) % m_Messages.size(); - switch (role) { +QVariant LogBuffer::data(const QModelIndex &index, int role) const +{ + unsigned int offset + = m_NumMessages < m_Messages.size() + ? 0 + : static_cast(m_NumMessages - m_Messages.size()); + unsigned int msgIndex = (offset + index.row() + 1) % m_Messages.size(); + switch (role) { case Qt::DisplayRole: { - if (index.column() == 0) { - return m_Messages[msgIndex].time; - } else if (index.column() == 1) { - const QString& msg = m_Messages[msgIndex].message; - if (msg.length() < 200) { - return msg; - } else { - return msg.mid(0, 200) + "..."; - } + if (index.column() == 0) { + return m_Messages[msgIndex].time; + } else if (index.column() == 1) { + const QString &msg = m_Messages[msgIndex].message; + if (msg.length() < 200) { + return msg; + } else { + return msg.mid(0, 200) + "..."; } + } } break; case Qt::DecorationRole: { - if (index.column() == 1) { - switch (m_Messages[msgIndex].type) { - case QtDebugMsg: - return QIcon(":/MO/gui/information"); - case QtWarningMsg: - return QIcon(":/MO/gui/warning"); - case QtCriticalMsg: - return QIcon(":/MO/gui/important"); - case QtFatalMsg: - return QIcon(":/MO/gui/problem"); - } + if (index.column() == 1) { + switch (m_Messages[msgIndex].type) { + case QtDebugMsg: + return QIcon(":/MO/gui/information"); + case QtWarningMsg: + return QIcon(":/MO/gui/warning"); + case QtCriticalMsg: + return QIcon(":/MO/gui/important"); + case QtFatalMsg: + return QIcon(":/MO/gui/problem"); } + } } break; case Qt::UserRole: { - if (index.column() == 1) { - switch (m_Messages[msgIndex].type) { - case QtDebugMsg: - return "D"; - case QtWarningMsg: - return "W"; - case QtCriticalMsg: - return "C"; - case QtFatalMsg: - return "F"; - } + if (index.column() == 1) { + switch (m_Messages[msgIndex].type) { + case QtDebugMsg: + return "D"; + case QtWarningMsg: + return "W"; + case QtCriticalMsg: + return "C"; + case QtFatalMsg: + return "F"; } + } } break; - } - return QVariant(); + } + return QVariant(); } -void LogBuffer::writeNow() { - QMutexLocker guard(&s_Mutex); - if (!s_Instance.isNull()) { - s_Instance->write(); - } +void LogBuffer::writeNow() +{ + QMutexLocker guard(&s_Mutex); + if (!s_Instance.isNull()) { + s_Instance->write(); + } } -void LogBuffer::cleanQuit() { - QMutexLocker guard(&s_Mutex); - if (!s_Instance.isNull()) { - s_Instance->m_ShutDown = true; - } +void LogBuffer::cleanQuit() +{ + QMutexLocker guard(&s_Mutex); + if (!s_Instance.isNull()) { + s_Instance->m_ShutDown = true; + } } -void log(const char* format, ...) { - va_list argList; - va_start(argList, format); +void log(const char *format, ...) +{ + va_list argList; + va_start(argList, format); - static const int BUFFERSIZE = 1000; + static const int BUFFERSIZE = 1000; - char buffer[BUFFERSIZE + 1]; - buffer[BUFFERSIZE] = '\0'; + char buffer[BUFFERSIZE + 1]; + buffer[BUFFERSIZE] = '\0'; - vsnprintf(buffer, BUFFERSIZE, format, argList); + vsnprintf(buffer, BUFFERSIZE, format, argList); - qCritical("%s", buffer); + qCritical("%s", buffer); - va_end(argList); + va_end(argList); } -QString LogBuffer::Message::toString() const { - return QString("%1 [%2] %3").arg(time.toString()).arg(msgTypeID(type)).arg(message); +QString LogBuffer::Message::toString() const +{ + return QString("%1 [%2] %3") + .arg(time.toString()) + .arg(msgTypeID(type)) + .arg(message); } diff --git a/src/logbuffer.h b/src/logbuffer.h index 0a35e91a..0cfecfa2 100644 --- a/src/logbuffer.h +++ b/src/logbuffer.h @@ -20,68 +20,76 @@ along with Mod Organizer. If not, see . #ifndef LOGBUFFER_H #define LOGBUFFER_H -#include #include +#include #include #include #include #include -class LogBuffer : public QAbstractItemModel { - Q_OBJECT + +class LogBuffer : public QAbstractItemModel +{ + Q_OBJECT public: - static void init(int messageCount, QtMsgType minMsgType, const QString& outputFileName); - static void log(QtMsgType type, const QMessageLogContext& context, const QString& message); - static void writeNow(); - static void cleanQuit(); + static void init(int messageCount, QtMsgType minMsgType, const QString &outputFileName); + static void log(QtMsgType type, const QMessageLogContext &context, const QString &message); - static LogBuffer* instance() { return s_Instance.data(); } + static void writeNow(); + static void cleanQuit(); + + static LogBuffer *instance() { return s_Instance.data(); } public: - virtual ~LogBuffer(); - void logMessage(QtMsgType type, const QString& message); + virtual ~LogBuffer(); + + void logMessage(QtMsgType type, const QString &message); - // QAbstractItemModel interface + // QAbstractItemModel interface public: - QModelIndex index(int row, int column, const QModelIndex& parent) const; - QModelIndex parent(const QModelIndex& child) const; - int rowCount(const QModelIndex& parent) const; - int columnCount(const QModelIndex& parent) const; - QVariant data(const QModelIndex& index, int role) const; + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; signals: public slots: private: - explicit LogBuffer(int messageCount, QtMsgType minMsgType, const QString& outputFileName); - LogBuffer(const LogBuffer& reference); // not implemented - LogBuffer& operator=(const LogBuffer& reference); // not implemented - void write() const; + explicit LogBuffer(int messageCount, QtMsgType minMsgType, const QString &outputFileName); + LogBuffer(const LogBuffer &reference); // not implemented + LogBuffer &operator=(const LogBuffer &reference); // not implemented - static char msgTypeID(QtMsgType type); + void write() const; + + static char msgTypeID(QtMsgType type); private: - struct Message { - QtMsgType type; - QTime time; - QString message; - QString toString() const; - }; + + struct Message { + QtMsgType type; + QTime time; + QString message; + QString toString() const; + }; private: - static QScopedPointer s_Instance; - static QMutex s_Mutex; - - QString m_OutFileName; - bool m_ShutDown; - QtMsgType m_MinMsgType; - size_t m_NumMessages; - std::vector m_Messages; + + static QScopedPointer s_Instance; + static QMutex s_Mutex; + + QString m_OutFileName; + bool m_ShutDown; + QtMsgType m_MinMsgType; + size_t m_NumMessages; + std::vector m_Messages; + }; #endif // LOGBUFFER_H diff --git a/src/loghighlighter.cpp b/src/loghighlighter.cpp index 414c3020..f43e5662 100644 --- a/src/loghighlighter.cpp +++ b/src/loghighlighter.cpp @@ -20,28 +20,33 @@ along with Mod Organizer. If not, see . #include "loghighlighter.h" #include -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); - } - } +LogHighlighter::LogHighlighter(QObject *parent) : + QSyntaxHighlighter(parent) +{ +} - 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); +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 d542259e..55c15142 100644 --- a/src/loghighlighter.h +++ b/src/loghighlighter.h @@ -26,17 +26,20 @@ along with Mod Organizer. If not, see . * @brief Syntax highlighter to make log files from mo.dll more readable. * @note this is currently not used! **/ -class LogHighlighter : public QSyntaxHighlighter { +class LogHighlighter : public QSyntaxHighlighter +{ Q_OBJECT public: - explicit LogHighlighter(QObject* parent = 0); + explicit LogHighlighter(QObject *parent = 0); signals: public slots: protected: - virtual void highlightBlock(const QString& text); + + virtual void highlightBlock(const QString &text); + }; #endif // LOGHIGHLIGHTER_H diff --git a/src/main.cpp b/src/main.cpp index bc1b4bc5..935b7404 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,58 +17,60 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ + #ifdef LEAK_CHECK_WITH_VLD -#include #include +#include #endif // LEAK_CHECK_WITH_VLD #define WIN32_LEAN_AND_MEAN -#include #include +#include -#include "executableslist.h" -#include "helper.h" -#include "instancemanager.h" -#include "logbuffer.h" +#include +#include +#include #include "mainwindow.h" -#include "moapplication.h" +#include #include "modlist.h" -#include "moshortcut.h" -#include "nxmaccessmanager.h" #include "profile.h" -#include "selectiondialog.h" -#include "singleinstance.h" #include "spawn.h" -#include "tutorialmanager.h" +#include "executableslist.h" +#include "singleinstance.h" #include "utility.h" -#include -#include -#include -#include +#include "helper.h" +#include "logbuffer.h" +#include "selectiondialog.h" +#include "moapplication.h" +#include "tutorialmanager.h" +#include "nxmaccessmanager.h" +#include "instancemanager.h" +#include "moshortcut.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 #include -#include -#include #include @@ -79,591 +81,629 @@ along with Mod Organizer. If not, see . #include #include -#pragma comment(linker, \ - "/manifestDependency:\"name='dlls' processorArchitecture='x86' version='1.0.0.0' type='win32' \"") + +#pragma comment(linker, "/manifestDependency:\"name='dlls' processorArchitecture='x86' version='1.0.0.0' type='win32' \"") + using namespace MOBase; using namespace MOShared; -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 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 bootstrap() { - // 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)); - } +bool bootstrap() +{ + // 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 logfile - removeOldFiles(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()), - "usvfs*.log", 5, QDir::Name); + // cycle logfile + removeOldFiles(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()), + "usvfs*.log", 5, QDir::Name); - if (!createAndMakeWritable(AppConfig::logPath())) { - return false; - } + if (!createAndMakeWritable(AppConfig::logPath())) { + return false; + } - return true; + return true; } LPTOP_LEVEL_EXCEPTION_FILTER prevUnhandledExceptionFilter = nullptr; -static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS* exceptionPtrs) { - const std::wstring& dumpPath = OrganizerCore::crashDumpsPath(); - int dumpRes = CreateMiniDump(exceptionPtrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str()); - if (!dumpRes) - qCritical("ModOrganizer has crashed, crash dump created."); - else - qCritical("ModOrganizer has crashed, CreateMiniDump failed (%d, error %lu).", dumpRes, GetLastError()); - - if (prevUnhandledExceptionFilter) - return prevUnhandledExceptionFilter(exceptionPtrs); - else - return EXCEPTION_CONTINUE_SEARCH; +static LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionPtrs) +{ + const std::wstring& dumpPath = OrganizerCore::crashDumpsPath(); + int dumpRes = + CreateMiniDump(exceptionPtrs, OrganizerCore::getGlobalCrashDumpsType(), dumpPath.c_str()); + if (!dumpRes) + qCritical("ModOrganizer has crashed, crash dump created."); + else + qCritical("ModOrganizer has crashed, CreateMiniDump failed (%d, error %lu).", dumpRes, GetLastError()); + + if (prevUnhandledExceptionFilter) + return prevUnhandledExceptionFilter(exceptionPtrs); + else + return EXCEPTION_CONTINUE_SEARCH; } // 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 UntouchedCommandLineArguments(int parseArgCount, std::vector& parsedArgs) { - LPCWSTR cmd = GetCommandLineW(); - 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) - escaped = *cmd == '\\' ? escaped + 1 : 0; - } - if (*cmd == ' ') { - if (arg) - 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; - --parseArgCount; - } +LPCWSTR UntouchedCommandLineArguments(int parseArgCount, std::vector& parsedArgs) +{ + LPCWSTR cmd = GetCommandLineW(); + 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) + escaped = *cmd == '\\' ? escaped + 1 : 0; } - return cmd; + if (*cmd == ' ') { + if (arg) + 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; + --parseArgCount; + } + } + return cmd; } static int SpawnWaitProcess(LPCWSTR workingDirectory, LPCWSTR commandLine) { - 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: - std::wostringstream ost; - ost << L"CreateProcess failed: " << commandLine << ", " << GetLastError(); - OutputDebugStringW(ost.str().c_str()); - return -1; - } + 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: + std::wostringstream ost; + ost << L"CreateProcess failed: " << commandLine << ", " << GetLastError(); + OutputDebugStringW(ost.str().c_str()); + return -1; + } + + WaitForSingleObject(pi.hProcess, INFINITE); + + DWORD exitCode = (DWORD)-1; + ::GetExitCodeProcess(pi.hProcess, &exitCode); + CloseHandle(pi.hThread); + CloseHandle(pi.hProcess); + return static_cast(exitCode); +} - WaitForSingleObject(pi.hProcess, INFINITE); +static DWORD WaitForProcess() { - DWORD exitCode = (DWORD)-1; - ::GetExitCodeProcess(pi.hProcess, &exitCode); - CloseHandle(pi.hThread); - CloseHandle(pi.hProcess); - return static_cast(exitCode); } -static DWORD WaitForProcess() {} - -static bool HaveWriteAccess(const std::wstring& path) { - bool writable = false; - - const static SECURITY_INFORMATION requestedFileInformation = - OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; - - DWORD length = 0; - if (!::GetFileSecurityW(path.c_str(), requestedFileInformation, nullptr, 0UL, &length) && - (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { - std::string tempBuffer; - tempBuffer.reserve(length); - PSECURITY_DESCRIPTOR security = (PSECURITY_DESCRIPTOR)tempBuffer.data(); - if (security && ::GetFileSecurity(path.c_str(), requestedFileInformation, security, length, &length)) { - HANDLE token = nullptr; - const static DWORD tokenDesiredAccess = - TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ; - if (!::OpenThreadToken(::GetCurrentThread(), tokenDesiredAccess, TRUE, &token)) { - if (!::OpenProcessToken(::GetCurrentProcess(), tokenDesiredAccess, &token)) { - throw std::runtime_error("Unable to get any thread or process token"); - } - } - - HANDLE impersonatedToken = nullptr; - if (::DuplicateToken(token, SecurityImpersonation, &impersonatedToken)) { - GENERIC_MAPPING mapping = {0xFFFFFFFF}; - mapping.GenericRead = FILE_GENERIC_READ; - mapping.GenericWrite = FILE_GENERIC_WRITE; - mapping.GenericExecute = FILE_GENERIC_EXECUTE; - mapping.GenericAll = FILE_ALL_ACCESS; - - DWORD genericAccessRights = FILE_GENERIC_WRITE; - ::MapGenericMask(&genericAccessRights, &mapping); - - PRIVILEGE_SET privileges = {0}; - DWORD grantedAccess = 0; - DWORD privilegesLength = sizeof(privileges); - BOOL result = 0; - if (::AccessCheck(security, impersonatedToken, genericAccessRights, &mapping, &privileges, - &privilegesLength, &grantedAccess, &result)) { - writable = result != 0; - } - ::CloseHandle(impersonatedToken); - } - - ::CloseHandle(token); +static bool HaveWriteAccess(const std::wstring &path) +{ + bool writable = false; + + const static SECURITY_INFORMATION requestedFileInformation = OWNER_SECURITY_INFORMATION | GROUP_SECURITY_INFORMATION | DACL_SECURITY_INFORMATION; + + DWORD length = 0; + if (!::GetFileSecurityW(path.c_str(), requestedFileInformation, nullptr, 0UL, &length) + && (::GetLastError() == ERROR_INSUFFICIENT_BUFFER)) { + std::string tempBuffer; + tempBuffer.reserve(length); + PSECURITY_DESCRIPTOR security = (PSECURITY_DESCRIPTOR)tempBuffer.data(); + if (security + && ::GetFileSecurity(path.c_str(), requestedFileInformation, security, length, &length)) { + HANDLE token = nullptr; + const static DWORD tokenDesiredAccess = TOKEN_IMPERSONATE | TOKEN_QUERY | TOKEN_DUPLICATE | STANDARD_RIGHTS_READ; + if (!::OpenThreadToken(::GetCurrentThread(), tokenDesiredAccess, TRUE, &token)) { + if (!::OpenProcessToken(::GetCurrentProcess(), tokenDesiredAccess, &token)) { + throw std::runtime_error("Unable to get any thread or process token"); } - } - return writable; -} + } -QString determineProfile(QStringList& arguments, const QSettings& settings) { - QString selectedProfileName = QString::fromUtf8(settings.value("selected_profile", "").toByteArray()); - { // see if there is a profile on the command line - int profileIndex = arguments.indexOf("-p", 1); - if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) { - qDebug("profile overwritten on command line"); - selectedProfileName = arguments.at(profileIndex + 1); + HANDLE impersonatedToken = nullptr; + if (::DuplicateToken(token, SecurityImpersonation, &impersonatedToken)) { + GENERIC_MAPPING mapping = { 0xFFFFFFFF }; + mapping.GenericRead = FILE_GENERIC_READ; + mapping.GenericWrite = FILE_GENERIC_WRITE; + mapping.GenericExecute = FILE_GENERIC_EXECUTE; + mapping.GenericAll = FILE_ALL_ACCESS; + + DWORD genericAccessRights = FILE_GENERIC_WRITE; + ::MapGenericMask(&genericAccessRights, &mapping); + + PRIVILEGE_SET privileges = { 0 }; + DWORD grantedAccess = 0; + DWORD privilegesLength = sizeof(privileges); + BOOL result = 0; + if (::AccessCheck(security, impersonatedToken, genericAccessRights, &mapping, &privileges, &privilegesLength, &grantedAccess, &result)) { + writable = result != 0; } - arguments.removeAt(profileIndex); - arguments.removeAt(profileIndex); - } - if (selectedProfileName.isEmpty()) { - qDebug("no configured profile"); - selectedProfileName = "Default"; - } else { - qDebug("configured profile: %s", qPrintable(selectedProfileName)); + ::CloseHandle(impersonatedToken); + } + + ::CloseHandle(token); } + } + return writable; +} + - return selectedProfileName; +QString determineProfile(QStringList &arguments, const QSettings &settings) +{ + QString selectedProfileName = QString::fromUtf8(settings.value("selected_profile", "").toByteArray()); + { // see if there is a profile on the command line + int profileIndex = arguments.indexOf("-p", 1); + if ((profileIndex != -1) && (profileIndex < arguments.size() - 1)) { + qDebug("profile overwritten on command line"); + selectedProfileName = arguments.at(profileIndex + 1); + } + arguments.removeAt(profileIndex); + arguments.removeAt(profileIndex); + } + if (selectedProfileName.isEmpty()) { + qDebug("no configured profile"); + selectedProfileName = "Default"; + } else { + qDebug("configured profile: %s", qPrintable(selectedProfileName)); + } + + return selectedProfileName; } -MOBase::IPluginGame* selectGame(QSettings& settings, QDir const& gamePath, MOBase::IPluginGame* game) { - settings.setValue("gameName", game->gameName()); - // Sadly, hookdll needs gamePath in order to run. So following code block is - // commented out - /*if (gamePath == game->gameDirectory()) { +MOBase::IPluginGame *selectGame(QSettings &settings, QDir const &gamePath, MOBase::IPluginGame *game) +{ + settings.setValue("gameName", game->gameName()); + //Sadly, hookdll needs gamePath in order to run. So following code block is + //commented out + /*if (gamePath == game->gameDirectory()) { settings.remove("gamePath"); } else*/ { - QString gameDir = gamePath.absolutePath(); - game->setGamePath(gameDir); - settings.setValue("gamePath", QDir::toNativeSeparators(gameDir).toUtf8().constData()); - } - return game; // Woot + QString gameDir = gamePath.absolutePath(); + game->setGamePath(gameDir); + settings.setValue("gamePath", QDir::toNativeSeparators(gameDir).toUtf8().constData()); + } + return game; //Woot } -MOBase::IPluginGame* determineCurrentGame(QString const& moPath, QSettings& settings, PluginContainer const& plugins) { - // Determine what game we are running where. Be very paranoid in case the - // user has done something odd. - // If the game name has been set up, use that. - QString gameName = settings.value("gameName", "").toString(); - if (!gameName.isEmpty()) { - MOBase::IPluginGame* game = plugins.managedGame(gameName); - if (game == nullptr) { - reportError(QObject::tr("Plugin to handle %1 no longer installed").arg(gameName)); - return nullptr; - } - QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); - if (gamePath == "") { - gamePath = game->gameDirectory().absolutePath(); - } - QDir gameDir(gamePath); - if (game->looksValid(gameDir)) { - return selectGame(settings, gameDir, game); - } - } - // gameName wasn't set, or otherwise can't be found. Try looking through all - // the plugins using the gamePath +MOBase::IPluginGame *determineCurrentGame(QString const &moPath, QSettings &settings, PluginContainer const &plugins) +{ + //Determine what game we are running where. Be very paranoid in case the + //user has done something odd. + //If the game name has been set up, use that. + QString gameName = settings.value("gameName", "").toString(); + if (!gameName.isEmpty()) { + MOBase::IPluginGame *game = plugins.managedGame(gameName); + if (game == nullptr) { + reportError(QObject::tr("Plugin to handle %1 no longer installed").arg(gameName)); + return nullptr; + } QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); - if (!gamePath.isEmpty()) { - QDir gameDir(gamePath); - // Look to see if one of the installed games binary file exists in the current - // game directory. - for (IPluginGame* const game : plugins.plugins()) { - if (game->looksValid(gameDir)) { - return selectGame(settings, gameDir, game); - } - } + if (gamePath == "") { + gamePath = game->gameDirectory().absolutePath(); } - - // The following code would try to determine the right game to mange but it would usually find the wrong one - // so it was commented out. - /* - //OK, we are in a new setup or existing info is useless. - //See if MO has been installed inside a game directory + QDir gameDir(gamePath); + if (game->looksValid(gameDir)) { + return selectGame(settings, gameDir, game); + } + } + + //gameName wasn't set, or otherwise can't be found. Try looking through all + //the plugins using the gamePath + QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); + if (!gamePath.isEmpty()) { + QDir gameDir(gamePath); + //Look to see if one of the installed games binary file exists in the current + //game directory. for (IPluginGame * const game : plugins.plugins()) { - if (game->isInstalled() && moPath.startsWith(game->gameDirectory().absolutePath())) { - //Found it. - return selectGame(settings, game->gameDirectory(), game); + if (game->looksValid(gameDir)) { + return selectGame(settings, gameDir, game); } } - - //Try walking up the directory tree to see if MO has been installed inside a game - { - QDir gameDir(moPath); - do { - //Look to see if one of the installed games binary file exists in the current - //directory. - for (IPluginGame * const game : plugins.plugins()) { - if (game->looksValid(gameDir)) { - return selectGame(settings, gameDir, game); - } - } - //OK, chop off the last directory and try again - } while (gameDir.cdUp()); + } + + //The following code would try to determine the right game to mange but it would usually find the wrong one + //so it was commented out. + /* + //OK, we are in a new setup or existing info is useless. + //See if MO has been installed inside a game directory + for (IPluginGame * const game : plugins.plugins()) { + if (game->isInstalled() && moPath.startsWith(game->gameDirectory().absolutePath())) { + //Found it. + return selectGame(settings, game->gameDirectory(), game); } - */ + } - // Then try a selection dialogue. - if (!gamePath.isEmpty() || !gameName.isEmpty()) { - reportError(QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\".") - .arg(gameName) - .arg(gamePath)); + //Try walking up the directory tree to see if MO has been installed inside a game + { + QDir gameDir(moPath); + do { + //Look to see if one of the installed games binary file exists in the current + //directory. + for (IPluginGame * const game : plugins.plugins()) { + if (game->looksValid(gameDir)) { + return selectGame(settings, gameDir, game); + } + } + //OK, chop off the last directory and try again + } while (gameDir.cdUp()); + } + */ + + //Then try a selection dialogue. + if (!gamePath.isEmpty() || !gameName.isEmpty()) { + reportError(QObject::tr("Could not use configuration settings for game \"%1\", path \"%2\"."). + arg(gameName).arg(gamePath)); + } + + SelectionDialog selection(QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32)); + + for (IPluginGame *game : plugins.plugins()) { + if (game->isInstalled()) { + QString path = game->gameDirectory().absolutePath(); + selection.addChoice(game->gameIcon(), game->gameName(), path, QVariant::fromValue(game)); } + } - SelectionDialog selection(QObject::tr("Please select the game to manage"), nullptr, QSize(32, 32)); + selection.addChoice(QString("Browse..."), QString(), QVariant::fromValue(static_cast(nullptr))); - for (IPluginGame* game : plugins.plugins()) { - if (game->isInstalled()) { - QString path = game->gameDirectory().absolutePath(); - selection.addChoice(game->gameIcon(), game->gameName(), path, QVariant::fromValue(game)); - } + while (selection.exec() != QDialog::Rejected) { + IPluginGame * game = selection.getChoiceData().value(); + if (game != nullptr) { + return selectGame(settings, game->gameDirectory(), game); } - selection.addChoice(QString("Browse..."), QString(), QVariant::fromValue(static_cast(nullptr))); - - while (selection.exec() != QDialog::Rejected) { - IPluginGame* game = selection.getChoiceData().value(); - if (game != nullptr) { - return selectGame(settings, game->gameDirectory(), game); - } + gamePath = QFileDialog::getExistingDirectory( + nullptr, QObject::tr("Please select the game to manage"), QString(), + QFileDialog::ShowDirsOnly); - gamePath = QFileDialog::getExistingDirectory(nullptr, QObject::tr("Please select the game to manage"), - QString(), QFileDialog::ShowDirsOnly); - - if (!gamePath.isEmpty()) { - QDir gameDir(gamePath); - for (IPluginGame* const game : plugins.plugins()) { - if (game->looksValid(gameDir)) { - return selectGame(settings, gameDir, game); - } - } - reportError(QObject::tr("No game identified in \"%1\". The directory is required to contain " - "the game binary and its launcher.") - .arg(gamePath)); + if (!gamePath.isEmpty()) { + QDir gameDir(gamePath); + for (IPluginGame * const game : plugins.plugins()) { + if (game->looksValid(gameDir)) { + return selectGame(settings, gameDir, game); } + } + reportError(QObject::tr("No game identified in \"%1\". The directory is required to contain " + "the game binary and its launcher.").arg(gamePath)); } + } - return nullptr; + return nullptr; } + // extend path to include dll directory so plugins don't need a manifest // (using AddDllDirectory would be an alternative to this but it seems fairly // complicated esp. // since it isn't easily accessible on Windows < 8 // SetDllDirectory replaces other search directories and this seems to // propagate to child processes) -void setupPath() { - static const int BUFSIZE = 4096; - - qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators(QCoreApplication::applicationDirPath()))); - - boost::scoped_array oldPath(new TCHAR[BUFSIZE]); - DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE); - if (offset > BUFSIZE) { - oldPath.reset(new TCHAR[offset]); - ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset); - } - - std::wstring newPath(oldPath.get()); - newPath += L";"; - newPath += ToWString(QDir::toNativeSeparators(QCoreApplication::applicationDirPath())).c_str(); - newPath += L"\\dlls"; - - ::SetEnvironmentVariableW(L"PATH", newPath.c_str()); +void setupPath() +{ + static const int BUFSIZE = 4096; + + qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators( + QCoreApplication::applicationDirPath()))); + + boost::scoped_array oldPath(new TCHAR[BUFSIZE]); + DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE); + if (offset > BUFSIZE) { + oldPath.reset(new TCHAR[offset]); + ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset); + } + + std::wstring newPath(oldPath.get()); + newPath += L";"; + newPath += ToWString(QDir::toNativeSeparators( + QCoreApplication::applicationDirPath())) + .c_str(); + newPath += L"\\dlls"; + + ::SetEnvironmentVariableW(L"PATH", newPath.c_str()); } -static void preloadSsl() { - QString appPath = QDir::toNativeSeparators(QCoreApplication::applicationDirPath()); - if (GetModuleHandleA("libeay32.dll")) - qWarning("libeay32.dll already loaded?!"); - else { - QString libeay32 = appPath + "\\libeay32.dll"; - if (!QFile::exists(libeay32)) - qWarning("libeay32.dll not found: %s", qPrintable(libeay32)); - else if (!LoadLibraryW(libeay32.toStdWString().c_str())) - qWarning("failed to load: %s, %d", qPrintable(libeay32), GetLastError()); - } - if (GetModuleHandleA("ssleay32.dll")) - qWarning("ssleay32.dll already loaded?!"); - else { - QString ssleay32 = appPath + "\\ssleay32.dll"; - if (!QFile::exists(ssleay32)) - qWarning("ssleay32.dll not found: %s", qPrintable(ssleay32)); - else if (!LoadLibraryW(ssleay32.toStdWString().c_str())) - qWarning("failed to load: %s, %d", qPrintable(ssleay32), GetLastError()); - } +static void preloadSsl() +{ + QString appPath = QDir::toNativeSeparators(QCoreApplication::applicationDirPath()); + if (GetModuleHandleA("libeay32.dll")) + qWarning("libeay32.dll already loaded?!"); + else { + QString libeay32 = appPath + "\\libeay32.dll"; + if (!QFile::exists(libeay32)) + qWarning("libeay32.dll not found: %s", qPrintable(libeay32)); + else if (!LoadLibraryW(libeay32.toStdWString().c_str())) + qWarning("failed to load: %s, %d", qPrintable(libeay32), GetLastError()); + } + if (GetModuleHandleA("ssleay32.dll")) + qWarning("ssleay32.dll already loaded?!"); + else { + QString ssleay32 = appPath + "\\ssleay32.dll"; + if (!QFile::exists(ssleay32)) + qWarning("ssleay32.dll not found: %s", qPrintable(ssleay32)); + else if (!LoadLibraryW(ssleay32.toStdWString().c_str())) + qWarning("failed to load: %s, %d", qPrintable(ssleay32), GetLastError()); + } } -static QString getVersionDisplayString() { - VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); - return VersionInfo(version.dwFileVersionMS >> 16, version.dwFileVersionMS & 0xFFFF, version.dwFileVersionLS >> 16, - version.dwFileVersionLS & 0xFFFF) - .displayString(); +static QString getVersionDisplayString() +{ + VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); + return VersionInfo(version.dwFileVersionMS >> 16, + version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16, + version.dwFileVersionLS & 0xFFFF).displayString(); } -int runApplication(MOApplication& application, SingleInstance& instance, const QString& splashPath) { +int runApplication(MOApplication &application, SingleInstance &instance, + const QString &splashPath) +{ - qDebug("Starting Mod Organizer version %s revision %s", qPrintable(getVersionDisplayString()), + qDebug("Starting Mod Organizer version %s revision %s", qPrintable(getVersionDisplayString()), #if defined(HGID) - HGID + HGID #elif defined(GITID) - GITID + GITID #else - "unknown" + "unknown" #endif - ); + ); #if !defined(QT_NO_SSL) - preloadSsl(); - qDebug("ssl support: %d", QSslSocket::supportsSsl()); + preloadSsl(); + qDebug("ssl support: %d", QSslSocket::supportsSsl()); #else - qDebug("non-ssl build"); + qDebug("non-ssl build"); #endif - QString dataPath = application.property("dataPath").toString(); - qDebug("data path: %s", qPrintable(dataPath)); + QString dataPath = application.property("dataPath").toString(); + qDebug("data path: %s", qPrintable(dataPath)); - if (!bootstrap()) { - reportError("failed to set up data paths"); - return 1; - } + if (!bootstrap()) { + reportError("failed to set up data paths"); + return 1; + } - QStringList arguments = application.arguments(); + QStringList arguments = application.arguments(); - try { - qDebug("Working directory: %s", qPrintable(QDir::toNativeSeparators(QDir::currentPath()))); - - QSettings settings(dataPath + "/" + QString::fromStdWString(AppConfig::iniFileName()), QSettings::IniFormat); - - // global crashDumpType sits in OrganizerCore to make a bit less ugly to update it when the settings are changed - // during runtime - OrganizerCore::setGlobalCrashDumpsType( - settings.value("Settings/crash_dumps_type", static_cast(CrashDumpsType::Mini)).toInt()); - - qDebug("Loaded settings:"); - settings.beginGroup("Settings"); - for (auto k : settings.allKeys()) - if (!k.contains("username") && !k.contains("password")) - qDebug(" %s=%s", k.toUtf8().data(), settings.value(k).toString().toUtf8().data()); - settings.endGroup(); - - qDebug("initializing core"); - OrganizerCore organizer(settings); - if (!organizer.bootstrap()) { - reportError("failed to set up data paths"); - return 1; - } - qDebug("initialize plugins"); - PluginContainer pluginContainer(&organizer); - pluginContainer.loadPlugins(); + try { + qDebug("Working directory: %s", qPrintable(QDir::toNativeSeparators(QDir::currentPath()))); - MOBase::IPluginGame* game = determineCurrentGame(application.applicationDirPath(), settings, pluginContainer); - if (game == nullptr) { - return 1; + QSettings settings(dataPath + "/" + + QString::fromStdWString(AppConfig::iniFileName()), + QSettings::IniFormat); + + // global crashDumpType sits in OrganizerCore to make a bit less ugly to update it when the settings are changed during runtime + OrganizerCore::setGlobalCrashDumpsType(settings.value("Settings/crash_dumps_type", static_cast(CrashDumpsType::Mini)).toInt()); + + qDebug("Loaded settings:"); + settings.beginGroup("Settings"); + for (auto k : settings.allKeys()) + if (!k.contains("username") && !k.contains("password")) + qDebug(" %s=%s", k.toUtf8().data(), settings.value(k).toString().toUtf8().data()); + settings.endGroup(); + + + qDebug("initializing core"); + OrganizerCore organizer(settings); + if (!organizer.bootstrap()) { + reportError("failed to set up data paths"); + return 1; + } + qDebug("initialize plugins"); + PluginContainer pluginContainer(&organizer); + pluginContainer.loadPlugins(); + + MOBase::IPluginGame *game = determineCurrentGame( + application.applicationDirPath(), settings, pluginContainer); + if (game == nullptr) { + return 1; + } + if (splashPath.startsWith(':')) { + // currently using MO splash, see if the plugin contains one + QString pluginSplash + = QString(":/%1/splash").arg(game->gameShortName()); + QImage image(pluginSplash); + if (!image.isNull()) { + image.save(dataPath + "/splash.png"); + } else { + qDebug("no plugin splash"); + } + } + + organizer.setManagedGame(game); + organizer.createDefaultProfile(); + + if (!settings.contains("game_edition")) { + QStringList editions = game->gameVariants(); + if (editions.size() > 1) { + SelectionDialog selection( + QObject::tr("Please select the game edition you have (MO can't " + "start the game correctly if this is set " + "incorrectly!)"), + nullptr); + int index = 0; + for (const QString &edition : editions) { + selection.addChoice(edition, "", index++); } - if (splashPath.startsWith(':')) { - // currently using MO splash, see if the plugin contains one - QString pluginSplash = QString(":/%1/splash").arg(game->gameShortName()); - QImage image(pluginSplash); - if (!image.isNull()) { - image.save(dataPath + "/splash.png"); - } else { - qDebug("no plugin splash"); - } + if (selection.exec() == QDialog::Rejected) { + return 1; + } else { + settings.setValue("game_edition", selection.getChoiceString()); } + } + } + game->setGameVariant(settings.value("game_edition").toString()); + + qDebug("managing game at %s", qPrintable(QDir::toNativeSeparators( + game->gameDirectory().absolutePath()))); + + organizer.updateExecutablesList(settings); - organizer.setManagedGame(game); - organizer.createDefaultProfile(); - - if (!settings.contains("game_edition")) { - QStringList editions = game->gameVariants(); - if (editions.size() > 1) { - SelectionDialog selection(QObject::tr("Please select the game edition you have (MO can't " - "start the game correctly if this is set " - "incorrectly!)"), - nullptr); - int index = 0; - for (const QString& edition : editions) { - selection.addChoice(edition, "", index++); - } - if (selection.exec() == QDialog::Rejected) { - return 1; - } else { - settings.setValue("game_edition", selection.getChoiceString()); - } - } + QString selectedProfileName = determineProfile(arguments, settings); + organizer.setCurrentProfile(selectedProfileName); + + // if we have a command line parameter, it is either a nxm link or + // a binary to start + if (arguments.size() > 1) { + if (MOShortcut shortcut{ arguments.at(1) }) { + try { + organizer.runShortcut(shortcut); + return 0; + } catch (const std::exception &e) { + reportError( + QObject::tr("failed to start shortcut: %1").arg(e.what())); + return 1; } - game->setGameVariant(settings.value("game_edition").toString()); - - qDebug("managing game at %s", qPrintable(QDir::toNativeSeparators(game->gameDirectory().absolutePath()))); - - organizer.updateExecutablesList(settings); - - QString selectedProfileName = determineProfile(arguments, settings); - organizer.setCurrentProfile(selectedProfileName); - - // if we have a command line parameter, it is either a nxm link or - // a binary to start - if (arguments.size() > 1) { - if (MOShortcut shortcut{arguments.at(1)}) { - try { - organizer.runShortcut(shortcut); - return 0; - } catch (const std::exception& e) { - reportError(QObject::tr("failed to start shortcut: %1").arg(e.what())); - return 1; - } - } else if (OrganizerCore::isNxmLink(arguments.at(1))) { - qDebug("starting download from command line: %s", qPrintable(arguments.at(1))); - organizer.externalMessage(arguments.at(1)); - } else { - QString exeName = arguments.at(1); - qDebug("starting %s from command line", qPrintable(exeName)); - arguments.removeFirst(); // remove application name (ModOrganizer.exe) - arguments.removeFirst(); // remove binary name - // pass the remaining parameters to the binary - try { - organizer.startApplication(exeName, arguments, QString(), QString()); - return 0; - } catch (const std::exception& e) { - reportError(QObject::tr("failed to start application: %1").arg(e.what())); - return 1; - } - } + } else if (OrganizerCore::isNxmLink(arguments.at(1))) { + qDebug("starting download from command line: %s", + qPrintable(arguments.at(1))); + organizer.externalMessage(arguments.at(1)); + } else { + QString exeName = arguments.at(1); + qDebug("starting %s from command line", qPrintable(exeName)); + arguments.removeFirst(); // remove application name (ModOrganizer.exe) + arguments.removeFirst(); // remove binary name + // pass the remaining parameters to the binary + try { + organizer.startApplication(exeName, arguments, QString(), QString()); + return 0; + } catch (const std::exception &e) { + reportError( + QObject::tr("failed to start application: %1").arg(e.what())); + return 1; } + } + } - QPixmap pixmap(splashPath); - QSplashScreen splash(pixmap); - splash.show(); + QPixmap pixmap(splashPath); + QSplashScreen splash(pixmap); + splash.show(); - NexusInterface::instance()->getAccessManager()->startLoginCheck(); + NexusInterface::instance()->getAccessManager()->startLoginCheck(); - qDebug("initializing tutorials"); - TutorialManager::init( - qApp->applicationDirPath() + "/" + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", &organizer); + qDebug("initializing tutorials"); + TutorialManager::init( + qApp->applicationDirPath() + "/" + + QString::fromStdWString(AppConfig::tutorialsPath()) + "/", + &organizer); - if (!application.setStyleFile(settings.value("Settings/style", "").toString())) { - // disable invalid stylesheet - settings.setValue("Settings/style", ""); - } + if (!application.setStyleFile(settings.value("Settings/style", "").toString())) { + // disable invalid stylesheet + settings.setValue("Settings/style", ""); + } - int res = 1; - { // scope to control lifetime of mainwindow - // set up main window and its data structures - MainWindow mainWindow(settings, organizer, pluginContainer); + int res = 1; + { // scope to control lifetime of mainwindow + // set up main window and its data structures + MainWindow mainWindow(settings, organizer, pluginContainer); - QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, SLOT(setStyleFile(QString))); - QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, SLOT(externalMessage(QString))); + QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, + SLOT(setStyleFile(QString))); + QObject::connect(&instance, SIGNAL(messageSent(QString)), &organizer, + SLOT(externalMessage(QString))); - mainWindow.readSettings(); + mainWindow.readSettings(); - qDebug("displaying main window"); - mainWindow.show(); + qDebug("displaying main window"); + mainWindow.show(); - splash.finish(&mainWindow); - return application.exec(); - } - } catch (const std::exception& e) { - reportError(e.what()); - return 1; + splash.finish(&mainWindow); + return application.exec(); } + } catch (const std::exception &e) { + reportError(e.what()); + return 1; + } } -int main(int argc, char* argv[]) { - // Should allow for better scaling of ui with higher resolution displays - QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - if (argc >= 4) { - std::vector arg; - auto args = UntouchedCommandLineArguments(2, arg); - if (arg[0] == L"launch") - return SpawnWaitProcess(arg[1].c_str(), args); - } +int main(int argc, char *argv[]) +{ + //Should allow for better scaling of ui with higher resolution displays + QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); - MOApplication application(argc, argv); - QStringList arguments = application.arguments(); + if (argc >= 4) { + std::vector arg; + auto args = UntouchedCommandLineArguments(2, arg); + if (arg[0] == L"launch") + return SpawnWaitProcess(arg[1].c_str(), args); + } - setupPath(); + MOApplication application(argc, argv); + QStringList arguments = application.arguments(); - bool forcePrimary = false; - if (arguments.contains("update")) { - arguments.removeAll("update"); - forcePrimary = true; - } + setupPath(); - MOShortcut moshortcut{arguments.size() > 1 ? arguments.at(1) : ""}; - - SingleInstance instance(forcePrimary); - if (!instance.primaryInstance()) { - if (moshortcut || arguments.size() > 1 && OrganizerCore::isNxmLink(arguments.at(1))) { - qDebug("not primary instance, sending shortcut/download message"); - instance.sendMessage(arguments.at(1)); - return 0; - } else if (arguments.size() == 1) { - QMessageBox::information(nullptr, QObject::tr("Mod Organizer"), - QObject::tr("An instance of Mod Organizer is already running")); - return 0; - } - } // we continue for the primary instance OR if MO was called with parameters + bool forcePrimary = false; + if (arguments.contains("update")) { + arguments.removeAll("update"); + forcePrimary = true; + } - do { - QString dataPath; + MOShortcut moshortcut{ arguments.size() > 1 ? arguments.at(1) : "" }; - try { - InstanceManager& instanceManager = InstanceManager::instance(); - if (moshortcut && moshortcut.hasInstance()) - instanceManager.overrideInstance(moshortcut.instance()); - dataPath = instanceManager.determineDataPath(); - } catch (const std::exception& e) { - if (strcmp(e.what(), "Canceled")) - QMessageBox::critical(nullptr, QObject::tr("Failed to set up instance"), e.what()); - return 1; - } - application.setProperty("dataPath", dataPath); + SingleInstance instance(forcePrimary); + if (!instance.primaryInstance()) { + if (moshortcut || + arguments.size() > 1 && OrganizerCore::isNxmLink(arguments.at(1))) + { + qDebug("not primary instance, sending shortcut/download message"); + instance.sendMessage(arguments.at(1)); + return 0; + } else if (arguments.size() == 1) { + QMessageBox::information( + nullptr, QObject::tr("Mod Organizer"), + QObject::tr("An instance of Mod Organizer is already running")); + return 0; + } + } // we continue for the primary instance OR if MO was called with parameters - // initialize dump collection only after "dataPath" since the crashes are stored under it - prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); + do { + QString dataPath; - LogBuffer::init(1000, QtDebugMsg, qApp->property("dataPath").toString() + "/logs/mo_interface.log"); + try { + InstanceManager& instanceManager = InstanceManager::instance(); + if (moshortcut && moshortcut.hasInstance()) + instanceManager.overrideInstance(moshortcut.instance()); + dataPath = instanceManager.determineDataPath(); + } catch (const std::exception &e) { + if (strcmp(e.what(),"Canceled")) + QMessageBox::critical(nullptr, QObject::tr("Failed to set up instance"), e.what()); + return 1; + } + application.setProperty("dataPath", dataPath); - QString splash = dataPath + "/splash.png"; - if (!QFile::exists(dataPath + "/splash.png")) { - splash = ":/MO/gui/splash"; - } + // initialize dump collection only after "dataPath" since the crashes are stored under it + prevUnhandledExceptionFilter = SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); - int result = runApplication(application, instance, splash); - if (result != INT_MAX) { - return result; - } - argc = 1; - } while (true); + LogBuffer::init(1000, QtDebugMsg, qApp->property("dataPath").toString() + "/logs/mo_interface.log"); + + QString splash = dataPath + "/splash.png"; + if (!QFile::exists(dataPath + "/splash.png")) { + splash = ":/MO/gui/splash"; + } + + int result = runApplication(application, instance, splash); + if (result != INT_MAX) { + return result; + } + argc = 1; + } while (true); } diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 31fe43ed..abac9317 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -26,64 +26,64 @@ along with Mod Organizer. If not, see . #include "executableslist.h" #include "guessedvalue.h" #include "imodinterface.h" -#include "instancemanager.h" -#include "iplugindiagnose.h" #include "iplugingame.h" +#include "iplugindiagnose.h" #include "isavegame.h" #include "isavegameinfowidget.h" #include "nexusinterface.h" #include "organizercore.h" #include "pluginlistsortproxy.h" #include "previewgenerator.h" -#include "savegameinfo.h" #include "serverinfo.h" +#include "savegameinfo.h" #include "spawn.h" #include "versioninfo.h" +#include "instancemanager.h" -#include "aboutdialog.h" -#include "activatemodsdialog.h" -#include "appconfig.h" -#include "browserdialog.h" +#include "report.h" +#include "modlist.h" +#include "modlistsortproxy.h" +#include "qtgroupingproxy.h" +#include "profile.h" +#include "pluginlist.h" +#include "profilesdialog.h" +#include "editexecutablesdialog.h" #include "categories.h" #include "categoriesdialog.h" -#include "csvbuilder.h" +#include "modinfodialog.h" +#include "overwriteinfodialog.h" +#include "activatemodsdialog.h" #include "downloadlist.h" -#include "downloadlistsortproxy.h" #include "downloadlistwidget.h" #include "downloadlistwidgetcompact.h" -#include "editexecutablesdialog.h" -#include "eventfilter.h" -#include "filedialogmemory.h" -#include "genericicondelegate.h" +#include "messagedialog.h" #include "installationmanager.h" #include "lockeddialog.h" +#include "waitingonclosedialog.h" #include "logbuffer.h" -#include "messagedialog.h" -#include "modflagicondelegate.h" -#include "modinfodialog.h" -#include "modlist.h" -#include "modlistsortproxy.h" +#include "downloadlistsortproxy.h" #include "motddialog.h" -#include "nxmaccessmanager.h" -#include "overwriteinfodialog.h" -#include "pluginlist.h" -#include "previewdialog.h" -#include "problemsdialog.h" -#include "profile.h" -#include "profilesdialog.h" -#include "qtgroupingproxy.h" -#include "report.h" -#include "savetextasdialog.h" -#include "selectiondialog.h" +#include "filedialogmemory.h" #include "tutorialmanager.h" -#include "waitingonclosedialog.h" -#include -#include +#include "modflagicondelegate.h" +#include "genericicondelegate.h" +#include "selectiondialog.h" +#include "csvbuilder.h" +#include "savetextasdialog.h" +#include "problemsdialog.h" +#include "previewdialog.h" +#include "browserdialog.h" +#include "aboutdialog.h" #include -#include +#include "nxmaccessmanager.h" +#include "appconfig.h" +#include "eventfilter.h" +#include +#include +#include #include +#include #include -#include #include #include @@ -98,7 +98,6 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include #include #include @@ -128,15 +127,16 @@ 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 @@ -146,9 +146,9 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include #include +#include #include #include @@ -156,4551 +156,4877 @@ along with Mod Organizer. If not, see . #include #ifndef Q_MOC_RUN +#include #include -#include #include -#include +#include #endif #include +#include #include #include -#include #include #include -#include #include +#include #include #ifdef TEST_MODELS #include "modeltest.h" #endif // TEST_MODELS -#pragma warning(disable : 4428) +#pragma warning( disable : 4428 ) using namespace MOBase; using namespace MOShared; -MainWindow::MainWindow(QSettings& initSettings, OrganizerCore& organizerCore, PluginContainer& pluginContainer, - QWidget* parent) - : QMainWindow(parent), ui(new Ui::MainWindow), m_WasVisible(false), m_Tutorial(this, "MainWindow"), - m_OldProfileIndex(-1), m_ModListGroupingProxy(nullptr), m_ModListSortProxy(nullptr), m_OldExecutableIndex(-1), - m_CategoryFactory(CategoryFactory::instance()), m_ContextItem(nullptr), m_ContextAction(nullptr), - m_CurrentSaveView(nullptr), m_OrganizerCore(organizerCore), m_PluginContainer(pluginContainer), - m_DidUpdateMasterList(false), m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)) { - QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); - QWebEngineProfile::defaultProfile()->setHttpCacheMaximumSize(52428800); - QWebEngineProfile::defaultProfile()->setCachePath(m_OrganizerCore.settings().getCacheDirectory()); - QWebEngineProfile::defaultProfile()->setPersistentStoragePath(m_OrganizerCore.settings().getCacheDirectory()); - ui->setupUi(this); - updateWindowTitle(QString(), false); - - languageChange(m_OrganizerCore.settings().language()); - - m_CategoryFactory.loadCategories(); - - ui->logList->setModel(LogBuffer::instance()); - ui->logList->setColumnWidth(0, 100); - ui->logList->setAutoScroll(true); - ui->logList->scrollToBottom(); - ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); - int splitterSize = - this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value - ui->topLevelSplitter->setSizes(QList() << splitterSize - 100 << 100); - connect(ui->logList->model(), SIGNAL(rowsInserted(const QModelIndex&, int, int)), ui->logList, - SLOT(scrollToBottom())); - connect(ui->logList->model(), SIGNAL(dataChanged(QModelIndex, QModelIndex)), ui->logList, SLOT(scrollToBottom())); - - m_RefreshProgress = new QProgressBar(statusBar()); - m_RefreshProgress->setTextVisible(true); - m_RefreshProgress->setRange(0, 100); - m_RefreshProgress->setValue(0); - m_RefreshProgress->setVisible(false); - statusBar()->addWidget(m_RefreshProgress, 1000); - statusBar()->clearMessage(); - statusBar()->hide(); - - ui->actionEndorseMO->setVisible(false); - - updateProblemsButton(); - - updateToolBar(); - - TaskProgressManager::instance().tryCreateTaskbar(); - - // set up mod list - m_ModListSortProxy = m_OrganizerCore.createModListProxyModel(); - - ui->modList->setModel(m_ModListSortProxy); - - GenericIconDelegate* contentDelegate = - new GenericIconDelegate(ui->modList, Qt::UserRole + 3, ModList::COL_CONTENT, 150); - connect(ui->modList->header(), SIGNAL(sectionResized(int, int, int)), contentDelegate, - SLOT(columnResized(int, int, int))); - ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); - ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(ui->modList)); - ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate); - ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); - bool modListAdjusted = registerWidgetState(ui->modList->objectName(), ui->modList->header(), "mod_list_state"); +MainWindow::MainWindow(QSettings &initSettings + , OrganizerCore &organizerCore + , PluginContainer &pluginContainer + , QWidget *parent) + : QMainWindow(parent) + , ui(new Ui::MainWindow) + , m_WasVisible(false) + , m_Tutorial(this, "MainWindow") + , m_OldProfileIndex(-1) + , m_ModListGroupingProxy(nullptr) + , m_ModListSortProxy(nullptr) + , m_OldExecutableIndex(-1) + , m_CategoryFactory(CategoryFactory::instance()) + , m_ContextItem(nullptr) + , m_ContextAction(nullptr) + , m_CurrentSaveView(nullptr) + , m_OrganizerCore(organizerCore) + , m_PluginContainer(pluginContainer) + , m_DidUpdateMasterList(false) + , m_ArchiveListWriter(std::bind(&MainWindow::saveArchiveList, this)) +{ + QWebEngineProfile::defaultProfile()->setPersistentCookiesPolicy(QWebEngineProfile::NoPersistentCookies); + QWebEngineProfile::defaultProfile()->setHttpCacheMaximumSize(52428800); + QWebEngineProfile::defaultProfile()->setCachePath(m_OrganizerCore.settings().getCacheDirectory()); + QWebEngineProfile::defaultProfile()->setPersistentStoragePath(m_OrganizerCore.settings().getCacheDirectory()); + ui->setupUi(this); + updateWindowTitle(QString(), false); + + languageChange(m_OrganizerCore.settings().language()); + + m_CategoryFactory.loadCategories(); + + ui->logList->setModel(LogBuffer::instance()); + ui->logList->setColumnWidth(0, 100); + ui->logList->setAutoScroll(true); + ui->logList->scrollToBottom(); + ui->logList->addAction(ui->actionCopy_Log_to_Clipboard); + int splitterSize = this->size().height(); // actually total window size, but the splitter doesn't seem to return the true value + ui->topLevelSplitter->setSizes(QList() << splitterSize - 100 << 100); + connect(ui->logList->model(), SIGNAL(rowsInserted(const QModelIndex &, int, int)), + ui->logList, SLOT(scrollToBottom())); + connect(ui->logList->model(), SIGNAL(dataChanged(QModelIndex,QModelIndex)), + ui->logList, SLOT(scrollToBottom())); + + m_RefreshProgress = new QProgressBar(statusBar()); + m_RefreshProgress->setTextVisible(true); + m_RefreshProgress->setRange(0, 100); + m_RefreshProgress->setValue(0); + m_RefreshProgress->setVisible(false); + statusBar()->addWidget(m_RefreshProgress, 1000); + statusBar()->clearMessage(); + statusBar()->hide(); + + ui->actionEndorseMO->setVisible(false); + + updateProblemsButton(); + + updateToolBar(); + + TaskProgressManager::instance().tryCreateTaskbar(); + + // set up mod list + m_ModListSortProxy = m_OrganizerCore.createModListProxyModel(); + + ui->modList->setModel(m_ModListSortProxy); + + GenericIconDelegate *contentDelegate = new GenericIconDelegate(ui->modList, Qt::UserRole + 3, ModList::COL_CONTENT, 150); + connect(ui->modList->header(), SIGNAL(sectionResized(int,int,int)), contentDelegate, SLOT(columnResized(int,int,int))); + ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); + ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(ui->modList)); + ui->modList->setItemDelegateForColumn(ModList::COL_CONTENT, contentDelegate); + ui->modList->header()->installEventFilter(m_OrganizerCore.modList()); + + bool modListAdjusted = registerWidgetState(ui->modList->objectName(), ui->modList->header(), "mod_list_state"); + + if (modListAdjusted) { + // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that + int sectionSize = ui->modList->header()->sectionSize(ModList::COL_CONTENT); + ui->modList->header()->resizeSection(ModList::COL_CONTENT, sectionSize + 1); + ui->modList->header()->resizeSection(ModList::COL_CONTENT, sectionSize); + } else { + // hide these columns by default + ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true); + ui->modList->header()->setSectionHidden(ModList::COL_MODID, true); + ui->modList->header()->setSectionHidden(ModList::COL_INSTALLTIME, true); + } + + ui->modList->header()->setSectionHidden(ModList::COL_NAME, false); // prevent the name-column from being hidden + ui->modList->installEventFilter(m_OrganizerCore.modList()); + + // set up plugin list + m_PluginListSortProxy = m_OrganizerCore.createPluginListProxyModel(); + + ui->espList->setModel(m_PluginListSortProxy); + ui->espList->sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder); + ui->espList->setItemDelegateForColumn(PluginList::COL_FLAGS, new GenericIconDelegate(ui->espList)); + ui->espList->installEventFilter(m_OrganizerCore.pluginList()); - if (modListAdjusted) { - // hack: force the resize-signal to be triggered because restoreState doesn't seem to do that - int sectionSize = ui->modList->header()->sectionSize(ModList::COL_CONTENT); - ui->modList->header()->resizeSection(ModList::COL_CONTENT, sectionSize + 1); - ui->modList->header()->resizeSection(ModList::COL_CONTENT, sectionSize); - } else { - // hide these columns by default - ui->modList->header()->setSectionHidden(ModList::COL_CONTENT, true); - ui->modList->header()->setSectionHidden(ModList::COL_MODID, true); - ui->modList->header()->setSectionHidden(ModList::COL_INSTALLTIME, true); - } - - ui->modList->header()->setSectionHidden(ModList::COL_NAME, false); // prevent the name-column from being hidden - ui->modList->installEventFilter(m_OrganizerCore.modList()); - - // set up plugin list - m_PluginListSortProxy = m_OrganizerCore.createPluginListProxyModel(); + ui->bsaList->setLocalMoveOnly(true); + + bool pluginListAdjusted = registerWidgetState(ui->espList->objectName(), ui->espList->header(), "plugin_list_state"); + registerWidgetState(ui->dataTree->objectName(), ui->dataTree->header()); + registerWidgetState(ui->downloadView->objectName(), + ui->downloadView->header()); + + ui->splitter->setStretchFactor(0, 3); + ui->splitter->setStretchFactor(1, 2); + + resizeLists(modListAdjusted, pluginListAdjusted); - ui->espList->setModel(m_PluginListSortProxy); - ui->espList->sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder); - ui->espList->setItemDelegateForColumn(PluginList::COL_FLAGS, new GenericIconDelegate(ui->espList)); - ui->espList->installEventFilter(m_OrganizerCore.pluginList()); + QMenu *linkMenu = new QMenu(this); + linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar"), this, SLOT(linkToolbar())); + linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Desktop"), this, SLOT(linkDesktop())); + linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu())); + ui->linkButton->setMenu(linkMenu); - ui->bsaList->setLocalMoveOnly(true); + ui->listOptionsBtn->setMenu(modListContextMenu()); - bool pluginListAdjusted = - registerWidgetState(ui->espList->objectName(), ui->espList->header(), "plugin_list_state"); - registerWidgetState(ui->dataTree->objectName(), ui->dataTree->header()); - registerWidgetState(ui->downloadView->objectName(), ui->downloadView->header()); + updateDownloadListDelegate(); - ui->splitter->setStretchFactor(0, 3); - ui->splitter->setStretchFactor(1, 2); + ui->savegameList->installEventFilter(this); + ui->savegameList->setMouseTracking(true); - resizeLists(modListAdjusted, pluginListAdjusted); + // 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; + }); - QMenu* linkMenu = new QMenu(this); - linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Toolbar"), this, SLOT(linkToolbar())); - linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Desktop"), this, SLOT(linkDesktop())); - linkMenu->addAction(QIcon(":/MO/gui/link"), tr("Start Menu"), this, SLOT(linkMenu())); - ui->linkButton->setMenu(linkMenu); + ui->groupCombo->installEventFilter(noWheel); + ui->profileBox->installEventFilter(noWheel); - ui->listOptionsBtn->setMenu(modListContextMenu()); + connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(saveSelectionChanged(QListWidgetItem*))); - updateDownloadListDelegate(); + connect(ui->modList, SIGNAL(dropModeUpdate(bool)), m_OrganizerCore.modList(), SLOT(dropModeUpdate(bool))); - ui->savegameList->installEventFilter(this); - ui->savegameList->setMouseTracking(true); + connect(ui->modList->selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), this, SLOT(modlistSelectionChanged(QModelIndex,QModelIndex))); + connect(m_ModListSortProxy, SIGNAL(filterActive(bool)), this, SLOT(modFilterActive(bool))); + connect(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString))); - // 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; }); + connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), m_PluginListSortProxy, SLOT(updateFilter(QString))); + connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString))); - ui->groupCombo->installEventFilter(noWheel); - ui->profileBox->installEventFilter(noWheel); + connect(ui->dataTree, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(expandDataTreeItem(QTreeWidgetItem*))); - connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)), this, - SLOT(saveSelectionChanged(QListWidgetItem*))); + connect(m_OrganizerCore.directoryRefresher(), SIGNAL(refreshed()), this, SLOT(directory_refreshed())); + connect(m_OrganizerCore.directoryRefresher(), SIGNAL(progress(int)), this, SLOT(refresher_progress(int))); + connect(m_OrganizerCore.directoryRefresher(), SIGNAL(error(QString)), this, SLOT(showError(QString))); - connect(ui->modList, SIGNAL(dropModeUpdate(bool)), m_OrganizerCore.modList(), SLOT(dropModeUpdate(bool))); + connect(&m_SavesWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(refreshSavesIfOpen())); - connect(ui->modList->selectionModel(), SIGNAL(currentChanged(QModelIndex, QModelIndex)), this, - SLOT(modlistSelectionChanged(QModelIndex, QModelIndex))); - connect(m_ModListSortProxy, SIGNAL(filterActive(bool)), this, SLOT(modFilterActive(bool))); - connect(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString))); + connect(&m_OrganizerCore.settings(), SIGNAL(languageChanged(QString)), this, SLOT(languageChange(QString))); + connect(&m_OrganizerCore.settings(), SIGNAL(styleChanged(QString)), this, SIGNAL(styleChanged(QString))); - connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), m_PluginListSortProxy, SLOT(updateFilter(QString))); - connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(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(ui->dataTree, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(expandDataTreeItem(QTreeWidgetItem*))); + connect(NexusInterface::instance(), SIGNAL(requestNXMDownload(QString)), &m_OrganizerCore, SLOT(downloadRequestedNXM(QString))); + connect(NexusInterface::instance(), SIGNAL(nxmDownloadURLsAvailable(int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(int,int,QVariant,QVariant,int))); + connect(NexusInterface::instance(), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusLogin())); + connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString))); + connect(NexusInterface::instance()->getAccessManager(), SIGNAL(credentialsReceived(const QString&, bool)), + this, SLOT(updateWindowTitle(const QString&, bool))); - connect(m_OrganizerCore.directoryRefresher(), SIGNAL(refreshed()), this, SLOT(directory_refreshed())); - connect(m_OrganizerCore.directoryRefresher(), SIGNAL(progress(int)), this, SLOT(refresher_progress(int))); - connect(m_OrganizerCore.directoryRefresher(), SIGNAL(error(QString)), this, SLOT(showError(QString))); + connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString))); + connect(ui->tabWidget, SIGNAL(currentChanged(int)), &TutorialManager::instance(), SIGNAL(tabChanged(int))); + connect(ui->modList->header(), SIGNAL(sortIndicatorChanged(int,Qt::SortOrder)), this, SLOT(modListSortIndicatorChanged(int,Qt::SortOrder))); + connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, SLOT(toolBar_customContextMenuRequested(QPoint))); - connect(&m_SavesWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(refreshSavesIfOpen())); + connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); + connect(&m_OrganizerCore, &OrganizerCore::close, this, &QMainWindow::close); - connect(&m_OrganizerCore.settings(), SIGNAL(languageChanged(QString)), this, SLOT(languageChange(QString))); - connect(&m_OrganizerCore.settings(), SIGNAL(styleChanged(QString)), this, SIGNAL(styleChanged(QString))); + connect(&m_IntegratedBrowser, SIGNAL(requestDownload(QUrl,QNetworkReply*)), &m_OrganizerCore, SLOT(requestDownload(QUrl,QNetworkReply*))); - 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(this, SIGNAL(styleChanged(QString)), this, SLOT(updateStyle(QString))); - connect(NexusInterface::instance(), SIGNAL(requestNXMDownload(QString)), &m_OrganizerCore, - SLOT(downloadRequestedNXM(QString))); - connect(NexusInterface::instance(), SIGNAL(nxmDownloadURLsAvailable(int, int, QVariant, QVariant, int)), this, - SLOT(nxmDownloadURLs(int, int, QVariant, QVariant, int))); - connect(NexusInterface::instance(), SIGNAL(needLogin()), &m_OrganizerCore, SLOT(nexusLogin())); - connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, - SLOT(loginFailed(QString))); - connect(NexusInterface::instance()->getAccessManager(), SIGNAL(credentialsReceived(const QString&, bool)), this, - SLOT(updateWindowTitle(const QString&, bool))); + m_CheckBSATimer.setSingleShot(true); + connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList())); - connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, - SLOT(windowTutorialFinished(QString))); - connect(ui->tabWidget, SIGNAL(currentChanged(int)), &TutorialManager::instance(), SIGNAL(tabChanged(int))); - connect(ui->modList->header(), SIGNAL(sortIndicatorChanged(int, Qt::SortOrder)), this, - SLOT(modListSortIndicatorChanged(int, Qt::SortOrder))); - connect(ui->toolBar, SIGNAL(customContextMenuRequested(QPoint)), this, - SLOT(toolBar_customContextMenuRequested(QPoint))); + connect(ui->espList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(esplistSelectionsChanged(QItemSelection))); + connect(ui->modList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, SLOT(modlistSelectionsChanged(QItemSelection))); - connect(&m_OrganizerCore, &OrganizerCore::modInstalled, this, &MainWindow::modInstalled); - connect(&m_OrganizerCore, &OrganizerCore::close, this, &QMainWindow::close); + m_UpdateProblemsTimer.setSingleShot(true); + connect(&m_UpdateProblemsTimer, SIGNAL(timeout()), this, SLOT(updateProblemsButton())); - connect(&m_IntegratedBrowser, SIGNAL(requestDownload(QUrl, QNetworkReply*)), &m_OrganizerCore, - SLOT(requestDownload(QUrl, QNetworkReply*))); + m_SaveMetaTimer.setSingleShot(false); + connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas())); + m_SaveMetaTimer.start(5000); - connect(this, SIGNAL(styleChanged(QString)), this, SLOT(updateStyle(QString))); + setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool()); + FileDialogMemory::restore(initSettings); - m_CheckBSATimer.setSingleShot(true); - connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList())); + fixCategories(); - connect(ui->espList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, - SLOT(esplistSelectionsChanged(QItemSelection))); - connect(ui->modList->selectionModel(), SIGNAL(selectionChanged(QItemSelection, QItemSelection)), this, - SLOT(modlistSelectionsChanged(QItemSelection))); + m_StartTime = QTime::currentTime(); - m_UpdateProblemsTimer.setSingleShot(true); - connect(&m_UpdateProblemsTimer, SIGNAL(timeout()), this, SLOT(updateProblemsButton())); + m_Tutorial.expose("modList", m_OrganizerCore.modList()); + m_Tutorial.expose("espList", m_OrganizerCore.pluginList()); - m_SaveMetaTimer.setSingleShot(false); - connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas())); - m_SaveMetaTimer.start(5000); + m_OrganizerCore.setUserInterface(this, this); + for (const QString &fileName : m_PluginContainer.pluginFileNames()) { + installTranslator(QFileInfo(fileName).baseName()); + } + for (IPluginTool *toolPlugin : m_PluginContainer.plugins()) { + registerPluginTool(toolPlugin); + } - setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool()); - FileDialogMemory::restore(initSettings); + for (IPluginModPage *modPagePlugin : m_PluginContainer.plugins()) { + registerModPage(modPagePlugin); + } - fixCategories(); + // refresh profiles so the current profile can be activated + refreshProfiles(false); - m_StartTime = QTime::currentTime(); - - m_Tutorial.expose("modList", m_OrganizerCore.modList()); - m_Tutorial.expose("espList", m_OrganizerCore.pluginList()); - - m_OrganizerCore.setUserInterface(this, this); - for (const QString& fileName : m_PluginContainer.pluginFileNames()) { - installTranslator(QFileInfo(fileName).baseName()); - } - for (IPluginTool* toolPlugin : m_PluginContainer.plugins()) { - registerPluginTool(toolPlugin); - } + ui->profileBox->setCurrentText(m_OrganizerCore.currentProfile()->name()); - for (IPluginModPage* modPagePlugin : m_PluginContainer.plugins()) { - registerModPage(modPagePlugin); - } + refreshExecutablesList(); + updateToolBar(); +} - // refresh profiles so the current profile can be activated - refreshProfiles(false); - ui->profileBox->setCurrentText(m_OrganizerCore.currentProfile()->name()); +MainWindow::~MainWindow() +{ + cleanup(); - refreshExecutablesList(); - updateToolBar(); + m_PluginContainer.setUserInterface(nullptr, nullptr); + m_OrganizerCore.setUserInterface(nullptr, nullptr); + m_IntegratedBrowser.close(); + delete ui; } -MainWindow::~MainWindow() { - cleanup(); - - m_PluginContainer.setUserInterface(nullptr, nullptr); - m_OrganizerCore.setUserInterface(nullptr, nullptr); - m_IntegratedBrowser.close(); - delete ui; -} -void MainWindow::updateWindowTitle(const QString& accountName, bool premium) { - QString title = QString("%1 Mod Organizer v%2") - .arg(m_OrganizerCore.managedGame()->gameName(), m_OrganizerCore.getVersion().displayString()); +void MainWindow::updateWindowTitle(const QString &accountName, bool premium) +{ + QString title = QString("%1 Mod Organizer v%2").arg( + m_OrganizerCore.managedGame()->gameName(), + m_OrganizerCore.getVersion().displayString()); - if (!accountName.isEmpty()) { - title.append(QString(" (%1%2)").arg(accountName, premium ? "*" : "")); - } + if (!accountName.isEmpty()) { + title.append(QString(" (%1%2)").arg(accountName, premium ? "*" : "")); + } - this->setWindowTitle(title); + this->setWindowTitle(title); } -void MainWindow::disconnectPlugins() { - if (ui->actionTool->menu() != nullptr) { - ui->actionTool->menu()->clear(); - } + +void MainWindow::disconnectPlugins() +{ + if (ui->actionTool->menu() != nullptr) { + ui->actionTool->menu()->clear(); + } } -void MainWindow::resizeLists(bool modListCustom, bool pluginListCustom) { - if (!modListCustom) { - // resize mod list to fit content - for (int i = 0; i < ui->modList->header()->count(); ++i) { - ui->modList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } - ui->modList->header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch); - } - // ensure the columns aren't so small you can't see them any more +void MainWindow::resizeLists(bool modListCustom, bool pluginListCustom) +{ + if (!modListCustom) { + // resize mod list to fit content for (int i = 0; i < ui->modList->header()->count(); ++i) { - if (ui->modList->header()->sectionSize(i) < 10) { - ui->modList->header()->resizeSection(i, 10); - } - } - - if (!pluginListCustom) { - // resize plugin list to fit content - for (int i = 0; i < ui->espList->header()->count(); ++i) { - ui->espList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } - ui->espList->header()->setSectionResizeMode(0, QHeaderView::Stretch); + ui->modList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); } -} + ui->modList->header()->setSectionResizeMode(ModList::COL_NAME, QHeaderView::Stretch); + } -void MainWindow::allowListResize() { - // allow resize on mod list - for (int i = 0; i < ui->modList->header()->count(); ++i) { - ui->modList->header()->setSectionResizeMode(i, QHeaderView::Interactive); + // ensure the columns aren't so small you can't see them any more + for (int i = 0; i < ui->modList->header()->count(); ++i) { + if (ui->modList->header()->sectionSize(i) < 10) { + ui->modList->header()->resizeSection(i, 10); } - // ui->modList->header()->setSectionResizeMode(ui->modList->header()->count() - 1, QHeaderView::Stretch); - ui->modList->header()->setStretchLastSection(true); + } - // allow resize on plugin list + if (!pluginListCustom) { + // resize plugin list to fit content for (int i = 0; i < ui->espList->header()->count(); ++i) { - ui->espList->header()->setSectionResizeMode(i, QHeaderView::Interactive); + ui->espList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); } - // ui->espList->header()->setSectionResizeMode(ui->espList->header()->count() - 1, QHeaderView::Stretch); - ui->espList->header()->setStretchLastSection(true); -} - -void MainWindow::updateStyle(const QString&) { - // no effect? - ensurePolished(); -} + ui->espList->header()->setSectionResizeMode(0, QHeaderView::Stretch); + } +} -void MainWindow::resizeEvent(QResizeEvent* event) { - m_Tutorial.resize(event->size()); - QMainWindow::resizeEvent(event); -} -static QModelIndex mapToModel(const QAbstractItemModel* targetModel, QModelIndex idx) { - QModelIndex result = idx; - const QAbstractItemModel* model = idx.model(); - while (model != targetModel) { - if (model == nullptr) { - return QModelIndex(); - } - const QAbstractProxyModel* proxyModel = qobject_cast(model); - if (proxyModel == nullptr) { - return QModelIndex(); - } - result = proxyModel->mapToSource(result); - model = proxyModel->sourceModel(); - } - return result; -} - -void MainWindow::actionToToolButton(QAction*& sourceAction) { - QToolButton* button = new QToolButton(ui->toolBar); - button->setObjectName(sourceAction->objectName()); - button->setIcon(sourceAction->icon()); - button->setText(sourceAction->text()); - button->setPopupMode(QToolButton::InstantPopup); - button->setToolButtonStyle(ui->toolBar->toolButtonStyle()); - button->setToolTip(sourceAction->toolTip()); - button->setShortcut(sourceAction->shortcut()); - QMenu* buttonMenu = new QMenu(sourceAction->text(), button); - button->setMenu(buttonMenu); - QAction* newAction = ui->toolBar->insertWidget(sourceAction, button); - newAction->setObjectName(sourceAction->objectName()); - newAction->setIcon(sourceAction->icon()); - newAction->setText(sourceAction->text()); - newAction->setToolTip(sourceAction->toolTip()); - newAction->setShortcut(sourceAction->shortcut()); - ui->toolBar->removeAction(sourceAction); - sourceAction->deleteLater(); - sourceAction = newAction; -} - -void MainWindow::updateToolBar() { - for (QAction* action : ui->toolBar->actions()) { - if (action->objectName().startsWith("custom__")) { - ui->toolBar->removeAction(action); +void MainWindow::allowListResize() +{ + // allow resize on mod list + for (int i = 0; i < ui->modList->header()->count(); ++i) { + ui->modList->header()->setSectionResizeMode(i, QHeaderView::Interactive); + } + //ui->modList->header()->setSectionResizeMode(ui->modList->header()->count() - 1, QHeaderView::Stretch); + ui->modList->header()->setStretchLastSection(true); + + + // allow resize on plugin list + for (int i = 0; i < ui->espList->header()->count(); ++i) { + ui->espList->header()->setSectionResizeMode(i, QHeaderView::Interactive); + } + //ui->espList->header()->setSectionResizeMode(ui->espList->header()->count() - 1, QHeaderView::Stretch); + ui->espList->header()->setStretchLastSection(true); +} + +void MainWindow::updateStyle(const QString&) +{ + // no effect? + ensurePolished(); +} + +void MainWindow::resizeEvent(QResizeEvent *event) +{ + m_Tutorial.resize(event->size()); + QMainWindow::resizeEvent(event); +} + + +static QModelIndex mapToModel(const QAbstractItemModel *targetModel, QModelIndex idx) +{ + QModelIndex result = idx; + const QAbstractItemModel *model = idx.model(); + while (model != targetModel) { + if (model == nullptr) { + return QModelIndex(); + } + const QAbstractProxyModel *proxyModel = qobject_cast(model); + if (proxyModel == nullptr) { + return QModelIndex(); + } + result = proxyModel->mapToSource(result); + model = proxyModel->sourceModel(); + } + return result; +} + + +void MainWindow::actionToToolButton(QAction *&sourceAction) +{ + QToolButton *button = new QToolButton(ui->toolBar); + button->setObjectName(sourceAction->objectName()); + button->setIcon(sourceAction->icon()); + button->setText(sourceAction->text()); + button->setPopupMode(QToolButton::InstantPopup); + button->setToolButtonStyle(ui->toolBar->toolButtonStyle()); + button->setToolTip(sourceAction->toolTip()); + button->setShortcut(sourceAction->shortcut()); + QMenu *buttonMenu = new QMenu(sourceAction->text(), button); + button->setMenu(buttonMenu); + QAction *newAction = ui->toolBar->insertWidget(sourceAction, button); + newAction->setObjectName(sourceAction->objectName()); + newAction->setIcon(sourceAction->icon()); + newAction->setText(sourceAction->text()); + newAction->setToolTip(sourceAction->toolTip()); + newAction->setShortcut(sourceAction->shortcut()); + ui->toolBar->removeAction(sourceAction); + sourceAction->deleteLater(); + sourceAction = newAction; +} + +void MainWindow::updateToolBar() +{ + for (QAction *action : ui->toolBar->actions()) { + if (action->objectName().startsWith("custom__")) { + ui->toolBar->removeAction(action); + } + } + + QWidget *spacer = new QWidget(ui->toolBar); + spacer->setObjectName("custom__spacer"); + spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); + QWidget *widget = ui->toolBar->widgetForAction(ui->actionTool); + QToolButton *toolBtn = qobject_cast(widget); + + if (toolBtn->menu() == nullptr) { + actionToToolButton(ui->actionTool); + } + + actionToToolButton(ui->actionHelp); + createHelpWidget(); + + for (QAction *action : ui->toolBar->actions()) { + if (action->isSeparator()) { + // insert spacers + ui->toolBar->insertWidget(action, spacer); + + std::vector::iterator begin, end; + m_OrganizerCore.executablesList()->getExecutables(begin, end); + for (auto iter = begin; iter != end; ++iter) { + if (iter->isShownOnToolbar()) { + QAction *exeAction = new QAction(iconForExecutable(iter->m_BinaryInfo.filePath()), + iter->m_Title, + ui->toolBar); + exeAction->setObjectName(QString("custom__") + iter->m_Title); + if (!connect(exeAction, SIGNAL(triggered()), this, SLOT(startExeAction()))) { + qDebug("failed to connect trigger?"); + } + ui->toolBar->insertAction(action, exeAction); } + } } + } +} - QWidget* spacer = new QWidget(ui->toolBar); - spacer->setObjectName("custom__spacer"); - spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); - QWidget* widget = ui->toolBar->widgetForAction(ui->actionTool); - QToolButton* toolBtn = qobject_cast(widget); - if (toolBtn->menu() == nullptr) { - actionToToolButton(ui->actionTool); - } +void MainWindow::scheduleUpdateButton() +{ + if (!m_UpdateProblemsTimer.isActive()) { + m_UpdateProblemsTimer.start(1000); + } +} - actionToToolButton(ui->actionHelp); - createHelpWidget(); - for (QAction* action : ui->toolBar->actions()) { - if (action->isSeparator()) { - // insert spacers - ui->toolBar->insertWidget(action, spacer); +void MainWindow::updateProblemsButton() +{ + size_t numProblems = checkForProblems(); + if (numProblems > 0) { + ui->actionProblems->setEnabled(true); + ui->actionProblems->setIconText(tr("Problems")); + ui->actionProblems->setToolTip(tr("There are potential problems with your setup")); - std::vector::iterator begin, end; - m_OrganizerCore.executablesList()->getExecutables(begin, end); - for (auto iter = begin; iter != end; ++iter) { - if (iter->isShownOnToolbar()) { - QAction* exeAction = - new QAction(iconForExecutable(iter->m_BinaryInfo.filePath()), iter->m_Title, ui->toolBar); - exeAction->setObjectName(QString("custom__") + iter->m_Title); - if (!connect(exeAction, SIGNAL(triggered()), this, SLOT(startExeAction()))) { - qDebug("failed to connect trigger?"); - } - ui->toolBar->insertAction(action, exeAction); - } - } - } + QPixmap mergedIcon = QPixmap(":/MO/gui/warning").scaled(64, 64); + { + QPainter painter(&mergedIcon); + std::string badgeName = std::string(":/MO/gui/badge_") + (numProblems < 10 ? std::to_string(static_cast(numProblems)) : "more"); + painter.drawPixmap(32, 32, 32, 32, QPixmap(badgeName.c_str())); } + ui->actionProblems->setIcon(QIcon(mergedIcon)); + } else { + ui->actionProblems->setEnabled(false); + ui->actionProblems->setIconText(tr("No Problems")); + ui->actionProblems->setToolTip(tr("Everything seems to be in order")); + ui->actionProblems->setIcon(QIcon(":/MO/gui/warning")); + } } -void MainWindow::scheduleUpdateButton() { - if (!m_UpdateProblemsTimer.isActive()) { - m_UpdateProblemsTimer.start(1000); - } -} -void MainWindow::updateProblemsButton() { - size_t numProblems = checkForProblems(); - if (numProblems > 0) { - ui->actionProblems->setEnabled(true); - ui->actionProblems->setIconText(tr("Problems")); - ui->actionProblems->setToolTip(tr("There are potential problems with your setup")); +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); - QPixmap mergedIcon = QPixmap(":/MO/gui/warning").scaled(64, 64); - { - QPainter painter(&mergedIcon); - std::string badgeName = std::string(":/MO/gui/badge_") + - (numProblems < 10 ? std::to_string(static_cast(numProblems)) : "more"); - painter.drawPixmap(32, 32, 32, 32, QPixmap(badgeName.c_str())); + if (files.count() > 0) { + logFile = files.at(0).absoluteFilePath(); + QFile file(logFile); + if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { + char buffer[1024]; + int line = 0; + while (!file.atEnd()) { + file.readLine(buffer, 1024); + if (strncmp(buffer, "ERROR", 5) == 0) { + return true; } - ui->actionProblems->setIcon(QIcon(mergedIcon)); - } else { - ui->actionProblems->setEnabled(false); - ui->actionProblems->setIconText(tr("No Problems")); - ui->actionProblems->setToolTip(tr("Everything seems to be in order")); - ui->actionProblems->setIcon(QIcon(":/MO/gui/warning")); - } -} - -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); - - if (files.count() > 0) { - logFile = files.at(0).absoluteFilePath(); - QFile file(logFile); - if (file.open(QIODevice::ReadOnly | QIODevice::Text)) { - char buffer[1024]; - int line = 0; - while (!file.atEnd()) { - file.readLine(buffer, 1024); - if (strncmp(buffer, "ERROR", 5) == 0) { - return true; - } - // prevent this function from taking forever - if (line++ >= 50000) { - break; - } - } + // prevent this function from taking forever + if (line++ >= 50000) { + break; } + } } + } - return false; + return false; } -size_t MainWindow::checkForProblems() { - size_t numProblems = 0; - for (IPluginDiagnose* diagnose : m_PluginContainer.plugins()) { - numProblems += diagnose->activeProblems().size(); - } - return numProblems; -} -void MainWindow::about() { - AboutDialog dialog(m_OrganizerCore.getVersion().displayString(), this); - dialog.exec(); -} +size_t MainWindow::checkForProblems() +{ + size_t numProblems = 0; + for (IPluginDiagnose *diagnose : m_PluginContainer.plugins()) { + numProblems += diagnose->activeProblems().size(); + } + return numProblems; +} -void MainWindow::createHelpWidget() { - QToolButton* toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionHelp)); - QMenu* buttonMenu = toolBtn->menu(); - if (buttonMenu == nullptr) { - return; - } - buttonMenu->clear(); +void MainWindow::about() +{ + AboutDialog dialog(m_OrganizerCore.getVersion().displayString(), this); + dialog.exec(); +} - QAction* helpAction = new QAction(tr("Help on UI"), buttonMenu); - connect(helpAction, SIGNAL(triggered()), this, SLOT(helpTriggered())); - buttonMenu->addAction(helpAction); - QAction* wikiAction = new QAction(tr("Documentation Wiki"), buttonMenu); - connect(wikiAction, SIGNAL(triggered()), this, SLOT(wikiTriggered())); - buttonMenu->addAction(wikiAction); +void MainWindow::createHelpWidget() +{ + QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionHelp)); + QMenu *buttonMenu = toolBtn->menu(); + if (buttonMenu == nullptr) { + return; + } + buttonMenu->clear(); - QAction* issueAction = new QAction(tr("Report Issue"), buttonMenu); - connect(issueAction, SIGNAL(triggered()), this, SLOT(issueTriggered())); - buttonMenu->addAction(issueAction); + QAction *helpAction = new QAction(tr("Help on UI"), buttonMenu); + connect(helpAction, SIGNAL(triggered()), this, SLOT(helpTriggered())); + buttonMenu->addAction(helpAction); - QMenu* tutorialMenu = new QMenu(tr("Tutorials"), buttonMenu); + QAction *wikiAction = new QAction(tr("Documentation Wiki"), buttonMenu); + connect(wikiAction, SIGNAL(triggered()), this, SLOT(wikiTriggered())); + buttonMenu->addAction(wikiAction); - typedef std::vector> ActionList; + QAction *issueAction = new QAction(tr("Report Issue"), buttonMenu); + connect(issueAction, SIGNAL(triggered()), this, SLOT(issueTriggered())); + buttonMenu->addAction(issueAction); - ActionList tutorials; + QMenu *tutorialMenu = new QMenu(tr("Tutorials"), buttonMenu); - QDirIterator dirIter(QApplication::applicationDirPath() + "/tutorials", QStringList("*.js"), QDir::Files); - while (dirIter.hasNext()) { - dirIter.next(); - QString fileName = dirIter.fileName(); + typedef std::vector > ActionList; - QFile file(dirIter.filePath()); - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open " << fileName; - continue; - } - QString firstLine = QString::fromUtf8(file.readLine()); - if (firstLine.startsWith("//TL")) { - QStringList params = firstLine.mid(4).trimmed().split('#'); - if (params.size() != 2) { - qCritical() << "invalid header line for tutorial " << fileName << " expected 2 parameters"; - continue; - } - QAction* tutAction = new QAction(params.at(0), tutorialMenu); - tutAction->setData(fileName); - tutorials.push_back(std::make_pair(params.at(1).toInt(), tutAction)); - } - } + ActionList tutorials; - std::sort( - tutorials.begin(), tutorials.end(), - [](const ActionList::value_type& LHS, const ActionList::value_type& RHS) { return LHS.first < RHS.first; }); + QDirIterator dirIter(QApplication::applicationDirPath() + "/tutorials", QStringList("*.js"), QDir::Files); + while (dirIter.hasNext()) { + dirIter.next(); + QString fileName = dirIter.fileName(); - for (auto iter = tutorials.begin(); iter != tutorials.end(); ++iter) { - connect(iter->second, SIGNAL(triggered()), this, SLOT(tutorialTriggered())); - tutorialMenu->addAction(iter->second); + QFile file(dirIter.filePath()); + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open " << fileName; + continue; } + QString firstLine = QString::fromUtf8(file.readLine()); + if (firstLine.startsWith("//TL")) { + QStringList params = firstLine.mid(4).trimmed().split('#'); + if (params.size() != 2) { + qCritical() << "invalid header line for tutorial " << fileName << " expected 2 parameters"; + continue; + } + QAction *tutAction = new QAction(params.at(0), tutorialMenu); + tutAction->setData(fileName); + tutorials.push_back(std::make_pair(params.at(1).toInt(), tutAction)); + } + } - buttonMenu->addMenu(tutorialMenu); - buttonMenu->addAction(tr("About"), this, SLOT(about())); - buttonMenu->addAction(tr("About Qt"), qApp, SLOT(aboutQt())); -} + std::sort(tutorials.begin(), tutorials.end(), + [] (const ActionList::value_type &LHS, const ActionList::value_type &RHS) { + return LHS.first < RHS.first; } ); -void MainWindow::modFilterActive(bool filterActive) { - if (filterActive) { - // m_OrganizerCore.modList()->setOverwriteMarkers(std::set(), std::set()); - ui->modList->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); - } else if (ui->groupCombo->currentIndex() != 0) { - ui->modList->setStyleSheet("QTreeView { border: 2px ridge #337733; }"); - } else { - ui->modList->setStyleSheet(""); - } -} + for (auto iter = tutorials.begin(); iter != tutorials.end(); ++iter) { + connect(iter->second, SIGNAL(triggered()), this, SLOT(tutorialTriggered())); + tutorialMenu->addAction(iter->second); + } -void MainWindow::espFilterChanged(const QString& filter) { - if (!filter.isEmpty()) { - ui->espList->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); - } else { - ui->espList->setStyleSheet(""); - } + buttonMenu->addMenu(tutorialMenu); + buttonMenu->addAction(tr("About"), this, SLOT(about())); + buttonMenu->addAction(tr("About Qt"), qApp, SLOT(aboutQt())); } -void MainWindow::downloadFilterChanged(const QString& filter) { - if (!filter.isEmpty()) { - ui->downloadView->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); - } else { - ui->downloadView->setStyleSheet(""); - } +void MainWindow::modFilterActive(bool filterActive) +{ + if (filterActive) { +// m_OrganizerCore.modList()->setOverwriteMarkers(std::set(), std::set()); + ui->modList->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); + } else if (ui->groupCombo->currentIndex() != 0) { + ui->modList->setStyleSheet("QTreeView { border: 2px ridge #337733; }"); + } else { + ui->modList->setStyleSheet(""); + } } -void MainWindow::expandModList(const QModelIndex& index) { - QAbstractItemModel* model = ui->modList->model(); +void MainWindow::espFilterChanged(const QString &filter) +{ + if (!filter.isEmpty()) { + ui->espList->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); + } else { + ui->espList->setStyleSheet(""); + } +} + +void MainWindow::downloadFilterChanged(const QString &filter) +{ + if (!filter.isEmpty()) { + ui->downloadView->setStyleSheet("QTreeView { border: 2px ridge #f00; }"); + } else { + ui->downloadView->setStyleSheet(""); + } +} + +void MainWindow::expandModList(const QModelIndex &index) +{ + QAbstractItemModel *model = ui->modList->model(); #pragma message("why is this so complicated? mapping the index doesn't work, probably a bug in QtGroupingProxy?") - for (int i = 0; i < model->rowCount(); ++i) { - QModelIndex targetIdx = model->index(i, 0); - if (model->data(targetIdx).toString() == index.data().toString()) { - ui->modList->expand(targetIdx); - break; - } + for (int i = 0; i < model->rowCount(); ++i) { + QModelIndex targetIdx = model->index(i, 0); + if (model->data(targetIdx).toString() == index.data().toString()) { + ui->modList->expand(targetIdx); + break; } + } } -bool MainWindow::addProfile() { - QComboBox* profileBox = findChild("profileBox"); - bool okClicked = false; - QString name = QInputDialog::getText(this, tr("Name"), tr("Please enter a name for the new profile"), - QLineEdit::Normal, QString(), &okClicked); - if (okClicked && (name.size() > 0)) { - try { - profileBox->addItem(name); - profileBox->setCurrentIndex(profileBox->count() - 1); - return true; - } catch (const std::exception& e) { - reportError(tr("failed to create profile: %1").arg(e.what())); - return false; - } - } else { - return false; +bool MainWindow::addProfile() +{ + QComboBox *profileBox = findChild("profileBox"); + bool okClicked = false; + + QString name = QInputDialog::getText(this, tr("Name"), + tr("Please enter a name for the new profile"), + QLineEdit::Normal, QString(), &okClicked); + if (okClicked && (name.size() > 0)) { + try { + profileBox->addItem(name); + profileBox->setCurrentIndex(profileBox->count() - 1); + return true; + } catch (const std::exception& e) { + reportError(tr("failed to create profile: %1").arg(e.what())); + return false; } + } else { + return false; + } } -void MainWindow::hookUpWindowTutorials() { - QDirIterator dirIter(QApplication::applicationDirPath() + "/tutorials", QStringList("*.js"), QDir::Files); - while (dirIter.hasNext()) { - dirIter.next(); - QString fileName = dirIter.fileName(); - QFile file(dirIter.filePath()); - if (!file.open(QIODevice::ReadOnly)) { - qCritical() << "Failed to open " << fileName; - continue; - } - QString firstLine = QString::fromUtf8(file.readLine()); - if (firstLine.startsWith("//WIN")) { - QString windowName = firstLine.mid(6).trimmed(); - if (!m_OrganizerCore.settings() - .directInterface() - .value("CompletedWindowTutorials/" + windowName, false) - .toBool()) { - TutorialManager::instance().activateTutorial(windowName, fileName); - } - } +void MainWindow::hookUpWindowTutorials() +{ + QDirIterator dirIter(QApplication::applicationDirPath() + "/tutorials", QStringList("*.js"), QDir::Files); + while (dirIter.hasNext()) { + dirIter.next(); + QString fileName = dirIter.fileName(); + QFile file(dirIter.filePath()); + if (!file.open(QIODevice::ReadOnly)) { + qCritical() << "Failed to open " << fileName; + continue; } + QString firstLine = QString::fromUtf8(file.readLine()); + if (firstLine.startsWith("//WIN")) { + QString windowName = firstLine.mid(6).trimmed(); + if (!m_OrganizerCore.settings().directInterface().value("CompletedWindowTutorials/" + windowName, false).toBool()) { + TutorialManager::instance().activateTutorial(windowName, fileName); + } + } + } } -void MainWindow::showEvent(QShowEvent* event) { - refreshFilters(); - - QMainWindow::showEvent(event); +void MainWindow::showEvent(QShowEvent *event) +{ + refreshFilters(); - if (!m_WasVisible) { - // only the first time the window becomes visible - m_Tutorial.registerControl(); + QMainWindow::showEvent(event); - hookUpWindowTutorials(); + if (!m_WasVisible) { + // only the first time the window becomes visible + m_Tutorial.registerControl(); - if (m_OrganizerCore.settings().directInterface().value("first_start", true).toBool()) { - QString firstStepsTutorial = ToQString(AppConfig::firstStepsTutorial()); - if (TutorialManager::instance().hasTutorial(firstStepsTutorial)) { - if (QMessageBox::question(this, 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::Yes) { - TutorialManager::instance().activateTutorial("MainWindow", firstStepsTutorial); - } - } else { - qCritical() << firstStepsTutorial << " missing"; - QPoint pos = ui->toolBar->mapToGlobal(QPoint()); - 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")); - } + hookUpWindowTutorials(); - m_OrganizerCore.settings().directInterface().setValue("first_start", false); + if (m_OrganizerCore.settings().directInterface().value("first_start", true).toBool()) { + QString firstStepsTutorial = ToQString(AppConfig::firstStepsTutorial()); + if (TutorialManager::instance().hasTutorial(firstStepsTutorial)) { + if (QMessageBox::question(this, 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::Yes) { + TutorialManager::instance().activateTutorial("MainWindow", firstStepsTutorial); } + } else { + qCritical() << firstStepsTutorial << " missing"; + QPoint pos = ui->toolBar->mapToGlobal(QPoint()); + 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")); + } + + m_OrganizerCore.settings().directInterface().setValue("first_start", false); + } - // this has no visible impact when called before the ui is visible - int grouping = m_OrganizerCore.settings().directInterface().value("group_state").toInt(); - ui->groupCombo->setCurrentIndex(grouping); + // this has no visible impact when called before the ui is visible + int grouping = m_OrganizerCore.settings().directInterface().value("group_state").toInt(); + ui->groupCombo->setCurrentIndex(grouping); - allowListResize(); + allowListResize(); - m_OrganizerCore.settings().registerAsNXMHandler(false); - m_WasVisible = true; - updateProblemsButton(); - } + m_OrganizerCore.settings().registerAsNXMHandler(false); + m_WasVisible = true; + updateProblemsButton(); + } } -void MainWindow::closeEvent(QCloseEvent* event) { - m_closing = true; - if (m_OrganizerCore.downloadManager()->downloadsInProgress()) { - 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) { - event->ignore(); - return; - } else { - m_OrganizerCore.downloadManager()->pauseAll(); - } +void MainWindow::closeEvent(QCloseEvent* event) +{ + m_closing = true; + + if (m_OrganizerCore.downloadManager()->downloadsInProgress()) { + 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) { + event->ignore(); + return; + } else { + m_OrganizerCore.downloadManager()->pauseAll(); } + } - std::vector hiddenList; - hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); - HANDLE injected_process_still_running = m_OrganizerCore.findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId()); - if (injected_process_still_running != INVALID_HANDLE_VALUE) { - m_OrganizerCore.waitForApplication(injected_process_still_running); - if (!m_closing) { // if operation cancelled - event->ignore(); - return; - } + std::vector hiddenList; + hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); + HANDLE injected_process_still_running = m_OrganizerCore.findAndOpenAUSVFSProcess(hiddenList, GetCurrentProcessId()); + if (injected_process_still_running != INVALID_HANDLE_VALUE) + { + m_OrganizerCore.waitForApplication(injected_process_still_running); + if (!m_closing) { // if operation cancelled + event->ignore(); + return; } + } - setCursor(Qt::WaitCursor); + setCursor(Qt::WaitCursor); } -void MainWindow::cleanup() { - if (ui->logList->model() != nullptr) { - disconnect(ui->logList->model(), nullptr, nullptr, nullptr); - ui->logList->setModel(nullptr); - } +void MainWindow::cleanup() +{ + if (ui->logList->model() != nullptr) { + disconnect(ui->logList->model(), nullptr, nullptr, nullptr); + ui->logList->setModel(nullptr); + } - QWebEngineProfile::defaultProfile()->clearAllVisitedLinks(); - m_IntegratedBrowser.close(); + QWebEngineProfile::defaultProfile()->clearAllVisitedLinks(); + m_IntegratedBrowser.close(); } -void MainWindow::setBrowserGeometry(const QByteArray& geometry) { m_IntegratedBrowser.restoreGeometry(geometry); } -void MainWindow::displaySaveGameInfo(QListWidgetItem* newItem) { - QString const& save = newItem->data(Qt::UserRole).toString(); +void MainWindow::setBrowserGeometry(const QByteArray &geometry) +{ + m_IntegratedBrowser.restoreGeometry(geometry); +} + +void MainWindow::displaySaveGameInfo(QListWidgetItem *newItem) +{ + QString const &save = newItem->data(Qt::UserRole).toString(); + if (m_CurrentSaveView == nullptr) { + IPluginGame const *game = m_OrganizerCore.managedGame(); + SaveGameInfo const *info = game->feature(); + if (info != nullptr) { + m_CurrentSaveView = info->getSaveGameWidget(this); + } if (m_CurrentSaveView == nullptr) { - IPluginGame const* game = m_OrganizerCore.managedGame(); - SaveGameInfo const* info = game->feature(); - if (info != nullptr) { - m_CurrentSaveView = info->getSaveGameWidget(this); - } - if (m_CurrentSaveView == nullptr) { - return; - } + return; } - m_CurrentSaveView->setSave(save); + } + m_CurrentSaveView->setSave(save); - QRect screenRect = QApplication::desktop()->availableGeometry(m_CurrentSaveView); + QRect screenRect = QApplication::desktop()->availableGeometry(m_CurrentSaveView); - QPoint pos = QCursor::pos(); - if (pos.x() + m_CurrentSaveView->width() > screenRect.right()) { - pos.rx() -= (m_CurrentSaveView->width() + 2); - } else { - pos.rx() += 5; - } + QPoint pos = QCursor::pos(); + if (pos.x() + m_CurrentSaveView->width() > screenRect.right()) { + pos.rx() -= (m_CurrentSaveView->width() + 2); + } else { + pos.rx() += 5; + } - if (pos.y() + m_CurrentSaveView->height() > screenRect.bottom()) { - pos.ry() -= (m_CurrentSaveView->height() + 10); - } else { - pos.ry() += 20; - } - m_CurrentSaveView->move(pos); + if (pos.y() + m_CurrentSaveView->height() > screenRect.bottom()) { + pos.ry() -= (m_CurrentSaveView->height() + 10); + } else { + pos.ry() += 20; + } + m_CurrentSaveView->move(pos); - m_CurrentSaveView->show(); - m_CurrentSaveView->setProperty("displayItem", qVariantFromValue(static_cast(newItem))); + m_CurrentSaveView->show(); + m_CurrentSaveView->setProperty("displayItem", qVariantFromValue(static_cast(newItem))); - ui->savegameList->activateWindow(); + ui->savegameList->activateWindow(); } -void MainWindow::saveSelectionChanged(QListWidgetItem* newItem) { - if (newItem == nullptr) { - hideSaveGameInfo(); - } else if (m_CurrentSaveView == nullptr || newItem != m_CurrentSaveView->property("displayItem").value()) { - displaySaveGameInfo(newItem); - } + +void MainWindow::saveSelectionChanged(QListWidgetItem *newItem) +{ + if (newItem == nullptr) { + hideSaveGameInfo(); + } else if (m_CurrentSaveView == nullptr || newItem != m_CurrentSaveView->property("displayItem").value()) { + displaySaveGameInfo(newItem); + } } -void MainWindow::hideSaveGameInfo() { - if (m_CurrentSaveView != nullptr) { - m_CurrentSaveView->deleteLater(); - m_CurrentSaveView = nullptr; - } + +void MainWindow::hideSaveGameInfo() +{ + if (m_CurrentSaveView != nullptr) { + m_CurrentSaveView->deleteLater(); + m_CurrentSaveView = nullptr; + } } -bool MainWindow::eventFilter(QObject* object, QEvent* event) { - if ((object == ui->savegameList) && - ((event->type() == QEvent::Leave) || (event->type() == QEvent::WindowDeactivate))) { - hideSaveGameInfo(); - } - return false; +bool MainWindow::eventFilter(QObject *object, QEvent *event) +{ + if ((object == ui->savegameList) && + ((event->type() == QEvent::Leave) || (event->type() == QEvent::WindowDeactivate))) { + hideSaveGameInfo(); + } + return false; } -void MainWindow::toolPluginInvoke() { - QAction* triggeredAction = qobject_cast(sender()); - IPluginTool* plugin = qobject_cast(triggeredAction->data().value()); - if (plugin != nullptr) { - try { - plugin->display(); - } catch (const std::exception& e) { - reportError(tr("Plugin \"%1\" failed: %2").arg(plugin->name()).arg(e.what())); - } catch (...) { - reportError(tr("Plugin \"%1\" failed").arg(plugin->name())); - } + +void MainWindow::toolPluginInvoke() +{ + QAction *triggeredAction = qobject_cast(sender()); + IPluginTool *plugin = qobject_cast(triggeredAction->data().value()); + if (plugin != nullptr) { + try { + plugin->display(); + } catch (const std::exception &e) { + reportError(tr("Plugin \"%1\" failed: %2").arg(plugin->name()).arg(e.what())); + } catch (...) { + reportError(tr("Plugin \"%1\" failed").arg(plugin->name())); } + } } -void MainWindow::modPagePluginInvoke() { - QAction* triggeredAction = qobject_cast(sender()); - IPluginModPage* plugin = qobject_cast(triggeredAction->data().value()); - if (plugin != nullptr) { - if (plugin->useIntegratedBrowser()) { - m_IntegratedBrowser.setWindowTitle(plugin->displayName()); - m_IntegratedBrowser.openUrl(plugin->pageURL()); - } else { - ::ShellExecuteW(nullptr, L"open", ToWString(plugin->pageURL().toString()).c_str(), nullptr, nullptr, - SW_SHOWNORMAL); - } +void MainWindow::modPagePluginInvoke() +{ + QAction *triggeredAction = qobject_cast(sender()); + IPluginModPage *plugin = qobject_cast(triggeredAction->data().value()); + if (plugin != nullptr) { + if (plugin->useIntegratedBrowser()) { + m_IntegratedBrowser.setWindowTitle(plugin->displayName()); + m_IntegratedBrowser.openUrl(plugin->pageURL()); + } else { + ::ShellExecuteW(nullptr, L"open", ToWString(plugin->pageURL().toString()).c_str(), + nullptr, nullptr, SW_SHOWNORMAL); } + } } -void MainWindow::registerPluginTool(IPluginTool* tool) { - QAction* action = new QAction(tool->icon(), tool->displayName(), ui->toolBar); - action->setToolTip(tool->tooltip()); - tool->setParentWidget(this); - action->setData(qVariantFromValue((QObject*)tool)); - connect(action, SIGNAL(triggered()), this, SLOT(toolPluginInvoke()), Qt::QueuedConnection); - QToolButton* toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionTool)); - toolBtn->menu()->addAction(action); +void MainWindow::registerPluginTool(IPluginTool *tool) +{ + QAction *action = new QAction(tool->icon(), tool->displayName(), ui->toolBar); + action->setToolTip(tool->tooltip()); + tool->setParentWidget(this); + action->setData(qVariantFromValue((QObject*)tool)); + connect(action, SIGNAL(triggered()), this, SLOT(toolPluginInvoke()), Qt::QueuedConnection); + QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionTool)); + toolBtn->menu()->addAction(action); } -void MainWindow::registerModPage(IPluginModPage* modPage) { - // turn the browser action into a drop-down menu if necessary - if (ui->actionNexus->menu() == nullptr) { - QAction* nexusAction = ui->actionNexus; - // TODO: use a different icon for nexus! - ui->actionNexus = new QAction(nexusAction->icon(), tr("Browse Mod Page"), ui->toolBar); - ui->toolBar->insertAction(nexusAction, ui->actionNexus); - ui->toolBar->removeAction(nexusAction); - actionToToolButton(ui->actionNexus); - - QToolButton* browserBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); - browserBtn->menu()->addAction(nexusAction); - } +void MainWindow::registerModPage(IPluginModPage *modPage) +{ + // turn the browser action into a drop-down menu if necessary + if (ui->actionNexus->menu() == nullptr) { + QAction *nexusAction = ui->actionNexus; + // TODO: use a different icon for nexus! + ui->actionNexus = new QAction(nexusAction->icon(), tr("Browse Mod Page"), ui->toolBar); + ui->toolBar->insertAction(nexusAction, ui->actionNexus); + ui->toolBar->removeAction(nexusAction); + actionToToolButton(ui->actionNexus); - QAction* action = new QAction(modPage->icon(), modPage->displayName(), ui->toolBar); - modPage->setParentWidget(this); - action->setData(qVariantFromValue(reinterpret_cast(modPage))); + QToolButton *browserBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); + browserBtn->menu()->addAction(nexusAction); + } - connect(action, SIGNAL(triggered()), this, SLOT(modPagePluginInvoke()), Qt::QueuedConnection); - QToolButton* toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); - toolBtn->menu()->addAction(action); -} + QAction *action = new QAction(modPage->icon(), modPage->displayName(), ui->toolBar); + modPage->setParentWidget(this); + action->setData(qVariantFromValue(reinterpret_cast(modPage))); -void MainWindow::startExeAction() { - QAction* action = qobject_cast(sender()); - if (action != nullptr) { - const Executable& selectedExecutable(m_OrganizerCore.executablesList()->find(action->text())); - QString customOverwrite = - m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.m_Title).toString(); - m_OrganizerCore.spawnBinary(selectedExecutable.m_BinaryInfo, selectedExecutable.m_Arguments, - selectedExecutable.m_WorkingDirectory.length() != 0 - ? selectedExecutable.m_WorkingDirectory - : selectedExecutable.m_BinaryInfo.absolutePath(), - selectedExecutable.m_SteamAppID, customOverwrite); - } else { - qCritical("not an action?"); - } + connect(action, SIGNAL(triggered()), this, SLOT(modPagePluginInvoke()), Qt::QueuedConnection); + QToolButton *toolBtn = qobject_cast(ui->toolBar->widgetForAction(ui->actionNexus)); + toolBtn->menu()->addAction(action); } -void MainWindow::setExecutableIndex(int index) { - QComboBox* executableBox = findChild("executablesListBox"); - if ((index != 0) && (executableBox->count() > index)) { - executableBox->setCurrentIndex(index); - } else { - executableBox->setCurrentIndex(1); - } +void MainWindow::startExeAction() +{ + QAction *action = qobject_cast(sender()); + if (action != nullptr) { + const Executable &selectedExecutable( + m_OrganizerCore.executablesList()->find(action->text())); + QString customOverwrite= m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.m_Title).toString(); + m_OrganizerCore.spawnBinary( + selectedExecutable.m_BinaryInfo, selectedExecutable.m_Arguments, + selectedExecutable.m_WorkingDirectory.length() != 0 + ? selectedExecutable.m_WorkingDirectory + : selectedExecutable.m_BinaryInfo.absolutePath(), + selectedExecutable.m_SteamAppID, customOverwrite); + } else { + qCritical("not an action?"); + } } -void MainWindow::activateSelectedProfile() { - m_OrganizerCore.setCurrentProfile(ui->profileBox->currentText()); - m_ModListSortProxy->setProfile(m_OrganizerCore.currentProfile()); +void MainWindow::setExecutableIndex(int index) +{ + QComboBox *executableBox = findChild("executablesListBox"); - refreshSaveList(); - m_OrganizerCore.refreshModList(); + if ((index != 0) && (executableBox->count() > index)) { + executableBox->setCurrentIndex(index); + } else { + executableBox->setCurrentIndex(1); + } } -void MainWindow::on_profileBox_currentIndexChanged(int index) { - if (ui->profileBox->isEnabled()) { - int previousIndex = m_OldProfileIndex; - m_OldProfileIndex = index; - - if ((previousIndex != -1) && (m_OrganizerCore.currentProfile() != nullptr) && - m_OrganizerCore.currentProfile()->exists()) { - m_OrganizerCore.saveCurrentLists(); - } +void MainWindow::activateSelectedProfile() +{ + m_OrganizerCore.setCurrentProfile(ui->profileBox->currentText()); - // ensure the new index is valid - if (index < 0 || index >= ui->profileBox->count()) { - qDebug("invalid profile index, using last profile"); - ui->profileBox->setCurrentIndex(ui->profileBox->count() - 1); - } + m_ModListSortProxy->setProfile(m_OrganizerCore.currentProfile()); - if (ui->profileBox->currentIndex() == 0) { - ui->profileBox->setCurrentIndex(previousIndex); - ProfilesDialog(ui->profileBox->currentText(), m_OrganizerCore.managedGame(), this).exec(); - while (!refreshProfiles()) { - ProfilesDialog(ui->profileBox->currentText(), m_OrganizerCore.managedGame(), this).exec(); - } - } else { - activateSelectedProfile(); - } - } + refreshSaveList(); + m_OrganizerCore.refreshModList(); } -void MainWindow::updateTo(QTreeWidgetItem* subTree, const std::wstring& directorySoFar, - const DirectoryEntry& directoryEntry, bool conflictsOnly) { - { - for (const FileEntry::Ptr current : directoryEntry.getFiles()) { - if (conflictsOnly && (current->getAlternatives().size() == 0)) { - continue; - } +void MainWindow::on_profileBox_currentIndexChanged(int index) +{ + if (ui->profileBox->isEnabled()) { + int previousIndex = m_OldProfileIndex; + m_OldProfileIndex = index; - QString fileName = ToQString(current->getName()); - QStringList columns(fileName); - bool isArchive = false; - int originID = current->getOrigin(isArchive); - FilesOrigin origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); - QString source("data"); - unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); - if (modIndex != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - source = modInfo->name(); - } + if ((previousIndex != -1) && + (m_OrganizerCore.currentProfile() != nullptr) && + m_OrganizerCore.currentProfile()->exists()) { + m_OrganizerCore.saveCurrentLists(); + } - std::wstring archive = current->getArchive(); - if (archive.length() != 0) { - source.append(" (").append(ToQString(archive)).append(")"); - } - columns.append(source); - QTreeWidgetItem* fileChild = new QTreeWidgetItem(columns); - if (isArchive) { - QFont font = fileChild->font(0); - font.setItalic(true); - fileChild->setFont(0, font); - fileChild->setFont(1, font); - } else if (fileName.endsWith(ModInfo::s_HiddenExt)) { - QFont font = fileChild->font(0); - font.setStrikeOut(true); - fileChild->setFont(0, font); - fileChild->setFont(1, font); - } - fileChild->setData(0, Qt::UserRole, ToQString(current->getFullPath())); - fileChild->setData(0, Qt::UserRole + 1, isArchive); - fileChild->setData(1, Qt::UserRole, source); - fileChild->setData(1, Qt::UserRole + 1, originID); - - std::vector> alternatives = current->getAlternatives(); - - if (!alternatives.empty()) { - std::wostringstream altString; - altString << ToWString(tr("Also in:
    ")); - for (std::vector>::iterator altIter = alternatives.begin(); - altIter != alternatives.end(); ++altIter) { - if (altIter != alternatives.begin()) { - altString << " , "; - } - altString << "" - << m_OrganizerCore.directoryStructure()->getOriginByID(altIter->first).getName() - << ""; - } - fileChild->setToolTip(1, QString("%1").arg(ToQString(altString.str()))); - fileChild->setForeground(1, QBrush(Qt::red)); - } else { - fileChild->setToolTip(1, tr("No conflict")); - } - subTree->addChild(fileChild); + // ensure the new index is valid + if (index < 0 || index >= ui->profileBox->count()) { + qDebug("invalid profile index, using last profile"); + ui->profileBox->setCurrentIndex(ui->profileBox->count() - 1); + } + + if (ui->profileBox->currentIndex() == 0) { + ui->profileBox->setCurrentIndex(previousIndex); + ProfilesDialog(ui->profileBox->currentText(), m_OrganizerCore.managedGame(), this).exec(); + while (!refreshProfiles()) { + ProfilesDialog(ui->profileBox->currentText(), m_OrganizerCore.managedGame(), this).exec(); + } + } else { + activateSelectedProfile(); + } + } +} + +void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const DirectoryEntry &directoryEntry, bool conflictsOnly) +{ + { + for (const FileEntry::Ptr current : directoryEntry.getFiles()) { + if (conflictsOnly && (current->getAlternatives().size() == 0)) { + continue; + } + + QString fileName = ToQString(current->getName()); + QStringList columns(fileName); + bool isArchive = false; + int originID = current->getOrigin(isArchive); + FilesOrigin origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); + QString source("data"); + unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); + if (modIndex != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + source = modInfo->name(); + } + + std::wstring archive = current->getArchive(); + if (archive.length() != 0) { + source.append(" (").append(ToQString(archive)).append(")"); + } + columns.append(source); + QTreeWidgetItem *fileChild = new QTreeWidgetItem(columns); + if (isArchive) { + QFont font = fileChild->font(0); + font.setItalic(true); + fileChild->setFont(0, font); + fileChild->setFont(1, font); + } else if (fileName.endsWith(ModInfo::s_HiddenExt)) { + QFont font = fileChild->font(0); + font.setStrikeOut(true); + fileChild->setFont(0, font); + fileChild->setFont(1, font); + } + fileChild->setData(0, Qt::UserRole, ToQString(current->getFullPath())); + fileChild->setData(0, Qt::UserRole + 1, isArchive); + fileChild->setData(1, Qt::UserRole, source); + fileChild->setData(1, Qt::UserRole + 1, originID); + + std::vector> alternatives = current->getAlternatives(); + + if (!alternatives.empty()) { + std::wostringstream altString; + altString << ToWString(tr("Also in:
    ")); + for (std::vector>::iterator altIter = alternatives.begin(); + altIter != alternatives.end(); ++altIter) { + if (altIter != alternatives.begin()) { + altString << " , "; + } + altString << "" << m_OrganizerCore.directoryStructure()->getOriginByID(altIter->first).getName() << ""; } + fileChild->setToolTip(1, QString("%1").arg(ToQString(altString.str()))); + fileChild->setForeground(1, QBrush(Qt::red)); + } else { + fileChild->setToolTip(1, tr("No conflict")); + } + subTree->addChild(fileChild); } + } - std::wostringstream temp; - temp << directorySoFar << "\\" << directoryEntry.getName(); - { - std::vector::const_iterator current, end; - directoryEntry.getSubDirectories(current, end); - for (; current != end; ++current) { - QString pathName = ToQString((*current)->getName()); - QStringList columns(pathName); - columns.append(""); - if (!(*current)->isEmpty()) { - QTreeWidgetItem* directoryChild = new QTreeWidgetItem(columns); - if (conflictsOnly) { - updateTo(directoryChild, temp.str(), **current, conflictsOnly); - if (directoryChild->childCount() != 0) { - subTree->addChild(directoryChild); - } else { - delete directoryChild; - } - } else { - QTreeWidgetItem* onDemandLoad = new QTreeWidgetItem(QStringList()); - onDemandLoad->setData(0, Qt::UserRole + 0, "__loaded_on_demand__"); - onDemandLoad->setData(0, Qt::UserRole + 1, ToQString(temp.str())); - onDemandLoad->setData(0, Qt::UserRole + 2, conflictsOnly); - directoryChild->addChild(onDemandLoad); - subTree->addChild(directoryChild); - } - } + std::wostringstream temp; + temp << directorySoFar << "\\" << directoryEntry.getName(); + { + std::vector::const_iterator current, end; + directoryEntry.getSubDirectories(current, end); + for (; current != end; ++current) { + QString pathName = ToQString((*current)->getName()); + QStringList columns(pathName); + columns.append(""); + if (!(*current)->isEmpty()) { + QTreeWidgetItem *directoryChild = new QTreeWidgetItem(columns); + if (conflictsOnly) { + updateTo(directoryChild, temp.str(), **current, conflictsOnly); + if (directoryChild->childCount() != 0) { + subTree->addChild(directoryChild); + } else { + delete directoryChild; + } + } else { + QTreeWidgetItem *onDemandLoad = new QTreeWidgetItem(QStringList()); + onDemandLoad->setData(0, Qt::UserRole + 0, "__loaded_on_demand__"); + onDemandLoad->setData(0, Qt::UserRole + 1, ToQString(temp.str())); + onDemandLoad->setData(0, Qt::UserRole + 2, conflictsOnly); + directoryChild->addChild(onDemandLoad); + subTree->addChild(directoryChild); } + } } + } - subTree->sortChildren(0, Qt::AscendingOrder); + subTree->sortChildren(0, Qt::AscendingOrder); } -void MainWindow::delayedRemove() { - for (QTreeWidgetItem* item : m_RemoveWidget) { - item->removeChild(item->child(0)); - } - m_RemoveWidget.clear(); +void MainWindow::delayedRemove() +{ + for (QTreeWidgetItem *item : m_RemoveWidget) { + item->removeChild(item->child(0)); + } + m_RemoveWidget.clear(); } -void MainWindow::expandDataTreeItem(QTreeWidgetItem* item) { - if ((item->childCount() == 1) && (item->child(0)->data(0, Qt::UserRole).toString() == "__loaded_on_demand__")) { - // read the data we need from the sub-item, then dispose of it - QTreeWidgetItem* onDemandDataItem = item->child(0); - std::wstring path = ToWString(onDemandDataItem->data(0, Qt::UserRole + 1).toString()); - bool conflictsOnly = onDemandDataItem->data(0, Qt::UserRole + 2).toBool(); +void MainWindow::expandDataTreeItem(QTreeWidgetItem *item) +{ + if ((item->childCount() == 1) && (item->child(0)->data(0, Qt::UserRole).toString() == "__loaded_on_demand__")) { + // read the data we need from the sub-item, then dispose of it + QTreeWidgetItem *onDemandDataItem = item->child(0); + std::wstring path = ToWString(onDemandDataItem->data(0, Qt::UserRole + 1).toString()); + bool conflictsOnly = onDemandDataItem->data(0, Qt::UserRole + 2).toBool(); - std::wstring virtualPath = (path + L"\\").substr(6) + ToWString(item->text(0)); - DirectoryEntry* dir = m_OrganizerCore.directoryStructure()->findSubDirectoryRecursive(virtualPath); - if (dir != nullptr) { - updateTo(item, path, *dir, conflictsOnly); - } else { - qWarning("failed to update view of %ls", path.c_str()); - } - m_RemoveWidget.push_back(item); - QTimer::singleShot(5, this, SLOT(delayedRemove())); + std::wstring virtualPath = (path + L"\\").substr(6) + ToWString(item->text(0)); + DirectoryEntry *dir = m_OrganizerCore.directoryStructure()->findSubDirectoryRecursive(virtualPath); + if (dir != nullptr) { + updateTo(item, path, *dir, conflictsOnly); + } else { + qWarning("failed to update view of %ls", path.c_str()); } + m_RemoveWidget.push_back(item); + QTimer::singleShot(5, this, SLOT(delayedRemove())); + } } -bool MainWindow::refreshProfiles(bool selectProfile) { - QComboBox* profileBox = findChild("profileBox"); - QString currentProfileName = profileBox->currentText(); +bool MainWindow::refreshProfiles(bool selectProfile) +{ + QComboBox* profileBox = findChild("profileBox"); - profileBox->blockSignals(true); - profileBox->clear(); - profileBox->addItem(QObject::tr("")); + QString currentProfileName = profileBox->currentText(); - QDir profilesDir(Settings::instance().getProfileDirectory()); - profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); + profileBox->blockSignals(true); + profileBox->clear(); + profileBox->addItem(QObject::tr("")); - QDirIterator profileIter(profilesDir); + QDir profilesDir(Settings::instance().getProfileDirectory()); + profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); - while (profileIter.hasNext()) { - profileIter.next(); - 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())); - } - } + QDirIterator profileIter(profilesDir); - // now select one of the profiles, preferably the one that was selected before - profileBox->blockSignals(false); + while (profileIter.hasNext()) { + profileIter.next(); + 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())); + } + } + + // now select one of the profiles, preferably the one that was selected before + profileBox->blockSignals(false); + + if (selectProfile) { + if (profileBox->count() > 1) { + profileBox->setCurrentText(currentProfileName); + if (profileBox->currentIndex() == 0) { + profileBox->setCurrentIndex(1); + } + } + } + return profileBox->count() > 1; +} + + +void MainWindow::refreshExecutablesList() +{ + QComboBox* executablesList = findChild("executablesListBox"); + executablesList->setEnabled(false); + executablesList->clear(); + executablesList->addItem(tr("")); + + QAbstractItemModel *model = executablesList->model(); + + std::vector::const_iterator current, end; + m_OrganizerCore.executablesList()->getExecutables(current, end); + for(int i = 0; current != end; ++current, ++i) { + QIcon icon = iconForExecutable(current->m_BinaryInfo.filePath()); + executablesList->addItem(icon, current->m_Title); + model->setData(model->index(i, 0), QSize(0, executablesList->iconSize().height() + 4), Qt::SizeHintRole); + } + + setExecutableIndex(1); + executablesList->setEnabled(true); +} + + +void MainWindow::refreshDataTree() +{ + QCheckBox *conflictsBox = findChild("conflictsCheckBox"); + QTreeWidget *tree = findChild("dataTree"); + tree->clear(); + QStringList columns("data"); + columns.append(""); + QTreeWidgetItem *subTree = new QTreeWidgetItem(columns); + updateTo(subTree, L"", *m_OrganizerCore.directoryStructure(), conflictsBox->isChecked()); + tree->insertTopLevelItem(0, subTree); + subTree->setExpanded(true); +} + +void MainWindow::refreshDataTreeKeepExpandedNodes() +{ + QCheckBox *conflictsBox = findChild("conflictsCheckBox"); + QTreeWidget *tree = findChild("dataTree"); + + QStringList expandedNodes; + QTreeWidgetItemIterator it1(tree, QTreeWidgetItemIterator::NotHidden | QTreeWidgetItemIterator::HasChildren); + while (*it1) { + QTreeWidgetItem *current = (*it1); + if (current->isExpanded() && !(current->text(0)=="data")) { + expandedNodes.append(current->text(0)+"/"+current->parent()->text(0)); + } + ++it1; + } + + tree->clear(); + QStringList columns("data"); + columns.append(""); + QTreeWidgetItem *subTree = new QTreeWidgetItem(columns); + updateTo(subTree, L"", *m_OrganizerCore.directoryStructure(), conflictsBox->isChecked()); + tree->insertTopLevelItem(0, subTree); + subTree->setExpanded(true); + QTreeWidgetItemIterator it2(tree, QTreeWidgetItemIterator::HasChildren); + while (*it2) { + QTreeWidgetItem *current = (*it2); + if (!(current->text(0)=="data") && expandedNodes.contains(current->text(0)+"/"+current->parent()->text(0))) { + current->setExpanded(true); + } + ++it2; + } +} + + +void MainWindow::refreshSavesIfOpen() +{ + if (ui->tabWidget->currentIndex() == 3) { + refreshSaveList(); + } +} - if (selectProfile) { - if (profileBox->count() > 1) { - profileBox->setCurrentText(currentProfileName); - if (profileBox->currentIndex() == 0) { - profileBox->setCurrentIndex(1); - } - } - } - return profileBox->count() > 1; -} +QDir MainWindow::currentSavesDir() const +{ + QDir savesDir; + if (m_OrganizerCore.currentProfile()->localSavesEnabled()) { + savesDir.setPath(m_OrganizerCore.currentProfile()->savePath()); + } else { + wchar_t path[MAX_PATH]; + ::GetPrivateProfileStringW( + L"General", L"SLocalSavePath", L"Saves", + path, MAX_PATH, + ToWString(m_OrganizerCore.currentProfile()->absolutePath() + "/" + + m_OrganizerCore.managedGame()->iniFiles()[0]).c_str()); + savesDir.setPath(m_OrganizerCore.managedGame()->documentsDirectory().absoluteFilePath(QString::fromWCharArray(path))); + } -void MainWindow::refreshExecutablesList() { - QComboBox* executablesList = findChild("executablesListBox"); - executablesList->setEnabled(false); - executablesList->clear(); - executablesList->addItem(tr("")); - - QAbstractItemModel* model = executablesList->model(); - - std::vector::const_iterator current, end; - m_OrganizerCore.executablesList()->getExecutables(current, end); - for (int i = 0; current != end; ++current, ++i) { - QIcon icon = iconForExecutable(current->m_BinaryInfo.filePath()); - executablesList->addItem(icon, current->m_Title); - model->setData(model->index(i, 0), QSize(0, executablesList->iconSize().height() + 4), Qt::SizeHintRole); - } - - setExecutableIndex(1); - executablesList->setEnabled(true); + return savesDir; } -void MainWindow::refreshDataTree() { - QCheckBox* conflictsBox = findChild("conflictsCheckBox"); - QTreeWidget* tree = findChild("dataTree"); - tree->clear(); - QStringList columns("data"); - columns.append(""); - QTreeWidgetItem* subTree = new QTreeWidgetItem(columns); - updateTo(subTree, L"", *m_OrganizerCore.directoryStructure(), conflictsBox->isChecked()); - tree->insertTopLevelItem(0, subTree); - subTree->setExpanded(true); -} +void MainWindow::startMonitorSaves() +{ + stopMonitorSaves(); -void MainWindow::refreshDataTreeKeepExpandedNodes() { - QCheckBox* conflictsBox = findChild("conflictsCheckBox"); - QTreeWidget* tree = findChild("dataTree"); + QDir savesDir = currentSavesDir(); - QStringList expandedNodes; - QTreeWidgetItemIterator it1(tree, QTreeWidgetItemIterator::NotHidden | QTreeWidgetItemIterator::HasChildren); - while (*it1) { - QTreeWidgetItem* current = (*it1); - if (current->isExpanded() && !(current->text(0) == "data")) { - expandedNodes.append(current->text(0) + "/" + current->parent()->text(0)); - } - ++it1; - } - - tree->clear(); - QStringList columns("data"); - columns.append(""); - QTreeWidgetItem* subTree = new QTreeWidgetItem(columns); - updateTo(subTree, L"", *m_OrganizerCore.directoryStructure(), conflictsBox->isChecked()); - tree->insertTopLevelItem(0, subTree); - subTree->setExpanded(true); - QTreeWidgetItemIterator it2(tree, QTreeWidgetItemIterator::HasChildren); - while (*it2) { - QTreeWidgetItem* current = (*it2); - if (!(current->text(0) == "data") && - expandedNodes.contains(current->text(0) + "/" + current->parent()->text(0))) { - current->setExpanded(true); - } - ++it2; - } + m_SavesWatcher.addPath(savesDir.absolutePath()); } -void MainWindow::refreshSavesIfOpen() { - if (ui->tabWidget->currentIndex() == 3) { - refreshSaveList(); - } +void MainWindow::stopMonitorSaves() +{ + if (m_SavesWatcher.directories().length() > 0) { + m_SavesWatcher.removePaths(m_SavesWatcher.directories()); + } } -QDir MainWindow::currentSavesDir() const { - QDir savesDir; - if (m_OrganizerCore.currentProfile()->localSavesEnabled()) { - savesDir.setPath(m_OrganizerCore.currentProfile()->savePath()); - } else { - wchar_t path[MAX_PATH]; - ::GetPrivateProfileStringW(L"General", L"SLocalSavePath", L"Saves", path, MAX_PATH, - ToWString(m_OrganizerCore.currentProfile()->absolutePath() + "/" + - m_OrganizerCore.managedGame()->iniFiles()[0]) - .c_str()); - savesDir.setPath( - m_OrganizerCore.managedGame()->documentsDirectory().absoluteFilePath(QString::fromWCharArray(path))); - } - - return savesDir; -} +void MainWindow::refreshSaveList() +{ + ui->savegameList->clear(); -void MainWindow::startMonitorSaves() { - stopMonitorSaves(); + startMonitorSaves(); // re-starts monitoring - QDir savesDir = currentSavesDir(); + QStringList filters; + filters << QString("*.") + m_OrganizerCore.managedGame()->savegameExtension(); - m_SavesWatcher.addPath(savesDir.absolutePath()); -} + QDir savesDir = currentSavesDir(); + savesDir.setNameFilters(filters); + qDebug("reading save games from %s", qPrintable(savesDir.absolutePath())); -void MainWindow::stopMonitorSaves() { - if (m_SavesWatcher.directories().length() > 0) { - m_SavesWatcher.removePaths(m_SavesWatcher.directories()); - } + QFileInfoList files = savesDir.entryInfoList(QDir::Files, QDir::Time); + for (const QFileInfo &file : files) { + QListWidgetItem *item = new QListWidgetItem(file.fileName()); + item->setData(Qt::UserRole, file.absoluteFilePath()); + ui->savegameList->addItem(item); + } } -void MainWindow::refreshSaveList() { - ui->savegameList->clear(); - - startMonitorSaves(); // re-starts monitoring - - QStringList filters; - filters << QString("*.") + m_OrganizerCore.managedGame()->savegameExtension(); - QDir savesDir = currentSavesDir(); - savesDir.setNameFilters(filters); - qDebug("reading save games from %s", qPrintable(savesDir.absolutePath())); - - QFileInfoList files = savesDir.entryInfoList(QDir::Files, QDir::Time); - for (const QFileInfo& file : files) { - QListWidgetItem* item = new QListWidgetItem(file.fileName()); - item->setData(Qt::UserRole, file.absoluteFilePath()); - ui->savegameList->addItem(item); - } -} - -static bool BySortValue(const std::pair& LHS, - const std::pair& RHS) { - return LHS.first < RHS.first; +static bool BySortValue(const std::pair &LHS, const std::pair &RHS) +{ + return LHS.first < RHS.first; } template -static QStringList toStringList(InputIterator current, InputIterator end) { - QStringList result; - for (; current != end; ++current) { - result.append(*current); +static QStringList toStringList(InputIterator current, InputIterator end) +{ + QStringList result; + for (; current != end; ++current) { + result.append(*current); + } + return result; +} + +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(); + 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); + }); + + auto hasAssociatedPlugin = [&](const QString &bsaName) -> bool { + for (const QString &pluginName : plugins) { + QFileInfo pluginInfo(pluginName); + if (bsaName.startsWith(QFileInfo(pluginName).baseName(), Qt::CaseInsensitive) + && (m_OrganizerCore.pluginList()->state(pluginInfo.fileName()) == IPluginList::STATE_ACTIVE)) { + return true; + } } - return result; -} - -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(); - 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); - }); - - auto hasAssociatedPlugin = [&](const QString& bsaName) -> bool { - for (const QString& pluginName : plugins) { - QFileInfo pluginInfo(pluginName); - if (bsaName.startsWith(QFileInfo(pluginName).baseName(), Qt::CaseInsensitive) && - (m_OrganizerCore.pluginList()->state(pluginInfo.fileName()) == IPluginList::STATE_ACTIVE)) { - return true; - } - } - return false; - }; - - for (FileEntry::Ptr current : files) { - QFileInfo fileInfo(ToQString(current->getName().c_str())); - - if (fileInfo.suffix().toLower() == "bsa" || fileInfo.suffix().toLower() == "ba2") { - int index = activeArchives.indexOf(fileInfo.fileName()); - if (index == -1) { - index = 0xFFFF; - } else { - index += 2; - } - - if ((invalidation != nullptr) && invalidation->isInvalidationBSA(fileInfo.fileName())) { - index = 1; - } - - int originId = current->getOrigin(); - FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByID(originId); - - 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->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked); - newItem->setData(0, Qt::UserRole, false); - if (m_OrganizerCore.settings().forceEnableCoreFiles() && defaultArchives.contains(fileInfo.fileName())) { - newItem->setCheckState(0, Qt::Checked); - newItem->setDisabled(true); - newItem->setData(0, Qt::UserRole, true); - } else if (fileInfo.fileName().compare("update.bsa", Qt::CaseInsensitive) == 0) { - newItem->setCheckState(0, Qt::Checked); - newItem->setDisabled(true); - } else if (hasAssociatedPlugin(fileInfo.fileName())) { - newItem->setCheckState(0, Qt::Checked); - newItem->setDisabled(true); - } else { - newItem->setCheckState(0, Qt::Unchecked); - newItem->setDisabled(true); - } - if (index < 0) - index = 0; - - UINT32 sortValue = ((origin.getPriority() & 0xFFFF) << 16) | (index & 0xFFFF); - items.push_back(std::make_pair(sortValue, newItem)); + return false; + }; + + for (FileEntry::Ptr current : files) { + QFileInfo fileInfo(ToQString(current->getName().c_str())); + + if (fileInfo.suffix().toLower() == "bsa" || fileInfo.suffix().toLower() == "ba2") { + int index = activeArchives.indexOf(fileInfo.fileName()); + if (index == -1) { + index = 0xFFFF; + } + else { + index += 2; + } + + if ((invalidation != nullptr) && invalidation->isInvalidationBSA(fileInfo.fileName())) { + index = 1; + } + + int originId = current->getOrigin(); + FilesOrigin & origin = m_OrganizerCore.directoryStructure()->getOriginByID(originId); + + 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->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked); + newItem->setData(0, Qt::UserRole, false); + if (m_OrganizerCore.settings().forceEnableCoreFiles() + && defaultArchives.contains(fileInfo.fileName())) { + newItem->setCheckState(0, Qt::Checked); + newItem->setDisabled(true); + newItem->setData(0, Qt::UserRole, true); + } else if (fileInfo.fileName().compare("update.bsa", Qt::CaseInsensitive) == 0) { + newItem->setCheckState(0, Qt::Checked); + newItem->setDisabled(true); + } else if (hasAssociatedPlugin(fileInfo.fileName())) { + newItem->setCheckState(0, Qt::Checked); + newItem->setDisabled(true); + } else { + newItem->setCheckState(0, Qt::Unchecked); + newItem->setDisabled(true); + } + if (index < 0) index = 0; + + UINT32 sortValue = ((origin.getPriority() & 0xFFFF) << 16) | (index & 0xFFFF); + items.push_back(std::make_pair(sortValue, newItem)); + } + } + std::sort(items.begin(), items.end(), BySortValue); + + for (auto iter = items.begin(); iter != items.end(); ++iter) { + int originID = iter->second->data(1, Qt::UserRole).toInt(); + + FilesOrigin origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); + QString modName("data"); + unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); + if (modIndex != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + modName = modInfo->name(); + } + QList items = ui->bsaList->findItems(modName, Qt::MatchFixedString); + QTreeWidgetItem * subItem = nullptr; + if (items.length() > 0) { + subItem = items.at(0); + } + else { + subItem = new QTreeWidgetItem(QStringList(modName)); + subItem->setFlags(subItem->flags() & ~Qt::ItemIsDragEnabled); + ui->bsaList->addTopLevelItem(subItem); + } + subItem->addChild(iter->second); + subItem->setExpanded(true); + } + checkBSAList(); +} + +void MainWindow::checkBSAList() +{ + DataArchives * archives = m_OrganizerCore.managedGame()->feature(); + + if (archives != nullptr) { + ui->bsaList->blockSignals(true); + 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); + for (int j = 0; j < tlItem->childCount(); ++j) { + QTreeWidgetItem * item = tlItem->child(j); + QString filename = item->text(0); + item->setIcon(0, QIcon()); + item->setToolTip(0, QString()); + + if (item->checkState(0) == Qt::Unchecked) { + if (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!")); + modWarning = true; + } } + } + if (modWarning) { + ui->bsaList->expandItem(ui->bsaList->topLevelItem(i)); + warning = true; + } } - std::sort(items.begin(), items.end(), BySortValue); - - for (auto iter = items.begin(); iter != items.end(); ++iter) { - int originID = iter->second->data(1, Qt::UserRole).toInt(); - - FilesOrigin origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); - QString modName("data"); - unsigned int modIndex = ModInfo::getIndex(ToQString(origin.getName())); - if (modIndex != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - modName = modInfo->name(); - } - QList items = ui->bsaList->findItems(modName, Qt::MatchFixedString); - QTreeWidgetItem* subItem = nullptr; - if (items.length() > 0) { - subItem = items.at(0); - } else { - subItem = new QTreeWidgetItem(QStringList(modName)); - subItem->setFlags(subItem->flags() & ~Qt::ItemIsDragEnabled); - ui->bsaList->addTopLevelItem(subItem); - } - subItem->addChild(iter->second); - subItem->setExpanded(true); + if (warning) { + ui->tabWidget->setTabIcon(1, QIcon(":/MO/gui/warning")); + } else { + ui->tabWidget->setTabIcon(1, QIcon()); } - checkBSAList(); + } } -void MainWindow::checkBSAList() { - DataArchives* archives = m_OrganizerCore.managedGame()->feature(); - - if (archives != nullptr) { - ui->bsaList->blockSignals(true); - 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); - for (int j = 0; j < tlItem->childCount(); ++j) { - QTreeWidgetItem* item = tlItem->child(j); - QString filename = item->text(0); - item->setIcon(0, QIcon()); - item->setToolTip(0, QString()); - - if (item->checkState(0) == Qt::Unchecked) { - if (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!")); - modWarning = true; - } - } - } - if (modWarning) { - ui->bsaList->expandItem(ui->bsaList->topLevelItem(i)); - warning = true; - } - } - if (warning) { - ui->tabWidget->setTabIcon(1, QIcon(":/MO/gui/warning")); - } else { - ui->tabWidget->setTabIcon(1, QIcon()); - } - } +void MainWindow::saveModMetas() +{ + for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + modInfo->saveMeta(); + } } -void MainWindow::saveModMetas() { - for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - modInfo->saveMeta(); +void MainWindow::fixCategories() +{ + for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + std::set categories = modInfo->getCategories(); + for (std::set::iterator iter = categories.begin(); + iter != categories.end(); ++iter) { + if (!m_CategoryFactory.categoryExists(*iter)) { + modInfo->setCategory(*iter, false); + } } + } } -void MainWindow::fixCategories() { - for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - std::set categories = modInfo->getCategories(); - for (std::set::iterator iter = categories.begin(); iter != categories.end(); ++iter) { - if (!m_CategoryFactory.categoryExists(*iter)) { - modInfo->setCategory(*iter, false); - } - } - } -} -void MainWindow::setupNetworkProxy(bool activate) { - QNetworkProxyFactory::setUseSystemConfiguration(activate); - /* QNetworkProxyQuery query(QUrl("http://www.google.com"), QNetworkProxyQuery::UrlRequest); - query.setProtocolTag("http"); - QList proxies = QNetworkProxyFactory::systemProxyForQuery(query); - if ((proxies.size() > 0) && (proxies.at(0).type() != QNetworkProxy::NoProxy)) { - qDebug("Using proxy: %s", qPrintable(proxies.at(0).hostName())); - QNetworkProxy::setApplicationProxy(proxies[0]); - } else { - qDebug("Not using proxy"); - }*/ +void MainWindow::setupNetworkProxy(bool activate) +{ + QNetworkProxyFactory::setUseSystemConfiguration(activate); +/* QNetworkProxyQuery query(QUrl("http://www.google.com"), QNetworkProxyQuery::UrlRequest); + query.setProtocolTag("http"); + QList proxies = QNetworkProxyFactory::systemProxyForQuery(query); + if ((proxies.size() > 0) && (proxies.at(0).type() != QNetworkProxy::NoProxy)) { + qDebug("Using proxy: %s", qPrintable(proxies.at(0).hostName())); + QNetworkProxy::setApplicationProxy(proxies[0]); + } else { + qDebug("Not using proxy"); + }*/ } -void MainWindow::activateProxy(bool activate) { - QProgressDialog busyDialog(tr("Activating Network Proxy"), QString(), 0, 0, parentWidget()); - busyDialog.setWindowFlags(busyDialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); - busyDialog.setWindowModality(Qt::WindowModal); - busyDialog.show(); - QFuture future = QtConcurrent::run(MainWindow::setupNetworkProxy, activate); - while (!future.isFinished()) { - QCoreApplication::processEvents(); - ::Sleep(100); - } - busyDialog.hide(); + +void MainWindow::activateProxy(bool activate) +{ + QProgressDialog busyDialog(tr("Activating Network Proxy"), QString(), 0, 0, parentWidget()); + busyDialog.setWindowFlags(busyDialog.windowFlags() & ~Qt::WindowContextHelpButtonHint); + busyDialog.setWindowModality(Qt::WindowModal); + busyDialog.show(); + QFuture future = QtConcurrent::run(MainWindow::setupNetworkProxy, activate); + while (!future.isFinished()) { + QCoreApplication::processEvents(); + ::Sleep(100); + } + busyDialog.hide(); } -void MainWindow::readSettings() { - QSettings settings(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::iniFileName()), - QSettings::IniFormat); +void MainWindow::readSettings() +{ + QSettings settings(qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::iniFileName()), QSettings::IniFormat); - if (settings.contains("window_geometry")) { - restoreGeometry(settings.value("window_geometry").toByteArray()); - } + if (settings.contains("window_geometry")) { + restoreGeometry(settings.value("window_geometry").toByteArray()); + } - if (settings.contains("window_split")) { - ui->splitter->restoreState(settings.value("window_split").toByteArray()); - } + if (settings.contains("window_split")) { + ui->splitter->restoreState(settings.value("window_split").toByteArray()); + } - if (settings.contains("log_split")) { - ui->topLevelSplitter->restoreState(settings.value("log_split").toByteArray()); - } + if (settings.contains("log_split")) { + ui->topLevelSplitter->restoreState(settings.value("log_split").toByteArray()); + } - bool filtersVisible = settings.value("filters_visible", false).toBool(); - setCategoryListVisible(filtersVisible); - ui->displayCategoriesBtn->setChecked(filtersVisible); + bool filtersVisible = settings.value("filters_visible", false).toBool(); + setCategoryListVisible(filtersVisible); + ui->displayCategoriesBtn->setChecked(filtersVisible); - int selectedExecutable = settings.value("selected_executable").toInt(); - setExecutableIndex(selectedExecutable); + int selectedExecutable = settings.value("selected_executable").toInt(); + setExecutableIndex(selectedExecutable); - if (settings.value("Settings/use_proxy", false).toBool()) { - activateProxy(true); - } + if (settings.value("Settings/use_proxy", false).toBool()) { + activateProxy(true); + } } -void MainWindow::storeSettings(QSettings& settings) { - settings.setValue("group_state", ui->groupCombo->currentIndex()); +void MainWindow::storeSettings(QSettings &settings) { + settings.setValue("group_state", ui->groupCombo->currentIndex()); - settings.setValue("window_geometry", saveGeometry()); - settings.setValue("window_split", ui->splitter->saveState()); - settings.setValue("log_split", ui->topLevelSplitter->saveState()); + settings.setValue("window_geometry", saveGeometry()); + settings.setValue("window_split", ui->splitter->saveState()); + settings.setValue("log_split", ui->topLevelSplitter->saveState()); - settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); + settings.setValue("browser_geometry", m_IntegratedBrowser.saveGeometry()); - settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); + settings.setValue("filters_visible", ui->displayCategoriesBtn->isChecked()); - settings.setValue("selected_executable", ui->executablesListBox->currentIndex()); + settings.setValue("selected_executable", + ui->executablesListBox->currentIndex()); - for (const std::pair kv : m_PersistedGeometry) { - QString key = QString("geometry/") + kv.first; - settings.setValue(key, kv.second->saveState()); - } + for (const std::pair kv : m_PersistedGeometry) { + QString key = QString("geometry/") + kv.first; + settings.setValue(key, kv.second->saveState()); + } } -ILockedWaitingForProcess* MainWindow::lock() { - if (m_LockDialog != nullptr) { - ++m_LockCount; - return m_LockDialog; - } - if (m_closing) - m_LockDialog = new WaitingOnCloseDialog(this); - else - m_LockDialog = new LockedDialog(this, true); - m_LockDialog->setModal(true); - m_LockDialog->show(); - setEnabled(false); - m_LockDialog->setEnabled(true); // What's the point otherwise? +ILockedWaitingForProcess* MainWindow::lock() +{ + if (m_LockDialog != nullptr) { ++m_LockCount; return m_LockDialog; -} - -void MainWindow::unlock() { - // If you come through here with a null lock pointer, it's a bug! - if (m_LockDialog == nullptr) { - qDebug("Unlocking main window when already unlocked"); - return; - } - --m_LockCount; - if (m_LockCount == 0) { - if (m_closing && m_LockDialog->canceled()) - m_closing = false; - m_LockDialog->hide(); - m_LockDialog->deleteLater(); - m_LockDialog = nullptr; - setEnabled(true); - } -} - -void MainWindow::on_btnRefreshData_clicked() { m_OrganizerCore.refreshDirectoryStructure(); } - -void MainWindow::on_tabWidget_currentChanged(int index) { - if (index == 0) { - m_OrganizerCore.refreshESPList(); - } else if (index == 1) { - m_OrganizerCore.refreshBSAList(); - } else if (index == 2) { - refreshDataTree(); - } else if (index == 3) { - refreshSaveList(); - } -} - -void MainWindow::installMod(QString fileName) { - try { - if (fileName.isEmpty()) { - QStringList extensions = m_OrganizerCore.installationManager()->getSupportedExtensions(); - for (auto iter = extensions.begin(); iter != extensions.end(); ++iter) { - *iter = "*." + *iter; - } - - fileName = - FileDialogMemory::getOpenFileName("installMod", this, tr("Choose Mod"), QString(), - tr("Mod Archive").append(QString(" (%1)").arg(extensions.join(" ")))); - } - - if (fileName.isEmpty()) { - return; - } else { - m_OrganizerCore.installMod(fileName, QString()); - } - } catch (const std::exception& e) { - reportError(e.what()); - } -} - -void MainWindow::on_startButton_clicked() { - const Executable& selectedExecutable(getSelectedExecutable()); - QString customOverwrite = - m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.m_Title).toString(); - m_OrganizerCore.spawnBinary(selectedExecutable.m_BinaryInfo, selectedExecutable.m_Arguments, - selectedExecutable.m_WorkingDirectory.length() != 0 - ? selectedExecutable.m_WorkingDirectory - : selectedExecutable.m_BinaryInfo.absolutePath(), - selectedExecutable.m_SteamAppID, customOverwrite); -} - -static HRESULT CreateShortcut(LPCWSTR targetFileName, LPCWSTR arguments, LPCSTR linkFileName, LPCWSTR description, - LPCTSTR iconFileName, int iconNumber, LPCWSTR currentDirectory) { - HRESULT result = E_INVALIDARG; - if ((targetFileName != nullptr) && (wcslen(targetFileName) > 0) && (arguments != nullptr) && - (linkFileName != nullptr) && (strlen(linkFileName) > 0) && (description != nullptr) && - (currentDirectory != nullptr)) { - - IShellLink* shellLink; - result = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, IID_IShellLink, (LPVOID*)&shellLink); - - if (!SUCCEEDED(result)) { - qCritical("failed to create IShellLink instance"); - return result; - } - - result = shellLink->SetPath(targetFileName); - if (!SUCCEEDED(result)) { - qCritical("failed to set target path %ls", targetFileName); - shellLink->Release(); - return result; - } - - result = shellLink->SetArguments(arguments); - if (!SUCCEEDED(result)) { - qCritical("failed to set arguments: %ls", arguments); - shellLink->Release(); - return result; - } - - if (wcslen(description) > 0) { - result = shellLink->SetDescription(description); - if (!SUCCEEDED(result)) { - qCritical("failed to set description: %ls", description); - shellLink->Release(); - return result; - } - } - - if (wcslen(currentDirectory) > 0) { - result = shellLink->SetWorkingDirectory(currentDirectory); - if (!SUCCEEDED(result)) { - qCritical("failed to set working directory: %ls", currentDirectory); - shellLink->Release(); - return result; - } - } - - if (iconFileName != nullptr) { - result = shellLink->SetIconLocation(iconFileName, iconNumber); - if (!SUCCEEDED(result)) { - qCritical("failed to load program icon: %ls %d", iconFileName, iconNumber); - shellLink->Release(); - return result; - } - } - - IPersistFile* persistFile; - result = shellLink->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile); - if (SUCCEEDED(result)) { - wchar_t linkFileNameW[MAX_PATH]; - if (MultiByteToWideChar(CP_ACP, 0, linkFileName, -1, linkFileNameW, MAX_PATH) > 0) { - result = persistFile->Save(linkFileNameW, TRUE); - } else { - qCritical("failed to create link: %s", linkFileName); - } - persistFile->Release(); - } else { - qCritical("failed to create IPersistFile instance"); - } - - shellLink->Release(); - } - return result; -} - -bool MainWindow::modifyExecutablesDialog() { - bool result = false; - try { - EditExecutablesDialog dialog(*m_OrganizerCore.executablesList(), *m_OrganizerCore.modList(), - m_OrganizerCore.currentProfile()); - if (dialog.exec() == QDialog::Accepted) { - m_OrganizerCore.setExecutablesList(dialog.getExecutablesList()); - result = true; - } - refreshExecutablesList(); - } catch (const std::exception& e) { - reportError(e.what()); - } - return result; -} - -void MainWindow::on_executablesListBox_currentIndexChanged(int index) { - QComboBox* executablesList = findChild("executablesListBox"); - - int previousIndex = m_OldExecutableIndex; - m_OldExecutableIndex = index; - - if (executablesList->isEnabled()) { - // I think the 2nd test is impossible - if ((index == 0) || (index > static_cast(m_OrganizerCore.executablesList()->size()))) { - if (modifyExecutablesDialog()) { - setExecutableIndex(previousIndex); - } - } else { - setExecutableIndex(index); - } - } -} - -void MainWindow::helpTriggered() { QWhatsThis::enterWhatsThisMode(); } - -void MainWindow::wikiTriggered() { - ::ShellExecuteW(nullptr, L"open", L"http://wiki.step-project.com/Guide:Mod_Organizer", nullptr, nullptr, - SW_SHOWNORMAL); -} - -void MainWindow::issueTriggered() { - ::ShellExecuteW(nullptr, L"open", L"http://github.com/LePresidente/modorganizer/issues", nullptr, nullptr, - SW_SHOWNORMAL); -} - -void MainWindow::tutorialTriggered() { - 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()); - } - } -} - -void MainWindow::on_actionInstallMod_triggered() { installMod(); } - -void MainWindow::on_actionAdd_Profile_triggered() { - for (;;) { - // Note: Calling this with an invalid profile name. Not quite sure why - ProfilesDialog profilesDialog(m_OrganizerCore.managedGame()->gameDirectory().absolutePath(), - m_OrganizerCore.managedGame(), this); - // workaround: need to disable monitoring of the saves directory, otherwise the active - // profile directory is locked - stopMonitorSaves(); - profilesDialog.exec(); - refreshSaveList(); // since the save list may now be outdated we have to refresh it completely - if (refreshProfiles() && !profilesDialog.failed()) { - break; - } - } - // addProfile(); -} - -void MainWindow::on_actionModify_Executables_triggered() { - if (modifyExecutablesDialog()) { - setExecutableIndex(m_OldExecutableIndex); - } -} - -void MainWindow::setModListSorting(int index) { - Qt::SortOrder order = ((index & 0x01) != 0) ? Qt::DescendingOrder : Qt::AscendingOrder; - int column = index >> 1; - ui->modList->header()->setSortIndicator(column, order); -} - -void MainWindow::setESPListSorting(int index) { - switch (index) { - case 0: { - ui->espList->header()->setSortIndicator(1, Qt::AscendingOrder); - } break; - case 1: { - ui->espList->header()->setSortIndicator(1, Qt::DescendingOrder); - } break; - case 2: { - ui->espList->header()->setSortIndicator(0, Qt::AscendingOrder); - } break; - case 3: { - ui->espList->header()->setSortIndicator(0, Qt::DescendingOrder); - } break; - } -} - -void MainWindow::refresher_progress(int percent) { - if (percent == 100) { - m_RefreshProgress->setVisible(false); - statusBar()->hide(); - } else if (!m_RefreshProgress->isVisible()) { - statusBar()->show(); - m_RefreshProgress->setVisible(true); - m_RefreshProgress->setRange(0, 100); - m_RefreshProgress->setValue(percent); - } -} - -void MainWindow::directory_refreshed() { - // some problem-reports may rely on the virtual directory tree so they need to be updated - // now - refreshDataTree(); - updateProblemsButton(); - statusBar()->hide(); -} - -void MainWindow::modorder_changed() { - for (unsigned int i = 0; i < m_OrganizerCore.currentProfile()->numMods(); ++i) { - int priority = m_OrganizerCore.currentProfile()->getModPriority(i); - if (m_OrganizerCore.currentProfile()->modEnabled(i)) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - // priorities in the directory structure are one higher because data is 0 - m_OrganizerCore.directoryStructure() - ->getOriginByName(ToWString(modInfo->internalName())) - .setPriority(priority + 1); - } - } + } + if (m_closing) + m_LockDialog = new WaitingOnCloseDialog(this); + else + m_LockDialog = new LockedDialog(this, true); + m_LockDialog->setModal(true); + m_LockDialog->show(); + setEnabled(false); + m_LockDialog->setEnabled(true); //What's the point otherwise? + ++m_LockCount; + return m_LockDialog; +} + +void MainWindow::unlock() +{ + //If you come through here with a null lock pointer, it's a bug! + if (m_LockDialog == nullptr) { + qDebug("Unlocking main window when already unlocked"); + return; + } + --m_LockCount; + if (m_LockCount == 0) { + if (m_closing && m_LockDialog->canceled()) + m_closing = false; + m_LockDialog->hide(); + m_LockDialog->deleteLater(); + m_LockDialog = nullptr; + setEnabled(true); + } +} + +void MainWindow::on_btnRefreshData_clicked() +{ + m_OrganizerCore.refreshDirectoryStructure(); +} + +void MainWindow::on_tabWidget_currentChanged(int index) +{ + if (index == 0) { + m_OrganizerCore.refreshESPList(); + } else if (index == 1) { m_OrganizerCore.refreshBSAList(); - m_OrganizerCore.currentProfile()->writeModlist(); - m_ArchiveListWriter.write(); - m_OrganizerCore.directoryStructure()->getFileRegister()->sortOrigins(); - - { // refresh selection - QModelIndex current = ui->modList->currentIndex(); - if (current.isValid()) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); - // clear caches on all mods conflicting with the moved mod - for (int i : modInfo->getModOverwrite()) { - ModInfo::getByIndex(i)->clearCaches(); - } - for (int i : modInfo->getModOverwritten()) { - ModInfo::getByIndex(i)->clearCaches(); - } - // update conflict check on the moved mod - modInfo->doConflictCheck(); - m_OrganizerCore.modList()->setOverwriteMarkers(modInfo->getModOverwrite(), modInfo->getModOverwritten()); - if (m_ModListSortProxy != nullptr) { - m_ModListSortProxy->invalidate(); - } - ui->modList->verticalScrollBar()->repaint(); - } - } -} - -void MainWindow::modInstalled(const QString& modName) { - QModelIndexList posList = - m_OrganizerCore.modList()->match(m_OrganizerCore.modList()->index(0, 0), Qt::DisplayRole, modName); - if (posList.count() == 1) { - ui->modList->scrollTo(posList.at(0)); - } -} - -void MainWindow::procError(QProcess::ProcessError error) { - reportError(tr("failed to spawn notepad.exe: %1").arg(error)); - this->sender()->deleteLater(); -} - -void MainWindow::procFinished(int, QProcess::ExitStatus) { this->sender()->deleteLater(); } - -void MainWindow::showMessage(const QString& message) { MessageDialog::showMessage(message, this); } - -void MainWindow::showError(const QString& message) { reportError(message); } - -void MainWindow::installMod_clicked() { installMod(); } - -void MainWindow::modRenamed(const QString& oldName, const QString& newName) { - Profile::renameModInAllProfiles(oldName, newName); - - // immediately refresh the active profile because the data in memory is invalid - m_OrganizerCore.currentProfile()->refreshModStatus(); - - // also fix the directory structure - try { - if (m_OrganizerCore.directoryStructure()->originExists(ToWString(oldName))) { - FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(oldName)); - origin.setName(ToWString(newName)); - } else { - } - } 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) { - const FileEntry::Ptr 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)); - - QString fullNewPath = ToQString(newOrigin.getPath()) + "\\" + filePath; - WIN32_FIND_DATAW findData; - HANDLE hFind; - hFind = ::FindFirstFileW(ToWString(fullNewPath).c_str(), &findData); - filePtr->addOrigin(newOrigin.getID(), findData.ftCreationTime, L""); - FindClose(hFind); - } - 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())); - } - } else { - // this is probably not an error, the specified path is likely a directory - } -} - -QTreeWidgetItem* MainWindow::addFilterItem(QTreeWidgetItem* root, const QString& name, int categoryID, - ModListSortProxy::FilterType type) { - QTreeWidgetItem* item = new QTreeWidgetItem(QStringList(name)); - item->setData(0, Qt::ToolTipRole, name); - item->setData(0, Qt::UserRole, categoryID); - item->setData(0, Qt::UserRole + 1, type); - if (root != nullptr) { - root->addChild(item); - } else { - ui->categoriesList->addTopLevelItem(item); - } - return item; -} - -void MainWindow::addContentFilters() { - for (unsigned i = 0; i < ModInfo::NUM_CONTENT_TYPES; ++i) { - addFilterItem(nullptr, tr("").arg(ModInfo::getContentTypeName(i)), i, - ModListSortProxy::TYPE_CONTENT); - } -} - -void MainWindow::addCategoryFilters(QTreeWidgetItem* root, const std::set& categoriesUsed, int targetID) { - for (unsigned int i = 1; i < static_cast(m_CategoryFactory.numCategories()); ++i) { - if ((m_CategoryFactory.getParentID(i) == targetID)) { - int categoryID = m_CategoryFactory.getCategoryID(i); - if (categoriesUsed.find(categoryID) != categoriesUsed.end()) { - QTreeWidgetItem* item = addFilterItem(root, m_CategoryFactory.getCategoryName(i), categoryID, - ModListSortProxy::TYPE_CATEGORY); - if (m_CategoryFactory.hasChildren(i)) { - addCategoryFilters(item, categoriesUsed, categoryID); - } - } - } - } + } else if (index == 2) { + refreshDataTree(); + } else if (index == 3) { + refreshSaveList(); + } } -void MainWindow::refreshFilters() { - QItemSelection currentSelection = ui->modList->selectionModel()->selection(); - - QVariant currentIndexName = ui->modList->currentIndex().data(); - ui->modList->setCurrentIndex(QModelIndex()); - - QStringList selectedItems; - for (QTreeWidgetItem* item : ui->categoriesList->selectedItems()) { - selectedItems.append(item->text(0)); - } - - ui->categoriesList->clear(); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CHECKED, ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNCHECKED, - ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE, - ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_MANAGED, - ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNMANAGED, - ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY, - ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CONFLICT, - ModListSortProxy::TYPE_SPECIAL); - addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED, - ModListSortProxy::TYPE_SPECIAL); - - addContentFilters(); - std::set categoriesUsed; - for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIdx); - for (int categoryID : modInfo->getCategories()) { - int currentID = categoryID; - std::set cycleTest; - // also add parents so they show up in the tree - while (currentID != 0) { - categoriesUsed.insert(currentID); - if (!cycleTest.insert(currentID).second) { - qWarning("cycle in categories: %s", qPrintable(SetJoin(cycleTest, ", "))); - break; - } - currentID = m_CategoryFactory.getParentID(m_CategoryFactory.getCategoryIndex(currentID)); - } - } - } - - addCategoryFilters(nullptr, categoriesUsed, 0); - - for (const QString& item : selectedItems) { - QList matches = - ui->categoriesList->findItems(item, Qt::MatchFixedString | Qt::MatchRecursive); - if (matches.size() > 0) { - matches.at(0)->setSelected(true); - } - } - ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); - QModelIndexList matchList; - if (currentIndexName.isValid()) { - matchList = ui->modList->model()->match(ui->modList->model()->index(0, 0), Qt::DisplayRole, currentIndexName); - } - if (matchList.size() > 0) { - ui->modList->setCurrentIndex(matchList.at(0)); - } -} +void MainWindow::installMod(QString fileName) +{ + try { + if (fileName.isEmpty()) { + QStringList extensions = m_OrganizerCore.installationManager()->getSupportedExtensions(); + for (auto iter = extensions.begin(); iter != extensions.end(); ++iter) { + *iter = "*." + *iter; + } -void MainWindow::renameMod_clicked() { - try { - ui->modList->edit(ui->modList->currentIndex()); - } catch (const std::exception& e) { - reportError(tr("failed to rename mod: %1").arg(e.what())); - } -} - -void MainWindow::restoreBackup_clicked() { - QRegExp backupRegEx("(.*)_backup[0-9]*$"); - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - if (backupRegEx.indexIn(modInfo->name()) != -1) { - QString regName = backupRegEx.cap(1); - QDir modDir(QDir::fromNativeSeparators(m_OrganizerCore.settings().getModDirectory())); - if (!modDir.exists(regName) || - (QMessageBox::question(this, 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_OrganizerCore.settings().getModDirectory()) + "/" + regName; - if (!modDir.rename(modInfo->absolutePath(), destinationPath)) { - reportError( - tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath)); - } - m_OrganizerCore.refreshModList(); - } - } + fileName = FileDialogMemory::getOpenFileName("installMod", this, tr("Choose Mod"), QString(), + tr("Mod Archive").append(QString(" (%1)").arg(extensions.join(" ")))); } -} - -void MainWindow::modlistChanged(const QModelIndex&, int) { m_OrganizerCore.currentProfile()->writeModlist(); } -void MainWindow::modlistSelectionChanged(const QModelIndex& current, const QModelIndex&) { - if (current.isValid()) { - ModInfo::Ptr selectedMod = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); - m_OrganizerCore.modList()->setOverwriteMarkers(selectedMod->getModOverwrite(), - selectedMod->getModOverwritten()); + if (fileName.isEmpty()) { + return; } else { - m_OrganizerCore.modList()->setOverwriteMarkers(std::set(), std::set()); - } - /* if ((m_ModListSortProxy != nullptr) - && !m_ModListSortProxy->beingInvalidated()) { - m_ModListSortProxy->invalidate(); - }*/ - ui->modList->verticalScrollBar()->repaint(); -} - -void MainWindow::modlistSelectionsChanged(const QItemSelection& selected) { - m_OrganizerCore.pluginList()->highlightPlugins(selected, *m_OrganizerCore.directoryStructure(), - *m_OrganizerCore.currentProfile()); - ui->espList->verticalScrollBar()->repaint(); -} - -void MainWindow::esplistSelectionsChanged(const QItemSelection& selected) { - m_OrganizerCore.modList()->highlightMods(selected, *m_OrganizerCore.directoryStructure()); - ui->modList->verticalScrollBar()->repaint(); -} - -void MainWindow::modListSortIndicatorChanged(int, Qt::SortOrder) { ui->modList->verticalScrollBar()->repaint(); } - -void MainWindow::removeMod_clicked() { - try { - QItemSelectionModel* selection = ui->modList->selectionModel(); - if (selection->hasSelection() && selection->selectedRows().count() > 1) { - QString mods; - QStringList modNames; - for (QModelIndex idx : selection->selectedRows()) { - QString name = idx.data().toString(); - if (!ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->isRegular()) { - continue; - } - mods += "
  • " + name + "
  • "; - modNames.append(name); - } - if (QMessageBox::question(this, 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 - for (QString name : modNames) { - m_OrganizerCore.modList()->removeRowForce(ModInfo::getIndex(name), QModelIndex()); - } - } - } else { - m_OrganizerCore.modList()->removeRow(m_ContextRow, QModelIndex()); - } - } catch (const std::exception& e) { - reportError(tr("failed to remove mod: %1").arg(e.what())); - } -} - -void MainWindow::modRemoved(const QString& fileName) { - if (!fileName.isEmpty() && !QFileInfo(fileName).isAbsolute()) { - int index = m_OrganizerCore.downloadManager()->indexByName(fileName); - if (index >= 0) { - m_OrganizerCore.downloadManager()->markUninstalled(index); - } + m_OrganizerCore.installMod(fileName, QString()); } + } catch (const std::exception &e) { + reportError(e.what()); + } } -void MainWindow::reinstallMod_clicked() { - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - QString installationFile = modInfo->getInstallationFile(); - if (installationFile.length() != 0) { - QString fullInstallationFile; - QFileInfo fileInfo(installationFile); - if (fileInfo.isAbsolute()) { - if (fileInfo.exists()) { - fullInstallationFile = installationFile; - } else { - fullInstallationFile = - m_OrganizerCore.downloadManager()->getOutputDirectory() + "/" + fileInfo.fileName(); - } - } else { - fullInstallationFile = m_OrganizerCore.downloadManager()->getOutputDirectory() + "/" + installationFile; - } - if (QFile::exists(fullInstallationFile)) { - m_OrganizerCore.installMod(fullInstallationFile, modInfo->name()); - } else { - QMessageBox::information(this, tr("Failed"), tr("Installation file no longer exists")); - } - } else { - QMessageBox::information(this, tr("Failed"), - tr("Mods installed with old versions of MO can't be reinstalled in this way.")); +void MainWindow::on_startButton_clicked() { + const Executable &selectedExecutable(getSelectedExecutable()); + QString customOverwrite = m_OrganizerCore.currentProfile()->setting("custom_overwrites", selectedExecutable.m_Title).toString(); + m_OrganizerCore.spawnBinary( + selectedExecutable.m_BinaryInfo, selectedExecutable.m_Arguments, + selectedExecutable.m_WorkingDirectory.length() != 0 + ? selectedExecutable.m_WorkingDirectory + : selectedExecutable.m_BinaryInfo.absolutePath(), + selectedExecutable.m_SteamAppID, customOverwrite); +} + +static HRESULT CreateShortcut(LPCWSTR targetFileName, LPCWSTR arguments, + LPCSTR linkFileName, LPCWSTR description, + LPCTSTR iconFileName, int iconNumber, + LPCWSTR currentDirectory) +{ + HRESULT result = E_INVALIDARG; + if ((targetFileName != nullptr) && (wcslen(targetFileName) > 0) && + (arguments != nullptr) && + (linkFileName != nullptr) && (strlen(linkFileName) > 0) && + (description != nullptr) && + (currentDirectory != nullptr)) { + + IShellLink* shellLink; + result = CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER, + IID_IShellLink, (LPVOID*)&shellLink); + + if (!SUCCEEDED(result)) { + qCritical("failed to create IShellLink instance"); + return result; + } + + result = shellLink->SetPath(targetFileName); + if (!SUCCEEDED(result)) { + qCritical("failed to set target path %ls", targetFileName); + shellLink->Release(); + return result; + } + + result = shellLink->SetArguments(arguments); + if (!SUCCEEDED(result)) { + qCritical("failed to set arguments: %ls", arguments); + shellLink->Release(); + return result; + } + + if (wcslen(description) > 0) { + result = shellLink->SetDescription(description); + if (!SUCCEEDED(result)) { + qCritical("failed to set description: %ls", description); + shellLink->Release(); + return result; + } } -} -void MainWindow::resumeDownload(int downloadIndex) { - if (NexusInterface::instance()->getAccessManager()->loggedIn()) { - m_OrganizerCore.downloadManager()->resumeDownload(downloadIndex); - } else { - QString username, password; - if (m_OrganizerCore.settings().getNexusLogin(username, password)) { - m_OrganizerCore.doAfterLogin([this, downloadIndex]() { this->resumeDownload(downloadIndex); }); - NexusInterface::instance()->getAccessManager()->login(username, password); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to resume a download"), this); - } + if (wcslen(currentDirectory) > 0) { + result = shellLink->SetWorkingDirectory(currentDirectory); + if (!SUCCEEDED(result)) { + qCritical("failed to set working directory: %ls", currentDirectory); + shellLink->Release(); + return result; + } } -} -void MainWindow::endorseMod(ModInfo::Ptr mod) { - if (NexusInterface::instance()->getAccessManager()->loggedIn()) { - mod->endorse(true); - } else { - QString username, password; - if (m_OrganizerCore.settings().getNexusLogin(username, password)) { - m_OrganizerCore.doAfterLogin(boost::bind(&MainWindow::endorseMod, this, mod)); - NexusInterface::instance()->getAccessManager()->login(username, password); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); - } + if (iconFileName != nullptr) { + result = shellLink->SetIconLocation(iconFileName, iconNumber); + if (!SUCCEEDED(result)) { + qCritical("failed to load program icon: %ls %d", iconFileName, iconNumber); + shellLink->Release(); + return result; + } } -} - -void MainWindow::endorse_clicked() { endorseMod(ModInfo::getByIndex(m_ContextRow)); } -void MainWindow::dontendorse_clicked() { ModInfo::getByIndex(m_ContextRow)->setNeverEndorse(); } - -void MainWindow::unendorse_clicked() { - QString username, password; - if (NexusInterface::instance()->getAccessManager()->loggedIn()) { - ModInfo::getByIndex(m_ContextRow)->endorse(false); + IPersistFile *persistFile; + result = shellLink->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile); + if (SUCCEEDED(result)) { + wchar_t linkFileNameW[MAX_PATH]; + if (MultiByteToWideChar(CP_ACP, 0, linkFileName, -1, linkFileNameW, MAX_PATH) > 0) { + result = persistFile->Save(linkFileNameW, TRUE); + } else { + qCritical("failed to create link: %s", linkFileName); + } + persistFile->Release(); } else { - if (m_OrganizerCore.settings().getNexusLogin(username, password)) { - m_OrganizerCore.doAfterLogin([this]() { this->unendorse_clicked(); }); - NexusInterface::instance()->getAccessManager()->login(username, password); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); - } + qCritical("failed to create IPersistFile instance"); } -} -void MainWindow::loginFailed(const QString& error) { - qDebug("login failed: %s", qPrintable(error)); - statusBar()->hide(); -} - -void MainWindow::windowTutorialFinished(const QString& windowName) { - m_OrganizerCore.settings().directInterface().setValue(QString("CompletedWindowTutorials/") + windowName, true); + shellLink->Release(); + } + return result; } -void MainWindow::overwriteClosed(int) { - OverwriteInfoDialog* dialog = this->findChild("__overwriteDialog"); - if (dialog != nullptr) { - m_OrganizerCore.modList()->modInfoChanged(dialog->modInfo()); - dialog->deleteLater(); - } - m_OrganizerCore.refreshDirectoryStructure(); -} -void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab) { - if (!m_OrganizerCore.modList()->modInfoAboutToChange(modInfo)) { - qDebug("A different mod information dialog is open. If this is incorrect, please restart MO"); - return; - } - std::vector flags = modInfo->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { - QDialog* dialog = this->findChild("__overwriteDialog"); - try { - if (dialog == nullptr) { - dialog = new OverwriteInfoDialog(modInfo, this); - dialog->setObjectName("__overwriteDialog"); - } else { - qobject_cast(dialog)->setModInfo(modInfo); - } - dialog->show(); - dialog->raise(); - dialog->activateWindow(); - connect(dialog, SIGNAL(finished(int)), this, SLOT(overwriteClosed(int))); - } catch (const std::exception& e) { - reportError(tr("Failed to display overwrite dialog: %1").arg(e.what())); - } - } else { - modInfo->saveMeta(); - ModInfoDialog dialog(modInfo, m_OrganizerCore.directoryStructure(), modInfo->hasFlag(ModInfo::FLAG_FOREIGN), - this); - connect(&dialog, SIGNAL(nexusLinkActivated(QString)), this, SLOT(nexusLinkActivated(QString))); - connect(&dialog, SIGNAL(downloadRequest(QString)), &m_OrganizerCore, SLOT(downloadRequestedNXM(QString))); - connect(&dialog, SIGNAL(modOpen(QString, int)), this, SLOT(displayModInformation(QString, int)), - Qt::QueuedConnection); - connect(&dialog, SIGNAL(modOpenNext()), this, SLOT(modOpenNext()), Qt::QueuedConnection); - connect(&dialog, SIGNAL(modOpenPrev()), this, SLOT(modOpenPrev()), Qt::QueuedConnection); - connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); - connect(&dialog, SIGNAL(endorseMod(ModInfo::Ptr)), this, SLOT(endorseMod(ModInfo::Ptr))); - - dialog.openTab(tab); - dialog.restoreTabState(m_OrganizerCore.settings().directInterface().value("mod_info_tabs").toByteArray()); - dialog.exec(); - m_OrganizerCore.settings().directInterface().setValue("mod_info_tabs", dialog.saveTabState()); - - modInfo->saveMeta(); - emit modInfoDisplayed(); - m_OrganizerCore.modList()->modInfoChanged(modInfo); - } - - if (m_OrganizerCore.currentProfile()->modEnabled(index) && !modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { - FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(modInfo->name())); - origin.enable(false); - - if (m_OrganizerCore.directoryStructure()->originExists(ToWString(modInfo->name()))) { - FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(modInfo->name())); - origin.enable(false); - - m_OrganizerCore.directoryRefresher()->addModToStructure( - m_OrganizerCore.directoryStructure(), modInfo->name(), - m_OrganizerCore.currentProfile()->getModPriority(index), modInfo->absolutePath(), modInfo->stealFiles(), - modInfo->archives()); - DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure()); - m_OrganizerCore.refreshLists(); - } +bool MainWindow::modifyExecutablesDialog() +{ + bool result = false; + try { + EditExecutablesDialog dialog(*m_OrganizerCore.executablesList(), + *m_OrganizerCore.modList(), + m_OrganizerCore.currentProfile()); + if (dialog.exec() == QDialog::Accepted) { + m_OrganizerCore.setExecutablesList(dialog.getExecutablesList()); + result = true; } + refreshExecutablesList(); + } catch (const std::exception &e) { + reportError(e.what()); + } + return result; } -bool MainWindow::closeWindow() { return close(); } - -void MainWindow::setWindowEnabled(bool enabled) { setEnabled(enabled); } - -void MainWindow::modOpenNext() { - QModelIndex index = m_ModListSortProxy->mapFromSource(m_OrganizerCore.modList()->index(m_ContextRow, 0)); - index = m_ModListSortProxy->index((index.row() + 1) % m_ModListSortProxy->rowCount(), 0); - - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); - ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); - std::vector flags = mod->getFlags(); - if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) || - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end())) { - // skip overwrite and backups - modOpenNext(); - } else { - displayModInformation(m_ContextRow); - } -} +void MainWindow::on_executablesListBox_currentIndexChanged(int index) +{ + QComboBox* executablesList = findChild("executablesListBox"); -void MainWindow::modOpenPrev() { - QModelIndex index = m_ModListSortProxy->mapFromSource(m_OrganizerCore.modList()->index(m_ContextRow, 0)); - int row = index.row() - 1; - if (row == -1) { - row = m_ModListSortProxy->rowCount() - 1; - } + int previousIndex = m_OldExecutableIndex; + m_OldExecutableIndex = index; - m_ContextRow = m_ModListSortProxy->mapToSource(m_ModListSortProxy->index(row, 0)).row(); - ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); - std::vector flags = mod->getFlags(); - if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) || - (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end())) { - // skip overwrite and backups - modOpenPrev(); + if (executablesList->isEnabled()) { + //I think the 2nd test is impossible + if ((index == 0) || (index > static_cast(m_OrganizerCore.executablesList()->size()))) { + if (modifyExecutablesDialog()) { + setExecutableIndex(previousIndex); + } } else { - displayModInformation(m_ContextRow); + setExecutableIndex(index); } + } } -void MainWindow::displayModInformation(const QString& modName, int tab) { - unsigned int index = ModInfo::getIndex(modName); - if (index == UINT_MAX) { - qCritical("failed to resolve mod name %s", modName.toUtf8().constData()); - return; - } - - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - displayModInformation(modInfo, index, tab); +void MainWindow::helpTriggered() +{ + QWhatsThis::enterWhatsThisMode(); } -void MainWindow::displayModInformation(int row, int tab) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - displayModInformation(modInfo, row, tab); -} - -void MainWindow::ignoreMissingData_clicked() { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - QDir(info->absolutePath()).mkdir("textures"); - info->testValid(); - connect(this, SIGNAL(modListDataChanged(QModelIndex, QModelIndex)), m_OrganizerCore.modList(), - SIGNAL(dataChanged(QModelIndex, QModelIndex))); - - emit modListDataChanged( - m_OrganizerCore.modList()->index(m_ContextRow, 0), - m_OrganizerCore.modList()->index(m_ContextRow, m_OrganizerCore.modList()->columnCount() - 1)); +void MainWindow::wikiTriggered() +{ + ::ShellExecuteW(nullptr, L"open", L"http://wiki.step-project.com/Guide:Mod_Organizer", nullptr, nullptr, SW_SHOWNORMAL); } -void MainWindow::visitOnNexus_clicked() { - int modID = - m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(m_ContextRow, 0), Qt::UserRole).toInt(); - if (modID > 0) { - nexusLinkActivated(NexusInterface::instance()->getModURL(modID)); - } else { - MessageDialog::showMessage(tr("Nexus ID for this Mod is unknown"), this); - } +void MainWindow::issueTriggered() +{ + ::ShellExecuteW(nullptr, L"open", L"http://github.com/LePresidente/modorganizer/issues", nullptr, nullptr, SW_SHOWNORMAL); } -void MainWindow::visitWebPage_clicked() { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - if (info->getURL() != "") { - linkClicked(info->getURL()); - } else { - MessageDialog::showMessage(tr("Web page for this mod is unknown"), this); +void MainWindow::tutorialTriggered() +{ + 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()); } + } } -void MainWindow::openExplorer_clicked() { - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - ::ShellExecuteW(nullptr, L"explore", ToWString(modInfo->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); +void MainWindow::on_actionInstallMod_triggered() +{ + installMod(); } -void MainWindow::information_clicked() { - try { - displayModInformation(m_ContextRow); - } catch (const std::exception& e) { - reportError(e.what()); +void MainWindow::on_actionAdd_Profile_triggered() +{ + for (;;) { + //Note: Calling this with an invalid profile name. Not quite sure why + ProfilesDialog profilesDialog(m_OrganizerCore.managedGame()->gameDirectory().absolutePath(), + m_OrganizerCore.managedGame(), + this); + // workaround: need to disable monitoring of the saves directory, otherwise the active + // profile directory is locked + stopMonitorSaves(); + profilesDialog.exec(); + refreshSaveList(); // since the save list may now be outdated we have to refresh it completely + if (refreshProfiles() && !profilesDialog.failed()) { + break; } + } +// addProfile(); } -void MainWindow::createEmptyMod_clicked() { - GuessedValue name; - name.setFilter(&fixDirectoryName); - - while (name->isEmpty()) { - bool ok; - name.update(QInputDialog::getText(this, tr("Create Mod..."), - tr("This will create an empty mod.\n" - "Please enter a name:"), - QLineEdit::Normal, "", &ok), - GUESS_USER); - if (!ok) { - return; - } - } - - if (m_OrganizerCore.getMod(name) != nullptr) { - reportError(tr("A mod with this name already exists")); - return; - } - - IModInterface* newMod = m_OrganizerCore.createMod(name); - if (newMod == nullptr) { - return; - } - - m_OrganizerCore.refreshModList(); +void MainWindow::on_actionModify_Executables_triggered() +{ + if (modifyExecutablesDialog()) { + setExecutableIndex(m_OldExecutableIndex); + } } -void MainWindow::createModFromOverwrite() { - GuessedValue name; - name.setFilter(&fixDirectoryName); - while (name->isEmpty()) { - bool ok; - name.update(QInputDialog::getText(this, 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; - } - } - - if (m_OrganizerCore.getMod(name) != nullptr) { - reportError(tr("A mod with this name already exists")); - return; - } - - IModInterface* newMod = m_OrganizerCore.createMod(name); - if (newMod == nullptr) { - return; - } - - unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { - std::vector flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); - }); - - ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(overwriteIndex); - shellMove(QStringList(QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"), - QStringList(QDir::toNativeSeparators(newMod->absolutePath())), this); - - m_OrganizerCore.refreshModList(); +void MainWindow::setModListSorting(int index) +{ + Qt::SortOrder order = ((index & 0x01) != 0) ? Qt::DescendingOrder : Qt::AscendingOrder; + int column = index >> 1; + ui->modList->header()->setSortIndicator(column, order); } -void MainWindow::clearOverwrite() { - unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { - std::vector flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); - }); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(overwriteIndex); - if (modInfo) { - QDir overwriteDir(modInfo->absolutePath()); - if (QMessageBox::question(this, 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)) - delList.push_back(overwriteDir.absoluteFilePath(f)); - shellDelete(delList, true); - updateProblemsButton(); - } - } -} -void MainWindow::cancelModListEditor() { - ui->modList->setEnabled(false); - ui->modList->setEnabled(true); +void MainWindow::setESPListSorting(int index) +{ + switch (index) { + case 0: { + ui->espList->header()->setSortIndicator(1, Qt::AscendingOrder); + } break; + case 1: { + ui->espList->header()->setSortIndicator(1, Qt::DescendingOrder); + } break; + case 2: { + ui->espList->header()->setSortIndicator(0, Qt::AscendingOrder); + } break; + case 3: { + ui->espList->header()->setSortIndicator(0, Qt::DescendingOrder); + } break; + } } -void MainWindow::on_modList_doubleClicked(const QModelIndex& index) { - if (!index.isValid()) { - return; - } - - if (m_OrganizerCore.modList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) { - // don't interpret double click if we only just checked a mod - return; - } - - QModelIndex sourceIdx = mapToModel(m_OrganizerCore.modList(), index); - if (!sourceIdx.isValid()) { - return; - } - - try { - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); - displayModInformation(sourceIdx.row()); - // workaround to cancel the editor that might have opened because of - // selection-click - ui->modList->closePersistentEditor(index); - } catch (const std::exception& e) { - reportError(e.what()); +void MainWindow::refresher_progress(int percent) +{ + if (percent == 100) { + m_RefreshProgress->setVisible(false); + statusBar()->hide(); + } else if (!m_RefreshProgress->isVisible()) { + statusBar()->show(); + m_RefreshProgress->setVisible(true); + m_RefreshProgress->setRange(0, 100); + m_RefreshProgress->setValue(percent); + } +} + +void MainWindow::directory_refreshed() +{ + // some problem-reports may rely on the virtual directory tree so they need to be updated + // now + refreshDataTree(); + updateProblemsButton(); + statusBar()->hide(); +} + +void MainWindow::modorder_changed() +{ + for (unsigned int i = 0; i < m_OrganizerCore.currentProfile()->numMods(); ++i) { + int priority = m_OrganizerCore.currentProfile()->getModPriority(i); + if (m_OrganizerCore.currentProfile()->modEnabled(i)) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + // priorities in the directory structure are one higher because data is 0 + m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(modInfo->internalName())).setPriority(priority + 1); + } + } + m_OrganizerCore.refreshBSAList(); + m_OrganizerCore.currentProfile()->writeModlist(); + m_ArchiveListWriter.write(); + m_OrganizerCore.directoryStructure()->getFileRegister()->sortOrigins(); + + { // refresh selection + QModelIndex current = ui->modList->currentIndex(); + if (current.isValid()) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); + // clear caches on all mods conflicting with the moved mod + for (int i : modInfo->getModOverwrite()) { + ModInfo::getByIndex(i)->clearCaches(); + } + for (int i : modInfo->getModOverwritten()) { + ModInfo::getByIndex(i)->clearCaches(); + } + // update conflict check on the moved mod + modInfo->doConflictCheck(); + m_OrganizerCore.modList()->setOverwriteMarkers(modInfo->getModOverwrite(), modInfo->getModOverwritten()); + if (m_ModListSortProxy != nullptr) { + m_ModListSortProxy->invalidate(); + } + ui->modList->verticalScrollBar()->repaint(); } + } } -bool MainWindow::populateMenuCategories(QMenu* menu, int targetID) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - const std::set& categories = modInfo->getCategories(); - - bool childEnabled = false; - - for (unsigned int i = 1; i < m_CategoryFactory.numCategories(); ++i) { - if (m_CategoryFactory.getParentID(i) == targetID) { - QMenu* targetMenu = menu; - if (m_CategoryFactory.hasChildren(i)) { - targetMenu = menu->addMenu(m_CategoryFactory.getCategoryName(i).replace('&', "&&")); - } - - int id = m_CategoryFactory.getCategoryID(i); - QScopedPointer checkBox(new QCheckBox(targetMenu)); - bool enabled = categories.find(id) != categories.end(); - checkBox->setText(m_CategoryFactory.getCategoryName(i).replace('&', "&&")); - if (enabled) { - childEnabled = true; - } - checkBox->setChecked(enabled ? Qt::Checked : Qt::Unchecked); +void MainWindow::modInstalled(const QString &modName) +{ + QModelIndexList posList = + m_OrganizerCore.modList()->match(m_OrganizerCore.modList()->index(0, 0), Qt::DisplayRole, modName); + if (posList.count() == 1) { + ui->modList->scrollTo(posList.at(0)); + } +} - QScopedPointer checkableAction(new QWidgetAction(targetMenu)); - checkableAction->setDefaultWidget(checkBox.take()); - checkableAction->setData(id); - targetMenu->addAction(checkableAction.take()); +void MainWindow::procError(QProcess::ProcessError error) +{ + reportError(tr("failed to spawn notepad.exe: %1").arg(error)); + this->sender()->deleteLater(); +} - if (m_CategoryFactory.hasChildren(i)) { - if (populateMenuCategories(targetMenu, m_CategoryFactory.getCategoryID(i)) || enabled) { - targetMenu->setIcon(QIcon(":/MO/gui/resources/check.png")); - } - } - } - } - return childEnabled; +void MainWindow::procFinished(int, QProcess::ExitStatus) +{ + this->sender()->deleteLater(); } -void MainWindow::replaceCategoriesFromMenu(QMenu* menu, int modRow) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modRow); - for (QAction* action : menu->actions()) { - if (action->menu() != nullptr) { - replaceCategoriesFromMenu(action->menu(), modRow); - } else { - QWidgetAction* widgetAction = qobject_cast(action); - if (widgetAction != nullptr) { - QCheckBox* checkbox = qobject_cast(widgetAction->defaultWidget()); - modInfo->setCategory(widgetAction->data().toInt(), checkbox->isChecked()); - } - } - } +void MainWindow::showMessage(const QString &message) +{ + MessageDialog::showMessage(message, this); } -void MainWindow::addRemoveCategoriesFromMenu(QMenu* menu, int modRow, int referenceRow) { - if (referenceRow != -1 && referenceRow != modRow) { - ModInfo::Ptr editedModInfo = ModInfo::getByIndex(referenceRow); - for (QAction* action : menu->actions()) { - if (action->menu() != nullptr) { - addRemoveCategoriesFromMenu(action->menu(), modRow, referenceRow); - } else { - QWidgetAction* widgetAction = qobject_cast(action); - if (widgetAction != nullptr) { - QCheckBox* checkbox = qobject_cast(widgetAction->defaultWidget()); - int categoryId = widgetAction->data().toInt(); - bool checkedBefore = editedModInfo->categorySet(categoryId); - bool checkedAfter = checkbox->isChecked(); - - if (checkedBefore != checkedAfter) { // only update if the category was changed on the edited mod - ModInfo::Ptr currentModInfo = ModInfo::getByIndex(modRow); - currentModInfo->setCategory(categoryId, checkedAfter); - } - } - } - } - } else { - replaceCategoriesFromMenu(menu, modRow); - } +void MainWindow::showError(const QString &message) +{ + reportError(message); } -void MainWindow::addRemoveCategories_MenuHandler() { - QMenu* menu = qobject_cast(sender()); - if (menu == nullptr) { - qCritical("not a menu?"); - return; - } +void MainWindow::installMod_clicked() +{ + installMod(); +} - QList selected; - for (const QModelIndex& idx : ui->modList->selectionModel()->selectedRows()) { - selected.append(QPersistentModelIndex(idx)); - } +void MainWindow::modRenamed(const QString &oldName, const QString &newName) +{ + Profile::renameModInAllProfiles(oldName, newName); - if (selected.size() > 0) { - int minRow = INT_MAX; - int maxRow = -1; + // immediately refresh the active profile because the data in memory is invalid + m_OrganizerCore.currentProfile()->refreshModStatus(); - for (const QPersistentModelIndex& idx : selected) { - qDebug("change categories on: %s", qPrintable(idx.data().toString())); - QModelIndex modIdx = mapToModel(m_OrganizerCore.modList(), idx); - if (modIdx.row() != m_ContextIdx.row()) { - addRemoveCategoriesFromMenu(menu, modIdx.row(), m_ContextIdx.row()); - } - if (idx.row() < minRow) - minRow = idx.row(); - if (idx.row() > maxRow) - maxRow = idx.row(); - } - replaceCategoriesFromMenu(menu, m_ContextIdx.row()); + // also fix the directory structure + try { + if (m_OrganizerCore.directoryStructure()->originExists(ToWString(oldName))) { + FilesOrigin &origin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(oldName)); + origin.setName(ToWString(newName)); + } else { - m_OrganizerCore.modList()->notifyChange(minRow, maxRow + 1); + } + } catch (const std::exception &e) { + reportError(tr("failed to change origin name: %1").arg(e.what())); + } +} - for (const QPersistentModelIndex& idx : selected) { - ui->modList->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); - } +void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName) +{ + const FileEntry::Ptr 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)); + + QString fullNewPath = ToQString(newOrigin.getPath()) + "\\" + filePath; + WIN32_FIND_DATAW findData; + HANDLE hFind; + hFind = ::FindFirstFileW(ToWString(fullNewPath).c_str(), &findData); + filePtr->addOrigin(newOrigin.getID(), findData.ftCreationTime, L""); + FindClose(hFind); + } + 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())); + } + } else { + // this is probably not an error, the specified path is likely a directory + } +} + +QTreeWidgetItem *MainWindow::addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID, ModListSortProxy::FilterType type) +{ + QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(name)); + item->setData(0, Qt::ToolTipRole, name); + item->setData(0, Qt::UserRole, categoryID); + item->setData(0, Qt::UserRole + 1, type); + if (root != nullptr) { + root->addChild(item); + } else { + ui->categoriesList->addTopLevelItem(item); + } + return item; +} + +void MainWindow::addContentFilters() +{ + for (unsigned i = 0; i < ModInfo::NUM_CONTENT_TYPES; ++i) { + addFilterItem(nullptr, tr("").arg(ModInfo::getContentTypeName(i)), i, ModListSortProxy::TYPE_CONTENT); + } +} + +void MainWindow::addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID) +{ + for (unsigned int i = 1; + i < static_cast(m_CategoryFactory.numCategories()); ++i) { + if ((m_CategoryFactory.getParentID(i) == targetID)) { + int categoryID = m_CategoryFactory.getCategoryID(i); + if (categoriesUsed.find(categoryID) != categoriesUsed.end()) { + QTreeWidgetItem *item = + addFilterItem(root, m_CategoryFactory.getCategoryName(i), + categoryID, ModListSortProxy::TYPE_CATEGORY); + if (m_CategoryFactory.hasChildren(i)) { + addCategoryFilters(item, categoriesUsed, categoryID); + } + } + } + } +} + +void MainWindow::refreshFilters() +{ + QItemSelection currentSelection = ui->modList->selectionModel()->selection(); + + QVariant currentIndexName = ui->modList->currentIndex().data(); + ui->modList->setCurrentIndex(QModelIndex()); + + QStringList selectedItems; + for (QTreeWidgetItem *item : ui->categoriesList->selectedItems()) { + selectedItems.append(item->text(0)); + } + + ui->categoriesList->clear(); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CHECKED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNCHECKED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_MANAGED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_UNMANAGED, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_CONFLICT, ModListSortProxy::TYPE_SPECIAL); + addFilterItem(nullptr, tr(""), CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED, ModListSortProxy::TYPE_SPECIAL); + + addContentFilters(); + std::set categoriesUsed; + for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIdx); + for (int categoryID : modInfo->getCategories()) { + int currentID = categoryID; + std::set cycleTest; + // also add parents so they show up in the tree + while (currentID != 0) { + categoriesUsed.insert(currentID); + if (!cycleTest.insert(currentID).second) { + qWarning("cycle in categories: %s", qPrintable(SetJoin(cycleTest, ", "))); + break; + } + currentID = m_CategoryFactory.getParentID(m_CategoryFactory.getCategoryIndex(currentID)); + } + } + } + + addCategoryFilters(nullptr, categoriesUsed, 0); + + for (const QString &item : selectedItems) { + QList matches = ui->categoriesList->findItems(item, Qt::MatchFixedString | Qt::MatchRecursive); + if (matches.size() > 0) { + matches.at(0)->setSelected(true); + } + } + ui->modList->selectionModel()->select(currentSelection, QItemSelectionModel::Select); + QModelIndexList matchList; + if (currentIndexName.isValid()) { + matchList = ui->modList->model()->match(ui->modList->model()->index(0, 0), Qt::DisplayRole, currentIndexName); + } + + if (matchList.size() > 0) { + ui->modList->setCurrentIndex(matchList.at(0)); + } +} + + +void MainWindow::renameMod_clicked() +{ + try { + ui->modList->edit(ui->modList->currentIndex()); + } catch (const std::exception &e) { + reportError(tr("failed to rename mod: %1").arg(e.what())); + } +} + + +void MainWindow::restoreBackup_clicked() +{ + QRegExp backupRegEx("(.*)_backup[0-9]*$"); + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + if (backupRegEx.indexIn(modInfo->name()) != -1) { + QString regName = backupRegEx.cap(1); + QDir modDir(QDir::fromNativeSeparators(m_OrganizerCore.settings().getModDirectory())); + if (!modDir.exists(regName) || + (QMessageBox::question(this, 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_OrganizerCore.settings().getModDirectory()) + "/" + regName; + if (!modDir.rename(modInfo->absolutePath(), destinationPath)) { + reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath)); + } + m_OrganizerCore.refreshModList(); + } + } + } +} + +void MainWindow::modlistChanged(const QModelIndex&, int) +{ + m_OrganizerCore.currentProfile()->writeModlist(); +} + +void MainWindow::modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex&) +{ + if (current.isValid()) { + ModInfo::Ptr selectedMod = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); + m_OrganizerCore.modList()->setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten()); + } else { + m_OrganizerCore.modList()->setOverwriteMarkers(std::set(), std::set()); + } +/* if ((m_ModListSortProxy != nullptr) + && !m_ModListSortProxy->beingInvalidated()) { + m_ModListSortProxy->invalidate(); + }*/ + ui->modList->verticalScrollBar()->repaint(); +} + +void MainWindow::modlistSelectionsChanged(const QItemSelection &selected) +{ + m_OrganizerCore.pluginList()->highlightPlugins(selected, *m_OrganizerCore.directoryStructure(), *m_OrganizerCore.currentProfile()); + ui->espList->verticalScrollBar()->repaint(); +} + +void MainWindow::esplistSelectionsChanged(const QItemSelection &selected) +{ + m_OrganizerCore.modList()->highlightMods(selected, *m_OrganizerCore.directoryStructure()); + ui->modList->verticalScrollBar()->repaint(); +} + +void MainWindow::modListSortIndicatorChanged(int, Qt::SortOrder) +{ + ui->modList->verticalScrollBar()->repaint(); +} + +void MainWindow::removeMod_clicked() +{ + try { + QItemSelectionModel *selection = ui->modList->selectionModel(); + if (selection->hasSelection() && selection->selectedRows().count() > 1) { + QString mods; + QStringList modNames; + for (QModelIndex idx : selection->selectedRows()) { + QString name = idx.data().toString(); + if (!ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->isRegular()) { + continue; + } + mods += "
  • " + name + "
  • "; + modNames.append(name); + } + if (QMessageBox::question(this, 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 + for (QString name : modNames) { + m_OrganizerCore.modList()->removeRowForce(ModInfo::getIndex(name), QModelIndex()); + } + } } else { - // For single mod selections, just do a replace - replaceCategoriesFromMenu(menu, m_ContextRow); - m_OrganizerCore.modList()->notifyChange(m_ContextRow); + m_OrganizerCore.modList()->removeRow(m_ContextRow, QModelIndex()); } - - refreshFilters(); + } catch (const std::exception &e) { + reportError(tr("failed to remove mod: %1").arg(e.what())); + } } -void MainWindow::replaceCategories_MenuHandler() { - QMenu* menu = qobject_cast(sender()); - if (menu == nullptr) { - qCritical("not a menu?"); - return; - } - QList selected; - for (const QModelIndex& idx : ui->modList->selectionModel()->selectedRows()) { - selected.append(QPersistentModelIndex(idx)); +void MainWindow::modRemoved(const QString &fileName) +{ + if (!fileName.isEmpty() && !QFileInfo(fileName).isAbsolute()) { + int index = m_OrganizerCore.downloadManager()->indexByName(fileName); + if (index >= 0) { + m_OrganizerCore.downloadManager()->markUninstalled(index); } + } +} - if (selected.size() > 0) { - QStringList selectedMods; - int minRow = INT_MAX; - int maxRow = -1; - for (int i = 0; i < selected.size(); ++i) { - QModelIndex temp = mapToModel(m_OrganizerCore.modList(), selected.at(i)); - selectedMods.append(temp.data().toString()); - replaceCategoriesFromMenu(menu, mapToModel(m_OrganizerCore.modList(), selected.at(i)).row()); - if (temp.row() < minRow) - minRow = temp.row(); - if (temp.row() > maxRow) - maxRow = temp.row(); - } - - m_OrganizerCore.modList()->notifyChange(minRow, maxRow + 1); - // find mods by their name because indices are invalidated - QAbstractItemModel* model = ui->modList->model(); - for (const QString& mod : selectedMods) { - QModelIndexList matches = model->match(model->index(0, 0), Qt::DisplayRole, mod, 1, - Qt::MatchFixedString | Qt::MatchCaseSensitive | Qt::MatchRecursive); - if (matches.size() > 0) { - ui->modList->selectionModel()->select(matches.at(0), - QItemSelectionModel::Select | QItemSelectionModel::Rows); - } - } +void MainWindow::reinstallMod_clicked() +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + QString installationFile = modInfo->getInstallationFile(); + if (installationFile.length() != 0) { + QString fullInstallationFile; + QFileInfo fileInfo(installationFile); + if (fileInfo.isAbsolute()) { + if (fileInfo.exists()) { + fullInstallationFile = installationFile; + } else { + fullInstallationFile = m_OrganizerCore.downloadManager()->getOutputDirectory() + "/" + fileInfo.fileName(); + } } else { - // For single mod selections, just do a replace - replaceCategoriesFromMenu(menu, m_ContextRow); - m_OrganizerCore.modList()->notifyChange(m_ContextRow); + fullInstallationFile = m_OrganizerCore.downloadManager()->getOutputDirectory() + "/" + installationFile; } - - refreshFilters(); + if (QFile::exists(fullInstallationFile)) { + m_OrganizerCore.installMod(fullInstallationFile, modInfo->name()); + } else { + QMessageBox::information(this, tr("Failed"), tr("Installation file no longer exists")); + } + } else { + QMessageBox::information(this, tr("Failed"), + tr("Mods installed with old versions of MO can't be reinstalled in this way.")); + } } -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); - for (int j = 0; j < tlItem->childCount(); ++j) { - QTreeWidgetItem* item = tlItem->child(j); - if (item->checkState(0) == Qt::Checked) { - archiveFile->write(item->text(0).toUtf8().append("\r\n")); - } - } - } - if (archiveFile.commitIfDifferent(m_ArchiveListHash)) { - qDebug("%s saved", - qPrintable(QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName()))); - } + +void MainWindow::resumeDownload(int downloadIndex) +{ + if (NexusInterface::instance()->getAccessManager()->loggedIn()) { + m_OrganizerCore.downloadManager()->resumeDownload(downloadIndex); + } else { + QString username, password; + if (m_OrganizerCore.settings().getNexusLogin(username, password)) { + m_OrganizerCore.doAfterLogin([this, downloadIndex] () { + this->resumeDownload(downloadIndex); + }); + NexusInterface::instance()->getAccessManager()->login(username, password); } else { - qWarning("archive list not initialised"); + MessageDialog::showMessage(tr("You need to be logged in with Nexus to resume a download"), this); } + } } -void MainWindow::checkModsForUpdates() { - statusBar()->show(); - if (NexusInterface::instance()->getAccessManager()->loggedIn()) { - m_ModsToUpdate = ModInfo::checkAllForUpdate(this); - m_RefreshProgress->setRange(0, m_ModsToUpdate); + +void MainWindow::endorseMod(ModInfo::Ptr mod) +{ + if (NexusInterface::instance()->getAccessManager()->loggedIn()) { + mod->endorse(true); + } else { + QString username, password; + if (m_OrganizerCore.settings().getNexusLogin(username, password)) { + m_OrganizerCore.doAfterLogin(boost::bind(&MainWindow::endorseMod, this, mod)); + NexusInterface::instance()->getAccessManager()->login(username, password); } else { - QString username, password; - if (m_OrganizerCore.settings().getNexusLogin(username, password)) { - m_OrganizerCore.doAfterLogin([this]() { this->checkModsForUpdates(); }); - NexusInterface::instance()->getAccessManager()->login(username, password); - } else { // otherwise there will be no endorsement info - MessageDialog::showMessage(tr("Not logged in, endorsement information will be wrong"), this, true); - m_ModsToUpdate = ModInfo::checkAllForUpdate(this); - } + MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); } + } } -void MainWindow::changeVersioningScheme() { - if (QMessageBox::question(this, 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(m_ContextRow); - - bool success = false; - - static VersionInfo::VersionScheme schemes[] = {VersionInfo::SCHEME_REGULAR, VersionInfo::SCHEME_DECIMALMARK, - VersionInfo::SCHEME_NUMBERSANDLETTERS}; - - for (int i = 0; i < sizeof(schemes) / sizeof(VersionInfo::VersionScheme) && !success; ++i) { - VersionInfo verOld(info->getVersion().canonicalString(), schemes[i]); - VersionInfo verNew(info->getNewestVersion().canonicalString(), schemes[i]); - if (verOld < verNew) { - info->setVersion(verOld); - info->setNewestVersion(verNew); - success = true; - } - } - if (!success) { - QMessageBox::information(this, tr("Sorry"), - tr("I don't know a versioning scheme where %1 is newer than %2.") - .arg(info->getNewestVersion().canonicalString()) - .arg(info->getVersion().canonicalString()), - QMessageBox::Ok); - } - } + +void MainWindow::endorse_clicked() +{ + endorseMod(ModInfo::getByIndex(m_ContextRow)); } -void MainWindow::ignoreUpdate() { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - info->ignoreUpdate(true); +void MainWindow::dontendorse_clicked() +{ + ModInfo::getByIndex(m_ContextRow)->setNeverEndorse(); } -void MainWindow::unignoreUpdate() { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - info->ignoreUpdate(false); -} - -void MainWindow::addPrimaryCategoryCandidates(QMenu* primaryCategoryMenu, ModInfo::Ptr info) { - const std::set& categories = info->getCategories(); - for (int categoryID : categories) { - int catIdx = m_CategoryFactory.getCategoryIndex(categoryID); - QWidgetAction* action = new QWidgetAction(primaryCategoryMenu); - try { - QRadioButton* categoryBox = - new QRadioButton(m_CategoryFactory.getCategoryName(catIdx).replace('&', "&&"), primaryCategoryMenu); - connect(categoryBox, &QRadioButton::toggled, [info, categoryID](bool enable) { - if (enable) { - info->setPrimaryCategory(categoryID); - } - }); - categoryBox->setChecked(categoryID == info->getPrimaryCategory()); - action->setDefaultWidget(categoryBox); - } catch (const std::exception& e) { - qCritical("failed to create category checkbox: %s", e.what()); - } - action->setData(categoryID); - primaryCategoryMenu->addAction(action); +void MainWindow::unendorse_clicked() +{ + QString username, password; + if (NexusInterface::instance()->getAccessManager()->loggedIn()) { + ModInfo::getByIndex(m_ContextRow)->endorse(false); + } else { + if (m_OrganizerCore.settings().getNexusLogin(username, password)) { + m_OrganizerCore.doAfterLogin([this] () { this->unendorse_clicked(); }); + NexusInterface::instance()->getAccessManager()->login(username, password); + } else { + MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); } + } } -void MainWindow::addPrimaryCategoryCandidates() { - QMenu* menu = qobject_cast(sender()); - if (menu == nullptr) { - qCritical("not a menu?"); - return; - } - menu->clear(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); +void MainWindow::loginFailed(const QString &error) +{ + qDebug("login failed: %s", qPrintable(error)); + statusBar()->hide(); +} - addPrimaryCategoryCandidates(menu, modInfo); +void MainWindow::windowTutorialFinished(const QString &windowName) +{ + m_OrganizerCore.settings().directInterface().setValue(QString("CompletedWindowTutorials/") + windowName, true); } -void MainWindow::enableVisibleMods() { - if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really enable all visible mods?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_ModListSortProxy->enableAllVisible(); - } +void MainWindow::overwriteClosed(int) +{ + OverwriteInfoDialog *dialog = this->findChild("__overwriteDialog"); + if (dialog != nullptr) { + m_OrganizerCore.modList()->modInfoChanged(dialog->modInfo()); + dialog->deleteLater(); + } + m_OrganizerCore.refreshDirectoryStructure(); } -void MainWindow::disableVisibleMods() { - if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really disable all visible mods?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_ModListSortProxy->disableAllVisible(); + +void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab) +{ + if (!m_OrganizerCore.modList()->modInfoAboutToChange(modInfo)) { + qDebug("A different mod information dialog is open. If this is incorrect, please restart MO"); + return; + } + std::vector flags = modInfo->getFlags(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { + QDialog *dialog = this->findChild("__overwriteDialog"); + try { + if (dialog == nullptr) { + dialog = new OverwriteInfoDialog(modInfo, this); + dialog->setObjectName("__overwriteDialog"); + } else { + qobject_cast(dialog)->setModInfo(modInfo); + } + dialog->show(); + dialog->raise(); + dialog->activateWindow(); + connect(dialog, SIGNAL(finished(int)), this, SLOT(overwriteClosed(int))); + } catch (const std::exception &e) { + reportError(tr("Failed to display overwrite dialog: %1").arg(e.what())); + } + } else { + modInfo->saveMeta(); + ModInfoDialog dialog(modInfo, m_OrganizerCore.directoryStructure(), modInfo->hasFlag(ModInfo::FLAG_FOREIGN), this); + connect(&dialog, SIGNAL(nexusLinkActivated(QString)), this, SLOT(nexusLinkActivated(QString))); + connect(&dialog, SIGNAL(downloadRequest(QString)), &m_OrganizerCore, SLOT(downloadRequestedNXM(QString))); + connect(&dialog, SIGNAL(modOpen(QString, int)), this, SLOT(displayModInformation(QString, int)), Qt::QueuedConnection); + connect(&dialog, SIGNAL(modOpenNext()), this, SLOT(modOpenNext()), Qt::QueuedConnection); + connect(&dialog, SIGNAL(modOpenPrev()), this, SLOT(modOpenPrev()), Qt::QueuedConnection); + connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int))); + connect(&dialog, SIGNAL(endorseMod(ModInfo::Ptr)), this, SLOT(endorseMod(ModInfo::Ptr))); + + dialog.openTab(tab); + dialog.restoreTabState(m_OrganizerCore.settings().directInterface().value("mod_info_tabs").toByteArray()); + dialog.exec(); + m_OrganizerCore.settings().directInterface().setValue("mod_info_tabs", dialog.saveTabState()); + + modInfo->saveMeta(); + emit modInfoDisplayed(); + m_OrganizerCore.modList()->modInfoChanged(modInfo); + } + + if (m_OrganizerCore.currentProfile()->modEnabled(index) + && !modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { + FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(modInfo->name())); + origin.enable(false); + + if (m_OrganizerCore.directoryStructure()->originExists(ToWString(modInfo->name()))) { + FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(modInfo->name())); + origin.enable(false); + + m_OrganizerCore.directoryRefresher()->addModToStructure(m_OrganizerCore.directoryStructure() + , modInfo->name() + , m_OrganizerCore.currentProfile()->getModPriority(index) + , modInfo->absolutePath() + , modInfo->stealFiles() + , modInfo->archives()); + DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure()); + m_OrganizerCore.refreshLists(); } + } } -void MainWindow::openInstanceFolder() { - ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.settings().getBaseDirectory()).c_str(), nullptr, - nullptr, SW_SHOWNORMAL); +bool MainWindow::closeWindow() +{ + return close(); } -void MainWindow::openProfileFolder() { - ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.currentProfile()->absolutePath()).c_str(), nullptr, - nullptr, SW_SHOWNORMAL); +void MainWindow::setWindowEnabled(bool enabled) +{ + setEnabled(enabled); } -void MainWindow::openDownloadsFolder() { - ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.settings().getDownloadDirectory()).c_str(), nullptr, - nullptr, SW_SHOWNORMAL); -} -void MainWindow::openGameFolder() { - ::ShellExecuteW(nullptr, L"explore", - ToWString(m_OrganizerCore.managedGame()->gameDirectory().absolutePath()).c_str(), nullptr, nullptr, - SW_SHOWNORMAL); -} - -void MainWindow::openMyGamesFolder() { - ::ShellExecuteW(nullptr, L"explore", - ToWString(m_OrganizerCore.managedGame()->documentsDirectory().absolutePath()).c_str(), nullptr, - nullptr, SW_SHOWNORMAL); -} - -void MainWindow::exportModListCSV() { - // SelectionDialog selection(tr("Choose what to export")); - - // selection.addChoice(tr("Everything"), tr("All installed mods are included in the list"), 0); - // selection.addChoice(tr("Active Mods"), tr("Only active (checked) mods from your current profile are included"), - // 1); selection.addChoice(tr("Visible"), tr("All mods visible in the mod list are included"), 2); - - QDialog selection(this); - QGridLayout* grid = new QGridLayout; - selection.setWindowTitle(tr("Export to csv")); - - 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")); - - QVBoxLayout* vbox = new QVBoxLayout; - vbox->addWidget(all); - vbox->addWidget(active); - vbox->addWidget(visible); - vbox->addStretch(1); - groupBoxRows->setLayout(vbox); - - grid->addWidget(groupBoxRows); - - QButtonGroup* buttonGroupRows = new QButtonGroup(); - buttonGroupRows->addButton(all, 0); - buttonGroupRows->addButton(active, 1); - buttonGroupRows->addButton(visible, 2); - buttonGroupRows->button(0)->setChecked(true); - - QGroupBox* groupBoxColumns = new QGroupBox(tr("Choose what Columns to export:")); - groupBoxColumns->setFlat(true); - - QCheckBox* mod_Priority = new QCheckBox(tr("Mod_Priority")); - mod_Priority->setChecked(true); - QCheckBox* mod_Name = new QCheckBox(tr("Mod_Name")); - mod_Name->setChecked(true); - QCheckBox* mod_Status = new QCheckBox(tr("Mod_Status")); - 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; - vbox1->addWidget(mod_Priority); - vbox1->addWidget(mod_Name); - vbox1->addWidget(mod_Status); - vbox1->addWidget(primary_Category); - vbox1->addWidget(nexus_ID); - vbox1->addWidget(mod_Nexus_URL); - vbox1->addWidget(mod_Version); - vbox1->addWidget(install_Date); - vbox1->addWidget(download_File_Name); - groupBoxColumns->setLayout(vbox1); - - grid->addWidget(groupBoxColumns); - - QPushButton* ok = new QPushButton("Ok"); - QPushButton* cancel = new QPushButton("Cancel"); - QDialogButtonBox* buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); - - connect(buttons, SIGNAL(accepted()), &selection, SLOT(accept())); - connect(buttons, SIGNAL(rejected()), &selection, SLOT(reject())); - - grid->addWidget(buttons); - - selection.setLayout(grid); - - if (selection.exec() == QDialog::Accepted) { - - unsigned int numMods = ModInfo::getNumMods(); - 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; - if (mod_Priority->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Priority"), CSVBuilder::TYPE_STRING)); - if (mod_Name->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Name"), CSVBuilder::TYPE_STRING)); - if (mod_Status->isChecked()) - fields.push_back(std::make_pair(QString("#Mod_Status"), CSVBuilder::TYPE_STRING)); - if (primary_Category->isChecked()) - 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)); - if (mod_Nexus_URL->isChecked()) - 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)); - if (install_Date->isChecked()) - 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)); - - builder.setFields(fields); - - builder.writeHeader(); - - for (unsigned int i = 0; i < numMods; ++i) { - ModInfo::Ptr info = ModInfo::getByIndex(i); - bool enabled = m_OrganizerCore.currentProfile()->modEnabled(i); - if ((selectedRowID == 1) && !enabled) { - continue; - } else if ((selectedRowID == 2) && !m_ModListSortProxy->filterMatchesMod(info, enabled)) { - 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 (mod_Priority->isChecked()) - builder.setRowField( - "#Mod_Priority", - QString("%1").arg(m_OrganizerCore.currentProfile()->getModPriority(i), 4, 10, QChar('0'))); - if (mod_Name->isChecked()) - builder.setRowField("#Mod_Name", info->name()); - if (mod_Status->isChecked()) - builder.setRowField("#Mod_Status", (enabled) ? "Enabled" : "Disabled"); - if (primary_Category->isChecked()) - builder.setRowField("#Primary_Category", - (m_CategoryFactory.categoryExists(info->getPrimaryCategory())) - ? m_CategoryFactory.getCategoryName(info->getPrimaryCategory()) - : ""); - if (nexus_ID->isChecked()) - builder.setRowField("#Nexus_ID", info->getNexusID()); - if (mod_Nexus_URL->isChecked()) - builder.setRowField("#Mod_Nexus_URL", info->getURL()); - if (mod_Version->isChecked()) - builder.setRowField("#Mod_Version", info->getVersion().canonicalString()); - if (install_Date->isChecked()) - builder.setRowField("#Install_Date", info->creationTime().toString("yyyy/MM/dd HH:mm:ss")); - if (download_File_Name->isChecked()) - builder.setRowField("#Download_File_Name", info->getInstallationFile()); - - builder.writeRow(); - } - } +void MainWindow::modOpenNext() +{ + QModelIndex index = m_ModListSortProxy->mapFromSource(m_OrganizerCore.modList()->index(m_ContextRow, 0)); + index = m_ModListSortProxy->index((index.row() + 1) % m_ModListSortProxy->rowCount(), 0); - SaveTextAsDialog saveDialog(this); - saveDialog.setText(buffer.data()); - saveDialog.exec(); - } catch (const std::exception& e) { - reportError(tr("export failed: %1").arg(e.what())); - } - } + m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); + ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); + std::vector flags = mod->getFlags(); + if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) || + (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end())) { + // skip overwrite and backups + modOpenNext(); + } else { + displayModInformation(m_ContextRow); + } } -static void addMenuAsPushButton(QMenu* menu, QMenu* subMenu) { - QPushButton* pushBtn = new QPushButton(subMenu->title()); - pushBtn->setMenu(subMenu); - QWidgetAction* action = new QWidgetAction(menu); - action->setDefaultWidget(pushBtn); - menu->addAction(action); -} +void MainWindow::modOpenPrev() +{ + QModelIndex index = m_ModListSortProxy->mapFromSource(m_OrganizerCore.modList()->index(m_ContextRow, 0)); + int row = index.row() - 1; + if (row == -1) { + row = m_ModListSortProxy->rowCount() - 1; + } -QMenu* MainWindow::modListContextMenu() { - QMenu* menu = new QMenu(this); - menu->addAction(tr("Install Mod..."), this, SLOT(installMod_clicked())); + m_ContextRow = m_ModListSortProxy->mapToSource(m_ModListSortProxy->index(row, 0)).row(); + ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); + std::vector flags = mod->getFlags(); + if ((std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) || + (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end())) { + // skip overwrite and backups + modOpenPrev(); + } else { + displayModInformation(m_ContextRow); + } +} - menu->addAction(tr("Create empty mod"), this, SLOT(createEmptyMod_clicked())); +void MainWindow::displayModInformation(const QString &modName, int tab) +{ + unsigned int index = ModInfo::getIndex(modName); + if (index == UINT_MAX) { + qCritical("failed to resolve mod name %s", modName.toUtf8().constData()); + return; + } - menu->addSeparator(); + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + displayModInformation(modInfo, index, tab); +} - menu->addAction(tr("Enable all visible"), this, SLOT(enableVisibleMods())); - menu->addAction(tr("Disable all visible"), this, SLOT(disableVisibleMods())); - menu->addAction(tr("Check all for update"), this, SLOT(checkModsForUpdates())); +void MainWindow::displayModInformation(int row, int tab) +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(row); + displayModInformation(modInfo, row, tab); +} - menu->addAction(tr("Refresh"), &m_OrganizerCore, SLOT(profileRefresh())); - menu->addAction(tr("Export to csv..."), this, SLOT(exportModListCSV())); +void MainWindow::ignoreMissingData_clicked() +{ + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + QDir(info->absolutePath()).mkdir("textures"); + info->testValid(); + connect(this, SIGNAL(modListDataChanged(QModelIndex,QModelIndex)), m_OrganizerCore.modList(), SIGNAL(dataChanged(QModelIndex,QModelIndex))); - menu->addSeparator(); + emit modListDataChanged(m_OrganizerCore.modList()->index(m_ContextRow, 0), m_OrganizerCore.modList()->index(m_ContextRow, m_OrganizerCore.modList()->columnCount() - 1)); +} - QMenu* openSubMenu = new QMenu(this); - openSubMenu->addAction(tr("Open Instance folder"), this, SLOT(openInstanceFolder())); +void MainWindow::visitOnNexus_clicked() +{ + int modID = m_OrganizerCore.modList()->data(m_OrganizerCore.modList()->index(m_ContextRow, 0), Qt::UserRole).toInt(); + if (modID > 0) { + nexusLinkActivated(NexusInterface::instance()->getModURL(modID)); + } else { + MessageDialog::showMessage(tr("Nexus ID for this Mod is unknown"), this); + } +} - openSubMenu->addAction(tr("Open Profile folder"), this, SLOT(openProfileFolder())); +void MainWindow::visitWebPage_clicked() +{ + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + if (info->getURL() != "") { + linkClicked(info->getURL()); + } else { + MessageDialog::showMessage(tr("Web page for this mod is unknown"), this); + } +} - openSubMenu->addAction(tr("Open Downloads folder"), this, SLOT(openDownloadsFolder())); +void MainWindow::openExplorer_clicked() +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - openSubMenu->addAction(tr("Open Game folder"), this, SLOT(openGameFolder())); + ::ShellExecuteW(nullptr, L"explore", ToWString(modInfo->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); +} - openSubMenu->addAction(tr("Open MyGames folder"), this, SLOT(openMyGamesFolder())); +void MainWindow::information_clicked() +{ + try { + displayModInformation(m_ContextRow); + } catch (const std::exception &e) { + reportError(e.what()); + } +} + +void MainWindow::createEmptyMod_clicked() +{ + GuessedValue name; + name.setFilter(&fixDirectoryName); + + while (name->isEmpty()) { + bool ok; + name.update(QInputDialog::getText(this, tr("Create Mod..."), + tr("This will create an empty mod.\n" + "Please enter a name:"), QLineEdit::Normal, "", &ok), + GUESS_USER); + if (!ok) { + return; + } + } - openSubMenu->setTitle(tr("Open Folder...")); + if (m_OrganizerCore.getMod(name) != nullptr) { + reportError(tr("A mod with this name already exists")); + return; + } - menu->addMenu(openSubMenu); + IModInterface *newMod = m_OrganizerCore.createMod(name); + if (newMod == nullptr) { + return; + } - return menu; + m_OrganizerCore.refreshModList(); } -void MainWindow::on_modList_customContextMenuRequested(const QPoint& pos) { - try { - QTreeView* modList = findChild("modList"); - - m_ContextIdx = mapToModel(m_OrganizerCore.modList(), modList->indexAt(pos)); - m_ContextRow = m_ContextIdx.row(); +void MainWindow::createModFromOverwrite() +{ + GuessedValue name; + name.setFilter(&fixDirectoryName); - QMenu* menu = nullptr; - QMenu* allMods = modListContextMenu(); - if (m_ContextRow == -1) { - // no selection - menu = allMods; - } else { - menu = new QMenu(this); - allMods->setTitle(tr("All Mods")); - menu->addMenu(allMods); - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - std::vector flags = info->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { - if (QDir(info->absolutePath()).count() > 2) { - menu->addAction(tr("Sync to Mods..."), &m_OrganizerCore, SLOT(syncOverwrite())); - menu->addAction(tr("Create Mod..."), this, SLOT(createModFromOverwrite())); - menu->addAction(tr("Clear Overwrite..."), this, SLOT(clearOverwrite())); - } - menu->addAction(tr("Open in explorer"), this, SLOT(openExplorer_clicked())); - } else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) { - menu->addAction(tr("Restore Backup"), this, SLOT(restoreBackup_clicked())); - menu->addAction(tr("Remove Backup..."), this, SLOT(removeMod_clicked())); - } else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) { - // nop, nothing to do with this mod - } else { - QMenu* addRemoveCategoriesMenu = new QMenu(tr("Add/Remove Categories")); - populateMenuCategories(addRemoveCategoriesMenu, 0); - connect(addRemoveCategoriesMenu, SIGNAL(aboutToHide()), this, SLOT(addRemoveCategories_MenuHandler())); - addMenuAsPushButton(menu, addRemoveCategoriesMenu); - - QMenu* replaceCategoriesMenu = new QMenu(tr("Replace Categories")); - populateMenuCategories(replaceCategoriesMenu, 0); - connect(replaceCategoriesMenu, SIGNAL(aboutToHide()), this, SLOT(replaceCategories_MenuHandler())); - addMenuAsPushButton(menu, replaceCategoriesMenu); - - QMenu* primaryCategoryMenu = new QMenu(tr("Primary Category")); - connect(primaryCategoryMenu, SIGNAL(aboutToShow()), this, SLOT(addPrimaryCategoryCandidates())); - addMenuAsPushButton(menu, primaryCategoryMenu); - - menu->addSeparator(); - if (info->downgradeAvailable()) { - menu->addAction(tr("Change versioning scheme"), this, SLOT(changeVersioningScheme())); - } - if (info->updateAvailable() || info->downgradeAvailable()) { - if (info->updateIgnored()) { - menu->addAction(tr("Un-ignore update"), this, SLOT(unignoreUpdate())); - } else { - menu->addAction(tr("Ignore update"), this, SLOT(ignoreUpdate())); - } - } - menu->addSeparator(); - - menu->addAction(tr("Rename Mod..."), this, SLOT(renameMod_clicked())); - menu->addAction(tr("Remove Mod..."), this, SLOT(removeMod_clicked())); - menu->addAction(tr("Reinstall Mod"), this, SLOT(reinstallMod_clicked())); - - menu->addSeparator(); - - if (info->getNexusID() > 0) { - switch (info->endorsedState()) { - case ModInfo::ENDORSED_TRUE: { - menu->addAction(tr("Un-Endorse"), this, SLOT(unendorse_clicked())); - } break; - case ModInfo::ENDORSED_FALSE: { - menu->addAction(tr("Endorse"), this, SLOT(endorse_clicked())); - menu->addAction(tr("Won't endorse"), this, SLOT(dontendorse_clicked())); - } break; - case ModInfo::ENDORSED_NEVER: { - menu->addAction(tr("Endorse"), this, SLOT(endorse_clicked())); - } break; - default: { - QAction* action = new QAction(tr("Endorsement state unknown"), menu); - action->setEnabled(false); - menu->addAction(action); - } break; - } - } + while (name->isEmpty()) { + bool ok; + name.update(QInputDialog::getText(this, 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; + } + } - menu->addSeparator(); + if (m_OrganizerCore.getMod(name) != nullptr) { + reportError(tr("A mod with this name already exists")); + return; + } - std::vector flags = info->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { - menu->addAction(tr("Ignore missing data"), this, SLOT(ignoreMissingData_clicked())); - } + IModInterface *newMod = m_OrganizerCore.createMod(name); + if (newMod == nullptr) { + return; + } - if (info->getNexusID() > 0) { - menu->addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked())); - } else if ((info->getURL() != "")) { - menu->addAction(tr("Visit web page"), this, SLOT(visitWebPage_clicked())); - } + unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { + std::vector flags = mod->getFlags(); + return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); - menu->addAction(tr("Open in explorer"), this, SLOT(openExplorer_clicked())); - } + ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(overwriteIndex); + shellMove(QStringList(QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"), + QStringList(QDir::toNativeSeparators(newMod->absolutePath())), this); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end()) { - QAction* infoAction = menu->addAction(tr("Information..."), this, SLOT(information_clicked())); - menu->setDefaultAction(infoAction); - } - } + m_OrganizerCore.refreshModList(); +} - menu->exec(modList->mapToGlobal(pos)); - } catch (const std::exception& e) { - reportError(tr("Exception: ").arg(e.what())); - } catch (...) { - reportError(tr("Unknown exception")); +void MainWindow::clearOverwrite() +{ + unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { + std::vector flags = mod->getFlags(); + return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) + != flags.end(); + }); + + ModInfo::Ptr modInfo = ModInfo::getByIndex(overwriteIndex); + if (modInfo) + { + QDir overwriteDir(modInfo->absolutePath()); + if (QMessageBox::question(this, 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)) + delList.push_back(overwriteDir.absoluteFilePath(f)); + shellDelete(delList, true); + updateProblemsButton(); } + } } -void MainWindow::on_categoriesList_itemSelectionChanged() { - QModelIndexList indices = ui->categoriesList->selectionModel()->selectedRows(); - std::vector categories; - std::vector content; - for (const QModelIndex& index : indices) { - int filterType = index.data(Qt::UserRole + 1).toInt(); - if ((filterType == ModListSortProxy::TYPE_CATEGORY) || (filterType == ModListSortProxy::TYPE_SPECIAL)) { - int categoryId = index.data(Qt::UserRole).toInt(); - if (categoryId != CategoryFactory::CATEGORY_NONE) { - categories.push_back(categoryId); - } - } else if (filterType == ModListSortProxy::TYPE_CONTENT) { - int contentId = index.data(Qt::UserRole).toInt(); - content.push_back(contentId); - } - } +void MainWindow::cancelModListEditor() +{ + ui->modList->setEnabled(false); + ui->modList->setEnabled(true); +} - m_ModListSortProxy->setCategoryFilter(categories); - m_ModListSortProxy->setContentFilter(content); - ui->clickBlankButton->setEnabled(categories.size() > 0 || content.size() > 0); - // ui->clearFiltersButton->setStyleSheet("border:5px solid #ff0000;"); - ui->clearFiltersButton->setVisible(categories.size() > 0 || content.size() > 0); +void MainWindow::on_modList_doubleClicked(const QModelIndex &index) +{ + if (!index.isValid()) { + return; + } - if (indices.count() == 0) { - ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); - } else if (indices.count() > 1) { - ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); + if (m_OrganizerCore.modList()->timeElapsedSinceLastChecked() <= QApplication::doubleClickInterval()) { + // don't interpret double click if we only just checked a mod + return; + } + + QModelIndex sourceIdx = mapToModel(m_OrganizerCore.modList(), index); + if (!sourceIdx.isValid()) { + return; + } + + try { + m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); + displayModInformation(sourceIdx.row()); + // workaround to cancel the editor that might have opened because of + // selection-click + ui->modList->closePersistentEditor(index); + } catch (const std::exception &e) { + reportError(e.what()); + } +} + +bool MainWindow::populateMenuCategories(QMenu *menu, int targetID) +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); + const std::set &categories = modInfo->getCategories(); + + bool childEnabled = false; + + for (unsigned int i = 1; i < m_CategoryFactory.numCategories(); ++i) { + if (m_CategoryFactory.getParentID(i) == targetID) { + QMenu *targetMenu = menu; + if (m_CategoryFactory.hasChildren(i)) { + targetMenu = menu->addMenu(m_CategoryFactory.getCategoryName(i).replace('&', "&&")); + } + + int id = m_CategoryFactory.getCategoryID(i); + QScopedPointer checkBox(new QCheckBox(targetMenu)); + bool enabled = categories.find(id) != categories.end(); + checkBox->setText(m_CategoryFactory.getCategoryName(i).replace('&', "&&")); + if (enabled) { + childEnabled = true; + } + checkBox->setChecked(enabled ? Qt::Checked : Qt::Unchecked); + + QScopedPointer checkableAction(new QWidgetAction(targetMenu)); + checkableAction->setDefaultWidget(checkBox.take()); + checkableAction->setData(id); + targetMenu->addAction(checkableAction.take()); + + if (m_CategoryFactory.hasChildren(i)) { + if (populateMenuCategories(targetMenu, m_CategoryFactory.getCategoryID(i)) || enabled) { + targetMenu->setIcon(QIcon(":/MO/gui/resources/check.png")); + } + } + } + } + return childEnabled; +} + +void MainWindow::replaceCategoriesFromMenu(QMenu *menu, int modRow) +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(modRow); + for (QAction* action : menu->actions()) { + if (action->menu() != nullptr) { + replaceCategoriesFromMenu(action->menu(), modRow); } else { - ui->currentCategoryLabel->setText(QString("(%1)").arg(indices.first().data().toString())); + QWidgetAction *widgetAction = qobject_cast(action); + if (widgetAction != nullptr) { + QCheckBox *checkbox = qobject_cast(widgetAction->defaultWidget()); + modInfo->setCategory(widgetAction->data().toInt(), checkbox->isChecked()); + } + } + } +} + +void MainWindow::addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int referenceRow) +{ + if (referenceRow != -1 && referenceRow != modRow) { + ModInfo::Ptr editedModInfo = ModInfo::getByIndex(referenceRow); + for (QAction* action : menu->actions()) { + if (action->menu() != nullptr) { + addRemoveCategoriesFromMenu(action->menu(), modRow, referenceRow); + } else { + QWidgetAction *widgetAction = qobject_cast(action); + if (widgetAction != nullptr) { + QCheckBox *checkbox = qobject_cast(widgetAction->defaultWidget()); + int categoryId = widgetAction->data().toInt(); + bool checkedBefore = editedModInfo->categorySet(categoryId); + bool checkedAfter = checkbox->isChecked(); + + if (checkedBefore != checkedAfter) { // only update if the category was changed on the edited mod + ModInfo::Ptr currentModInfo = ModInfo::getByIndex(modRow); + currentModInfo->setCategory(categoryId, checkedAfter); + } + } + } } - ui->modList->reset(); + } else { + replaceCategoriesFromMenu(menu, modRow); + } } -void MainWindow::deleteSavegame_clicked() { - SaveGameInfo const* info = m_OrganizerCore.managedGame()->feature(); +void MainWindow::addRemoveCategories_MenuHandler() { + QMenu *menu = qobject_cast(sender()); + if (menu == nullptr) { + qCritical("not a menu?"); + return; + } - QString savesMsgLabel; - QStringList deleteFiles; + QList selected; + for (const QModelIndex &idx : ui->modList->selectionModel()->selectedRows()) { + selected.append(QPersistentModelIndex(idx)); + } - int count = 0; + if (selected.size() > 0) { + int minRow = INT_MAX; + int maxRow = -1; - for (const QModelIndex& idx : ui->savegameList->selectionModel()->selectedIndexes()) { - QString name = idx.data(Qt::UserRole).toString(); + for (const QPersistentModelIndex &idx : selected) { + qDebug("change categories on: %s", qPrintable(idx.data().toString())); + QModelIndex modIdx = mapToModel(m_OrganizerCore.modList(), idx); + if (modIdx.row() != m_ContextIdx.row()) { + addRemoveCategoriesFromMenu(menu, modIdx.row(), m_ContextIdx.row()); + } + if (idx.row() < minRow) minRow = idx.row(); + if (idx.row() > maxRow) maxRow = idx.row(); + } + replaceCategoriesFromMenu(menu, m_ContextIdx.row()); - if (count < 10) { - savesMsgLabel += "
  • " + QFileInfo(name).completeBaseName() + "
  • "; - } - ++count; + m_OrganizerCore.modList()->notifyChange(minRow, maxRow + 1); - if (info == nullptr) { - deleteFiles.push_back(name); - } else { - ISaveGame const* save = info->getSaveGameInfo(name); - deleteFiles += save->allFiles(); - } + for (const QPersistentModelIndex &idx : selected) { + ui->modList->selectionModel()->select(idx, QItemSelectionModel::Select | QItemSelectionModel::Rows); } + } else { + //For single mod selections, just do a replace + replaceCategoriesFromMenu(menu, m_ContextRow); + m_OrganizerCore.modList()->notifyChange(m_ContextRow); + } - if (count > 10) { - savesMsgLabel += "
  • ... " + tr("%1 more").arg(count - 10) + "
  • "; + refreshFilters(); +} + +void MainWindow::replaceCategories_MenuHandler() { + QMenu *menu = qobject_cast(sender()); + if (menu == nullptr) { + qCritical("not a menu?"); + return; + } + + QList selected; + for (const QModelIndex &idx : ui->modList->selectionModel()->selectedRows()) { + selected.append(QPersistentModelIndex(idx)); + } + + if (selected.size() > 0) { + QStringList selectedMods; + int minRow = INT_MAX; + int maxRow = -1; + for (int i = 0; i < selected.size(); ++i) { + QModelIndex temp = mapToModel(m_OrganizerCore.modList(), selected.at(i)); + selectedMods.append(temp.data().toString()); + replaceCategoriesFromMenu(menu, mapToModel(m_OrganizerCore.modList(), selected.at(i)).row()); + if (temp.row() < minRow) minRow = temp.row(); + if (temp.row() > maxRow) maxRow = temp.row(); + } + + m_OrganizerCore.modList()->notifyChange(minRow, maxRow + 1); + + // find mods by their name because indices are invalidated + QAbstractItemModel *model = ui->modList->model(); + for (const QString &mod : selectedMods) { + QModelIndexList matches = model->match(model->index(0, 0), Qt::DisplayRole, mod, 1, + Qt::MatchFixedString | Qt::MatchCaseSensitive | Qt::MatchRecursive); + if (matches.size() > 0) { + ui->modList->selectionModel()->select(matches.at(0), QItemSelectionModel::Select | QItemSelectionModel::Rows); + } + } + } else { + //For single mod selections, just do a replace + replaceCategoriesFromMenu(menu, m_ContextRow); + m_OrganizerCore.modList()->notifyChange(m_ContextRow); + } + + refreshFilters(); +} + +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); + for (int j = 0; j < tlItem->childCount(); ++j) { + QTreeWidgetItem * item = tlItem->child(j); + if (item->checkState(0) == Qt::Checked) { + archiveFile->write(item->text(0).toUtf8().append("\r\n")); + } + } + } + if (archiveFile.commitIfDifferent(m_ArchiveListHash)) { + qDebug("%s saved", qPrintable(QDir::toNativeSeparators(m_OrganizerCore.currentProfile()->getArchivesFileName()))); + } + } else { + qWarning("archive list not initialised"); + } +} + +void MainWindow::checkModsForUpdates() +{ + statusBar()->show(); + if (NexusInterface::instance()->getAccessManager()->loggedIn()) { + m_ModsToUpdate = ModInfo::checkAllForUpdate(this); + m_RefreshProgress->setRange(0, m_ModsToUpdate); + } else { + QString username, password; + if (m_OrganizerCore.settings().getNexusLogin(username, password)) { + m_OrganizerCore.doAfterLogin([this] () { this->checkModsForUpdates(); }); + NexusInterface::instance()->getAccessManager()->login(username, password); + } else { // otherwise there will be no endorsement info + MessageDialog::showMessage(tr("Not logged in, endorsement information will be wrong"), + this, true); + m_ModsToUpdate = ModInfo::checkAllForUpdate(this); } + } +} - if (QMessageBox::question(this, 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 MainWindow::changeVersioningScheme() { + if (QMessageBox::question(this, 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(m_ContextRow); + + bool success = false; + + static VersionInfo::VersionScheme schemes[] = { VersionInfo::SCHEME_REGULAR, VersionInfo::SCHEME_DECIMALMARK, VersionInfo::SCHEME_NUMBERSANDLETTERS }; + + for (int i = 0; i < sizeof(schemes) / sizeof(VersionInfo::VersionScheme) && !success; ++i) { + VersionInfo verOld(info->getVersion().canonicalString(), schemes[i]); + VersionInfo verNew(info->getNewestVersion().canonicalString(), schemes[i]); + if (verOld < verNew) { + info->setVersion(verOld); + info->setNewestVersion(verNew); + success = true; + } + } + if (!success) { + QMessageBox::information(this, tr("Sorry"), + tr("I don't know a versioning scheme where %1 is newer than %2.").arg(info->getNewestVersion().canonicalString()).arg(info->getVersion().canonicalString()), + QMessageBox::Ok); } + } } -void MainWindow::fixMods_clicked(SaveGameInfo::MissingAssets const& missingAssets) { - ActivateModsDialog dialog(missingAssets, this); - if (dialog.exec() == QDialog::Accepted) { - // activate the required mods, then enable all esps - std::set modsToActivate = dialog.getModsToActivate(); - for (std::set::iterator iter = modsToActivate.begin(); iter != modsToActivate.end(); ++iter) { - if ((*iter != "") && (*iter != "")) { - unsigned int modIndex = ModInfo::getIndex(*iter); - m_OrganizerCore.currentProfile()->setModEnabled(modIndex, true); - } - } +void MainWindow::ignoreUpdate() { + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + info->ignoreUpdate(true); +} - m_OrganizerCore.currentProfile()->writeModlist(); - m_OrganizerCore.refreshLists(); +void MainWindow::unignoreUpdate() +{ + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + info->ignoreUpdate(false); +} - std::set espsToActivate = dialog.getESPsToActivate(); - for (std::set::iterator iter = espsToActivate.begin(); iter != espsToActivate.end(); ++iter) { - m_OrganizerCore.pluginList()->enableESP(*iter); +void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, + ModInfo::Ptr info) { + const std::set &categories = info->getCategories(); + for (int categoryID : categories) { + int catIdx = m_CategoryFactory.getCategoryIndex(categoryID); + QWidgetAction *action = new QWidgetAction(primaryCategoryMenu); + try { + QRadioButton *categoryBox = new QRadioButton( + m_CategoryFactory.getCategoryName(catIdx).replace('&', "&&"), + primaryCategoryMenu); + connect(categoryBox, &QRadioButton::toggled, [info, categoryID](bool enable) { + if (enable) { + info->setPrimaryCategory(categoryID); } - m_OrganizerCore.saveCurrentLists(); + }); + categoryBox->setChecked(categoryID == info->getPrimaryCategory()); + action->setDefaultWidget(categoryBox); + } catch (const std::exception &e) { + qCritical("failed to create category checkbox: %s", e.what()); } + + action->setData(categoryID); + primaryCategoryMenu->addAction(action); + } } -void MainWindow::on_savegameList_customContextMenuRequested(const QPoint& pos) { - QItemSelectionModel* selection = ui->savegameList->selectionModel(); +void MainWindow::addPrimaryCategoryCandidates() +{ + QMenu *menu = qobject_cast(sender()); + if (menu == nullptr) { + qCritical("not a menu?"); + return; + } + menu->clear(); + ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - if (!selection->hasSelection()) { - return; - } + addPrimaryCategoryCandidates(menu, modInfo); +} - QMenu menu; - QAction* action = menu.addAction(tr("Enable Mods...")); - action->setEnabled(false); +void MainWindow::enableVisibleMods() +{ + if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really enable all visible mods?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_ModListSortProxy->enableAllVisible(); + } +} - if (selection->selectedIndexes().count() == 1) { - SaveGameInfo const* info = this->m_OrganizerCore.managedGame()->feature(); - if (info != nullptr) { - QString save = ui->savegameList->currentItem()->data(Qt::UserRole).toString(); - SaveGameInfo::MissingAssets missing = info->getMissingAssets(save); - if (missing.size() != 0) { - connect(action, &QAction::triggered, this, [this, missing] { fixMods_clicked(missing); }); - action->setEnabled(true); - } - } - } +void MainWindow::disableVisibleMods() +{ + if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really disable all visible mods?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + m_ModListSortProxy->disableAllVisible(); + } +} - QString deleteMenuLabel = tr("Delete %n save(s)", "", selection->selectedIndexes().count()); +void MainWindow::openInstanceFolder() +{ + ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.settings().getBaseDirectory()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); +} - menu.addAction(deleteMenuLabel, this, SLOT(deleteSavegame_clicked())); +void MainWindow::openProfileFolder() +{ + ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.currentProfile()->absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); +} - menu.exec(ui->savegameList->mapToGlobal(pos)); +void MainWindow::openDownloadsFolder() +{ + ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.settings().getDownloadDirectory()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); } -void MainWindow::linkToolbar() { - Executable& exe(getSelectedExecutable()); - exe.showOnToolbar(!exe.isShownOnToolbar()); - ui->linkButton->menu() - ->actions() - .at(static_cast(ShortcutType::Toolbar)) - ->setIcon(exe.isShownOnToolbar() ? QIcon(":/MO/gui/remove") : QIcon(":/MO/gui/link")); - updateToolBar(); +void MainWindow::openGameFolder() +{ + ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.managedGame()->gameDirectory().absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); } -namespace { -QString getLinkfile(const QString& dir, const Executable& exec) { - return QDir::fromNativeSeparators(dir) + "/" + exec.m_Title + ".lnk"; +void MainWindow::openMyGamesFolder() +{ + ::ShellExecuteW(nullptr, L"explore", ToWString(m_OrganizerCore.managedGame()->documentsDirectory().absolutePath()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); } -QString getDesktopLinkfile(const Executable& exec) { return getLinkfile(getDesktopDirectory(), exec); } -QString getStartMenuLinkfile(const Executable& exec) { return getLinkfile(getStartMenuDirectory(), exec); } -} // namespace +void MainWindow::exportModListCSV() +{ + //SelectionDialog selection(tr("Choose what to export")); -void MainWindow::addWindowsLink(const ShortcutType mapping) { - const Executable& selectedExecutable(getSelectedExecutable()); - QString const linkName = getLinkfile( - mapping == ShortcutType::Desktop ? getDesktopDirectory() : getStartMenuDirectory(), selectedExecutable); + //selection.addChoice(tr("Everything"), tr("All installed mods are included in the list"), 0); + //selection.addChoice(tr("Active Mods"), tr("Only active (checked) mods from your current profile are included"), 1); + //selection.addChoice(tr("Visible"), tr("All mods visible in the mod list are included"), 2); - if (QFile::exists(linkName)) { - if (QFile::remove(linkName)) { - ui->linkButton->menu()->actions().at(static_cast(mapping))->setIcon(QIcon(":/MO/gui/link")); - } else { - reportError(tr("failed to remove %1").arg(linkName)); - } - } else { - QFileInfo const exeInfo(qApp->applicationFilePath()); - // create link - QString executable = QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath()); - - std::wstring targetFile = ToWString(exeInfo.absoluteFilePath()); - std::wstring parameter = - ToWString(QString("\"moshortcut://%1:%2\"") - .arg(InstanceManager::instance().currentInstance(), selectedExecutable.m_Title)); - std::wstring description = ToWString(QString("Run %1 with ModOrganizer").arg(selectedExecutable.m_Title)); - std::wstring iconFile = ToWString(executable); - std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(qApp->applicationDirPath())); - - if (CreateShortcut(targetFile.c_str(), parameter.c_str(), - QDir::toNativeSeparators(linkName).toUtf8().constData(), description.c_str(), - (selectedExecutable.usesOwnIcon() ? iconFile.c_str() : nullptr), 0, - currentDirectory.c_str()) == 0) { - ui->linkButton->menu()->actions().at(static_cast(mapping))->setIcon(QIcon(":/MO/gui/remove")); - } else { - reportError(tr("failed to create %1").arg(linkName)); - } - } + QDialog selection(this); + QGridLayout *grid = new QGridLayout; + selection.setWindowTitle(tr("Export to csv")); + + 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")); + + QVBoxLayout *vbox = new QVBoxLayout; + vbox->addWidget(all); + vbox->addWidget(active); + vbox->addWidget(visible); + vbox->addStretch(1); + groupBoxRows->setLayout(vbox); + + + + grid->addWidget(groupBoxRows); + + QButtonGroup *buttonGroupRows = new QButtonGroup(); + buttonGroupRows->addButton(all, 0); + buttonGroupRows->addButton(active, 1); + buttonGroupRows->addButton(visible, 2); + buttonGroupRows->button(0)->setChecked(true); + + + + QGroupBox *groupBoxColumns = new QGroupBox(tr("Choose what Columns to export:")); + groupBoxColumns->setFlat(true); + + QCheckBox *mod_Priority = new QCheckBox(tr("Mod_Priority")); + mod_Priority->setChecked(true); + QCheckBox *mod_Name = new QCheckBox(tr("Mod_Name")); + mod_Name->setChecked(true); + QCheckBox *mod_Status = new QCheckBox(tr("Mod_Status")); + 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; + vbox1->addWidget(mod_Priority); + vbox1->addWidget(mod_Name); + vbox1->addWidget(mod_Status); + vbox1->addWidget(primary_Category); + vbox1->addWidget(nexus_ID); + vbox1->addWidget(mod_Nexus_URL); + vbox1->addWidget(mod_Version); + vbox1->addWidget(install_Date); + vbox1->addWidget(download_File_Name); + groupBoxColumns->setLayout(vbox1); + + grid->addWidget(groupBoxColumns); + + QPushButton *ok = new QPushButton("Ok"); + QPushButton *cancel = new QPushButton("Cancel"); + QDialogButtonBox *buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); + + connect(buttons, SIGNAL(accepted()), &selection, SLOT(accept())); + connect(buttons, SIGNAL(rejected()), &selection, SLOT(reject())); + + grid->addWidget(buttons); + + selection.setLayout(grid); + + + if (selection.exec() == QDialog::Accepted) { + + unsigned int numMods = ModInfo::getNumMods(); + 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; + if (mod_Priority->isChecked()) + fields.push_back(std::make_pair(QString("#Mod_Priority"), CSVBuilder::TYPE_STRING)); + if (mod_Name->isChecked()) + fields.push_back(std::make_pair(QString("#Mod_Name"), CSVBuilder::TYPE_STRING)); + if (mod_Status->isChecked()) + fields.push_back(std::make_pair(QString("#Mod_Status"), CSVBuilder::TYPE_STRING)); + if (primary_Category->isChecked()) + 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)); + if (mod_Nexus_URL->isChecked()) + 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)); + if (install_Date->isChecked()) + 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)); + + builder.setFields(fields); + + builder.writeHeader(); + + for (unsigned int i = 0; i < numMods; ++i) { + ModInfo::Ptr info = ModInfo::getByIndex(i); + bool enabled = m_OrganizerCore.currentProfile()->modEnabled(i); + if ((selectedRowID == 1) && !enabled) { + continue; + } + else if ((selectedRowID == 2) && !m_ModListSortProxy->filterMatchesMod(info, enabled)) { + 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 (mod_Priority->isChecked()) + builder.setRowField("#Mod_Priority", QString("%1").arg(m_OrganizerCore.currentProfile()->getModPriority(i), 4, 10, QChar('0'))); + if (mod_Name->isChecked()) + builder.setRowField("#Mod_Name", info->name()); + if (mod_Status->isChecked()) + builder.setRowField("#Mod_Status", (enabled)? "Enabled" : "Disabled"); + if (primary_Category->isChecked()) + builder.setRowField("#Primary_Category", (m_CategoryFactory.categoryExists(info->getPrimaryCategory())) ? m_CategoryFactory.getCategoryName(info->getPrimaryCategory()) : ""); + if (nexus_ID->isChecked()) + builder.setRowField("#Nexus_ID", info->getNexusID()); + if (mod_Nexus_URL->isChecked()) + builder.setRowField("#Mod_Nexus_URL", info->getURL()); + if (mod_Version->isChecked()) + builder.setRowField("#Mod_Version", info->getVersion().canonicalString()); + if (install_Date->isChecked()) + builder.setRowField("#Install_Date", info->creationTime().toString("yyyy/MM/dd HH:mm:ss")); + if (download_File_Name->isChecked()) + builder.setRowField("#Download_File_Name", info->getInstallationFile()); + + builder.writeRow(); + } + } + + SaveTextAsDialog saveDialog(this); + saveDialog.setText(buffer.data()); + saveDialog.exec(); + } + catch (const std::exception &e) { + reportError(tr("export failed: %1").arg(e.what())); + } + } +} + +static void addMenuAsPushButton(QMenu *menu, QMenu *subMenu) +{ + QPushButton *pushBtn = new QPushButton(subMenu->title()); + pushBtn->setMenu(subMenu); + QWidgetAction *action = new QWidgetAction(menu); + action->setDefaultWidget(pushBtn); + menu->addAction(action); } -void MainWindow::linkDesktop() { addWindowsLink(ShortcutType::Desktop); } +QMenu *MainWindow::modListContextMenu() +{ + QMenu *menu = new QMenu(this); + menu->addAction(tr("Install Mod..."), this, SLOT(installMod_clicked())); + + menu->addAction(tr("Create empty mod"), this, SLOT(createEmptyMod_clicked())); -void MainWindow::linkMenu() { addWindowsLink(ShortcutType::StartMenu); } + menu->addSeparator(); + + menu->addAction(tr("Enable all visible"), this, SLOT(enableVisibleMods())); + menu->addAction(tr("Disable all visible"), this, SLOT(disableVisibleMods())); + + menu->addAction(tr("Check all for update"), this, SLOT(checkModsForUpdates())); + + menu->addAction(tr("Refresh"), &m_OrganizerCore, SLOT(profileRefresh())); + + menu->addAction(tr("Export to csv..."), this, SLOT(exportModListCSV())); + + menu->addSeparator(); + + QMenu *openSubMenu = new QMenu(this); + + + openSubMenu->addAction(tr("Open Instance folder"), this, SLOT(openInstanceFolder())); + + openSubMenu->addAction(tr("Open Profile folder"), this, SLOT(openProfileFolder())); + + openSubMenu->addAction(tr("Open Downloads folder"), this, SLOT(openDownloadsFolder())); + + openSubMenu->addAction(tr("Open Game folder"), this, SLOT(openGameFolder())); -void MainWindow::on_actionSettings_triggered() { - Settings& settings = m_OrganizerCore.settings(); + openSubMenu->addAction(tr("Open MyGames folder"), this, SLOT(openMyGamesFolder())); - QString oldModDirectory(settings.getModDirectory()); - QString oldCacheDirectory(settings.getCacheDirectory()); - QString oldProfilesDirectory(settings.getProfileDirectory()); - bool oldDisplayForeign(settings.displayForeign()); - bool proxy = settings.useProxy(); + openSubMenu->setTitle(tr("Open Folder...")); - settings.query(this); + menu->addMenu(openSubMenu); - InstallationManager* instManager = m_OrganizerCore.installationManager(); - instManager->setModsDirectory(settings.getModDirectory()); - instManager->setDownloadDirectory(settings.getDownloadDirectory()); + return menu; +} - fixCategories(); - refreshFilters(); +void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) +{ + try { + QTreeView *modList = findChild("modList"); - if (settings.getProfileDirectory() != oldProfilesDirectory) { - refreshProfiles(); - } + m_ContextIdx = mapToModel(m_OrganizerCore.modList(), modList->indexAt(pos)); + m_ContextRow = m_ContextIdx.row(); + + QMenu *menu = nullptr; + QMenu *allMods = modListContextMenu(); + if (m_ContextRow == -1) { + // no selection + menu = allMods; + } else { + menu = new QMenu(this); + allMods->setTitle(tr("All Mods")); + menu->addMenu(allMods); + ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); + std::vector flags = info->getFlags(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { + if (QDir(info->absolutePath()).count() > 2) { + menu->addAction(tr("Sync to Mods..."), &m_OrganizerCore, SLOT(syncOverwrite())); + menu->addAction(tr("Create Mod..."), this, SLOT(createModFromOverwrite())); + menu->addAction(tr("Clear Overwrite..."), this, SLOT(clearOverwrite())); + } + menu->addAction(tr("Open in explorer"), this, SLOT(openExplorer_clicked())); + } else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) != flags.end()) { + menu->addAction(tr("Restore Backup"), this, SLOT(restoreBackup_clicked())); + menu->addAction(tr("Remove Backup..."), this, SLOT(removeMod_clicked())); + } else if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) { + // nop, nothing to do with this mod + } else { + QMenu *addRemoveCategoriesMenu = new QMenu(tr("Add/Remove Categories")); + populateMenuCategories(addRemoveCategoriesMenu, 0); + connect(addRemoveCategoriesMenu, SIGNAL(aboutToHide()), this, SLOT(addRemoveCategories_MenuHandler())); + addMenuAsPushButton(menu, addRemoveCategoriesMenu); + + QMenu *replaceCategoriesMenu = new QMenu(tr("Replace Categories")); + populateMenuCategories(replaceCategoriesMenu, 0); + connect(replaceCategoriesMenu, SIGNAL(aboutToHide()), this, SLOT(replaceCategories_MenuHandler())); + addMenuAsPushButton(menu, replaceCategoriesMenu); + + QMenu *primaryCategoryMenu = new QMenu(tr("Primary Category")); + connect(primaryCategoryMenu, SIGNAL(aboutToShow()), this, SLOT(addPrimaryCategoryCandidates())); + addMenuAsPushButton(menu, primaryCategoryMenu); + + menu->addSeparator(); + if (info->downgradeAvailable()) { + menu->addAction(tr("Change versioning scheme"), this, SLOT(changeVersioningScheme())); + } + if (info->updateAvailable() || info->downgradeAvailable()) { + if (info->updateIgnored()) { + menu->addAction(tr("Un-ignore update"), this, SLOT(unignoreUpdate())); + } else { + menu->addAction(tr("Ignore update"), this, SLOT(ignoreUpdate())); + } + } + menu->addSeparator(); + + menu->addAction(tr("Rename Mod..."), this, SLOT(renameMod_clicked())); + menu->addAction(tr("Remove Mod..."), this, SLOT(removeMod_clicked())); + menu->addAction(tr("Reinstall Mod"), this, SLOT(reinstallMod_clicked())); + + menu->addSeparator(); + + if (info->getNexusID() > 0) { + switch (info->endorsedState()) { + case ModInfo::ENDORSED_TRUE: { + menu->addAction(tr("Un-Endorse"), this, SLOT(unendorse_clicked())); + } break; + case ModInfo::ENDORSED_FALSE: { + menu->addAction(tr("Endorse"), this, SLOT(endorse_clicked())); + menu->addAction(tr("Won't endorse"), this, SLOT(dontendorse_clicked())); + } break; + case ModInfo::ENDORSED_NEVER: { + menu->addAction(tr("Endorse"), this, SLOT(endorse_clicked())); + } break; + default: { + QAction *action = new QAction(tr("Endorsement state unknown"), menu); + action->setEnabled(false); + menu->addAction(action); + } break; + } + } - DownloadManager* dlManager = m_OrganizerCore.downloadManager(); + menu->addSeparator(); - if (dlManager->getOutputDirectory() != settings.getDownloadDirectory()) { - if (dlManager->downloadsInProgress()) { - MessageDialog::showMessage(tr("Can't change download directory while " - "downloads are in progress!"), - this); - } else { - dlManager->setOutputDirectory(settings.getDownloadDirectory()); + std::vector flags = info->getFlags(); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_INVALID) != flags.end()) { + menu->addAction(tr("Ignore missing data"), this, SLOT(ignoreMissingData_clicked())); } - } - dlManager->setPreferredServers(settings.getPreferredServers()); - if ((settings.getModDirectory() != oldModDirectory) || (settings.displayForeign() != oldDisplayForeign)) { - m_OrganizerCore.profileRefresh(); - } + if (info->getNexusID() > 0) { + menu->addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked())); + } else if ((info->getURL() != "")) { + menu->addAction(tr("Visit web page"), this, SLOT(visitWebPage_clicked())); + } - if (settings.getCacheDirectory() != oldCacheDirectory) { - NexusInterface::instance()->setCacheDirectory(settings.getCacheDirectory()); - } + menu->addAction(tr("Open in explorer"), this, SLOT(openExplorer_clicked())); + } - if (proxy != settings.useProxy()) { - activateProxy(settings.useProxy()); + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) == flags.end()) { + QAction *infoAction = menu->addAction(tr("Information..."), this, SLOT(information_clicked())); + menu->setDefaultAction(infoAction); + } } - NexusInterface::instance()->setNMMVersion(settings.getNMMVersion()); + menu->exec(modList->mapToGlobal(pos)); + } catch (const std::exception &e) { + reportError(tr("Exception: ").arg(e.what())); + } catch (...) { + reportError(tr("Unknown exception")); + } +} - updateDownloadListDelegate(); - m_OrganizerCore.updateVFSParams(settings.logLevel(), settings.crashDumpsType()); - m_OrganizerCore.cycleDiagnostics(); -} +void MainWindow::on_categoriesList_itemSelectionChanged() +{ + QModelIndexList indices = ui->categoriesList->selectionModel()->selectedRows(); + std::vector categories; + std::vector content; + for (const QModelIndex &index : indices) { + int filterType = index.data(Qt::UserRole + 1).toInt(); + if ((filterType == ModListSortProxy::TYPE_CATEGORY) + || (filterType == ModListSortProxy::TYPE_SPECIAL)) { + int categoryId = index.data(Qt::UserRole).toInt(); + if (categoryId != CategoryFactory::CATEGORY_NONE) { + categories.push_back(categoryId); + } + } else if (filterType == ModListSortProxy::TYPE_CONTENT) { + int contentId = index.data(Qt::UserRole).toInt(); + content.push_back(contentId); + } + } -void MainWindow::on_actionNexus_triggered() { - ::ShellExecuteW(nullptr, L"open", NexusInterface::instance()->getGameURL().toStdWString().c_str(), nullptr, nullptr, - SW_SHOWNORMAL); -} + m_ModListSortProxy->setCategoryFilter(categories); + m_ModListSortProxy->setContentFilter(content); + ui->clickBlankButton->setEnabled(categories.size() > 0 || content.size() >0); + //ui->clearFiltersButton->setStyleSheet("border:5px solid #ff0000;"); + ui->clearFiltersButton->setVisible(categories.size() > 0 || content.size() > 0); -void MainWindow::nexusLinkActivated(const QString& link) { - ::ShellExecuteW(nullptr, L"open", ToWString(link).c_str(), nullptr, nullptr, SW_SHOWNORMAL); - ui->tabWidget->setCurrentIndex(4); + if (indices.count() == 0) { + ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); + } else if (indices.count() > 1) { + ui->currentCategoryLabel->setText(QString("(%1)").arg(tr(""))); + } else { + ui->currentCategoryLabel->setText(QString("(%1)").arg(indices.first().data().toString())); + } + ui->modList->reset(); } -void MainWindow::linkClicked(const QString& url) { - ::ShellExecuteW(nullptr, L"open", ToWString(url).c_str(), nullptr, nullptr, SW_SHOWNORMAL); -} -void MainWindow::installTranslator(const QString& name) { - QTranslator* translator = new QTranslator(this); - QString fileName = name + "_" + m_CurrentLanguage; - if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) { - if ((m_CurrentLanguage != "en-US") && (m_CurrentLanguage != "en_US") && (m_CurrentLanguage != "en-GB")) { - qDebug("localization file %s not found", qPrintable(fileName)); - } // we don't actually expect localization files for english +void MainWindow::deleteSavegame_clicked() +{ + SaveGameInfo const *info = m_OrganizerCore.managedGame()->feature(); + + QString savesMsgLabel; + QStringList deleteFiles; + + int count = 0; + + for (const QModelIndex &idx : ui->savegameList->selectionModel()->selectedIndexes()) { + QString name = idx.data(Qt::UserRole).toString(); + + if (count < 10) { + savesMsgLabel += "
  • " + QFileInfo(name).completeBaseName() + "
  • "; } + ++count; - qApp->installTranslator(translator); - m_Translators.push_back(translator); + if (info == nullptr) { + deleteFiles.push_back(name); + } else { + ISaveGame const *save = info->getSaveGameInfo(name); + deleteFiles += save->allFiles(); + } + } + + if (count > 10) { + savesMsgLabel += "
  • ... " + tr("%1 more").arg(count - 10) + "
  • "; + } + + if (QMessageBox::question(this, 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 MainWindow::languageChange(const QString& newLanguage) { - for (QTranslator* trans : m_Translators) { - qApp->removeTranslator(trans); - } - m_Translators.clear(); - m_CurrentLanguage = newLanguage; +void MainWindow::fixMods_clicked(SaveGameInfo::MissingAssets const &missingAssets) +{ + ActivateModsDialog dialog(missingAssets, this); + if (dialog.exec() == QDialog::Accepted) { + // activate the required mods, then enable all esps + std::set modsToActivate = dialog.getModsToActivate(); + for (std::set::iterator iter = modsToActivate.begin(); iter != modsToActivate.end(); ++iter) { + if ((*iter != "") && (*iter != "")) { + unsigned int modIndex = ModInfo::getIndex(*iter); + m_OrganizerCore.currentProfile()->setModEnabled(modIndex, true); + } + } - installTranslator("qt"); - installTranslator(ToQString(AppConfig::translationPrefix())); - ui->retranslateUi(this); - qDebug("loaded language %s", qPrintable(newLanguage)); + m_OrganizerCore.currentProfile()->writeModlist(); + m_OrganizerCore.refreshLists(); - ui->profileBox->setItemText(0, QObject::tr("")); + std::set espsToActivate = dialog.getESPsToActivate(); + for (std::set::iterator iter = espsToActivate.begin(); iter != espsToActivate.end(); ++iter) { + m_OrganizerCore.pluginList()->enableESP(*iter); + } + m_OrganizerCore.saveCurrentLists(); + } +} - createHelpWidget(); - updateDownloadListDelegate(); - updateProblemsButton(); +void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) +{ + QItemSelectionModel *selection = ui->savegameList->selectionModel(); - ui->listOptionsBtn->setMenu(modListContextMenu()); -} + if (!selection->hasSelection()) { + return; + } -void MainWindow::writeDataToFile(QFile& file, const QString& directory, const DirectoryEntry& directoryEntry) { - for (FileEntry::Ptr current : directoryEntry.getFiles()) { - bool isArchive = false; - int origin = current->getOrigin(isArchive); - if (isArchive) { - // TODO: don't list files from archives. maybe make this an option? - continue; - } - QString fullName = directory + "\\" + ToQString(current->getName()); - file.write(fullName.toUtf8()); + QMenu menu; + QAction *action = menu.addAction(tr("Enable Mods...")); + action->setEnabled(false); - file.write("\t("); - file.write(ToQString(m_OrganizerCore.directoryStructure()->getOriginByID(origin).getName()).toUtf8()); - file.write(")\r\n"); + if (selection->selectedIndexes().count() == 1) { + SaveGameInfo const *info = this->m_OrganizerCore.managedGame()->feature(); + if (info != nullptr) { + QString save = ui->savegameList->currentItem()->data(Qt::UserRole).toString(); + SaveGameInfo::MissingAssets missing = info->getMissingAssets(save); + if (missing.size() != 0) { + connect(action, &QAction::triggered, this, [this, missing]{ fixMods_clicked(missing); }); + action->setEnabled(true); + } } + } - // recurse into subdirectories - std::vector::const_iterator current, end; - directoryEntry.getSubDirectories(current, end); - for (; current != end; ++current) { - writeDataToFile(file, directory + "\\" + ToQString((*current)->getName()), **current); - } -} - -void MainWindow::writeDataToFile() { - QString fileName = QFileDialog::getSaveFileName(this); - QFile file(fileName); - if (!file.open(QIODevice::WriteOnly)) { - reportError(tr("failed to write to file %1").arg(fileName)); - } - - writeDataToFile(file, "data", *m_OrganizerCore.directoryStructure()); - file.close(); - - MessageDialog::showMessage(tr("%1 written").arg(QDir::toNativeSeparators(fileName)), this); -} - -int MainWindow::getBinaryExecuteInfo(const QFileInfo& targetInfo, QFileInfo& binaryInfo, QString& arguments) { - QString extension = targetInfo.suffix(); - if ((extension.compare("cmd", Qt::CaseInsensitive) == 0) || (extension.compare("com", Qt::CaseInsensitive) == 0) || - (extension.compare("bat", Qt::CaseInsensitive) == 0)) { - binaryInfo = QFileInfo("C:\\Windows\\System32\\cmd.exe"); - arguments = QString("/C \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - return 1; - } else if (extension.compare("exe", Qt::CaseInsensitive) == 0) { - binaryInfo = targetInfo; - return 1; - } else if (extension.compare("jar", Qt::CaseInsensitive) == 0) { - // types that need to be injected into - std::wstring targetPathW = ToWString(targetInfo.absoluteFilePath()); - QString binaryPath; - - { // try to find java automatically - WCHAR buffer[MAX_PATH]; - if (::FindExecutableW(targetPathW.c_str(), nullptr, buffer) > (HINSTANCE)32) { - DWORD binaryType = 0UL; - if (!::GetBinaryTypeW(buffer, &binaryType)) { - qDebug("failed to determine binary type of \"%ls\": %lu", buffer, ::GetLastError()); - } else if (binaryType == SCS_32BIT_BINARY) { - binaryPath = ToQString(buffer); - } - } - } - if (binaryPath.isEmpty() && (extension == "jar")) { - // second attempt: look to the registry - QSettings javaReg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", - QSettings::NativeFormat); - if (javaReg.contains("CurrentVersion")) { - QString currentVersion = javaReg.value("CurrentVersion").toString(); - binaryPath = - javaReg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe"); - } - } - if (binaryPath.isEmpty()) { - binaryPath = QFileDialog::getOpenFileName(this, tr("Select binary"), QString(), tr("Binary") + " (*.exe)"); - } - if (binaryPath.isEmpty()) { - return 0; - } - binaryInfo = QFileInfo(binaryPath); - if (extension == "jar") { - arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - } else { - arguments = QString("\"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - } - return 1; - } else { - return 2; - } -} - -void MainWindow::addAsExecutable() { - if (m_ContextItem != nullptr) { - QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); - QFileInfo binaryInfo; - QString arguments; - switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) { - case 1: { - QString name = QInputDialog::getText(this, tr("Enter Name"), tr("Please enter a name for the executable"), - QLineEdit::Normal, targetInfo.baseName()); - if (!name.isEmpty()) { - // Note: If this already exists, you'll lose custom settings - m_OrganizerCore.executablesList()->addExecutable(name, binaryInfo.absoluteFilePath(), arguments, - targetInfo.absolutePath(), QString(), - Executable::CustomExecutable); - refreshExecutablesList(); - } - } break; - case 2: { - QMessageBox::information(this, tr("Not an executable"), tr("This is not a recognized executable.")); - } break; - default: { - // nop - } break; - } - } + QString deleteMenuLabel = tr("Delete %n save(s)", "", selection->selectedIndexes().count()); + + menu.addAction(deleteMenuLabel, this, SLOT(deleteSavegame_clicked())); + + menu.exec(ui->savegameList->mapToGlobal(pos)); } -void MainWindow::originModified(int originID) { - FilesOrigin& origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); - origin.enable(false); - m_OrganizerCore.directoryStructure()->addFromOrigin(origin.getName(), origin.getPath(), origin.getPriority()); - DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure()); +void MainWindow::linkToolbar() +{ + Executable &exe(getSelectedExecutable()); + exe.showOnToolbar(!exe.isShownOnToolbar()); + ui->linkButton->menu()->actions().at(static_cast(ShortcutType::Toolbar))->setIcon(exe.isShownOnToolbar() ? QIcon(":/MO/gui/remove") : QIcon(":/MO/gui/link")); + updateToolBar(); } -void MainWindow::hideFile() { - QString oldName = m_ContextItem->data(0, Qt::UserRole).toString(); - QString newName = oldName + ModInfo::s_HiddenExt; +namespace { +QString getLinkfile(const QString &dir, const Executable &exec) +{ + return QDir::fromNativeSeparators(dir) + "/" + exec.m_Title + ".lnk"; +} - if (QFileInfo(newName).exists()) { - if (QMessageBox::question(this, tr("Replace file?"), - tr("There already is a hidden version of this file. Replace it?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - if (!QFile(newName).remove()) { - QMessageBox::critical( - this, tr("File operation failed"), - tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); - return; - } - } else { - return; - } - } +QString getDesktopLinkfile(const Executable &exec) +{ + return getLinkfile(getDesktopDirectory(), exec); +} - if (QFile::rename(oldName, newName)) { - originModified(m_ContextItem->data(1, Qt::UserRole + 1).toInt()); - refreshDataTreeKeepExpandedNodes(); - } else { - reportError(tr("failed to rename \"%1\" to \"%2\"").arg(oldName).arg(QDir::toNativeSeparators(newName))); - } +QString getStartMenuLinkfile(const Executable &exec) +{ + return getLinkfile(getStartMenuDirectory(), exec); +} } -void MainWindow::unhideFile() { - QString oldName = m_ContextItem->data(0, Qt::UserRole).toString(); - QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); - if (QFileInfo(newName).exists()) { - if (QMessageBox::question(this, tr("Replace file?"), - tr("There already is a visible version of this file. Replace it?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - if (!QFile(newName).remove()) { - QMessageBox::critical( - this, tr("File operation failed"), - tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); - return; - } - } else { - return; - } - } - if (QFile::rename(oldName, newName)) { - originModified(m_ContextItem->data(1, Qt::UserRole + 1).toInt()); - refreshDataTreeKeepExpandedNodes(); +void MainWindow::addWindowsLink(const ShortcutType mapping) +{ + const Executable &selectedExecutable(getSelectedExecutable()); + QString const linkName = getLinkfile(mapping == ShortcutType::Desktop ? getDesktopDirectory() : getStartMenuDirectory(), + selectedExecutable); + + if (QFile::exists(linkName)) { + if (QFile::remove(linkName)) { + ui->linkButton->menu()->actions().at(static_cast(mapping))->setIcon(QIcon(":/MO/gui/link")); + } else { + reportError(tr("failed to remove %1").arg(linkName)); + } + } else { + QFileInfo const exeInfo(qApp->applicationFilePath()); + // create link + QString executable = QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath()); + + std::wstring targetFile = ToWString(exeInfo.absoluteFilePath()); + std::wstring parameter = ToWString( + QString("\"moshortcut://%1:%2\"").arg(InstanceManager::instance().currentInstance(),selectedExecutable.m_Title)); + std::wstring description = ToWString(QString("Run %1 with ModOrganizer").arg(selectedExecutable.m_Title)); + std::wstring iconFile = ToWString(executable); + std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(qApp->applicationDirPath())); + + if (CreateShortcut(targetFile.c_str() + , parameter.c_str() + , QDir::toNativeSeparators(linkName).toUtf8().constData() + , description.c_str() + , (selectedExecutable.usesOwnIcon() ? iconFile.c_str() : nullptr), 0 + , currentDirectory.c_str()) == 0) { + ui->linkButton->menu()->actions().at(static_cast(mapping))->setIcon(QIcon(":/MO/gui/remove")); } else { - reportError(tr("failed to rename \"%1\" to \"%2\"") - .arg(QDir::toNativeSeparators(oldName)) - .arg(QDir::toNativeSeparators(newName))); + reportError(tr("failed to create %1").arg(linkName)); } + } } -void MainWindow::previewDataFile() { - QString fileName = QDir::fromNativeSeparators(m_ContextItem->data(0, Qt::UserRole).toString()); +void MainWindow::linkDesktop() +{ + addWindowsLink(ShortcutType::Desktop); +} - // 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 +void MainWindow::linkMenu() +{ + addWindowsLink(ShortcutType::StartMenu); +} - // crude: we search for the next slash after the base mod directory to skip everything up to the data-relative - // directory - int offset = m_OrganizerCore.settings().getModDirectory().size() + 1; - offset = fileName.indexOf("/", offset); - fileName = fileName.mid(offset + 1); +void MainWindow::on_actionSettings_triggered() +{ + Settings &settings = m_OrganizerCore.settings(); - const FileEntry::Ptr file = m_OrganizerCore.directoryStructure()->searchFile(ToWString(fileName), nullptr); + QString oldModDirectory(settings.getModDirectory()); + QString oldCacheDirectory(settings.getCacheDirectory()); + QString oldProfilesDirectory(settings.getProfileDirectory()); + bool oldDisplayForeign(settings.displayForeign()); + bool proxy = settings.useProxy(); - if (file.get() == nullptr) { - reportError(tr("file not found: %1").arg(fileName)); - return; - } + settings.query(this); - // set up preview dialog - PreviewDialog preview(fileName); - auto addFunc = [&](int originId) { - FilesOrigin& origin = m_OrganizerCore.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); - if (wid == nullptr) { - reportError(tr("failed to generate preview for %1").arg(filePath)); - } else { - preview.addVariant(ToQString(origin.getName()), wid); - } - } - }; + InstallationManager *instManager = m_OrganizerCore.installationManager(); + instManager->setModsDirectory(settings.getModDirectory()); + instManager->setDownloadDirectory(settings.getDownloadDirectory()); - addFunc(file->getOrigin()); - for (auto alt : file->getAlternatives()) { - addFunc(alt.first); - } - if (preview.numVariants() > 0) { - preview.exec(); + fixCategories(); + refreshFilters(); + + if (settings.getProfileDirectory() != oldProfilesDirectory) { + refreshProfiles(); + } + + DownloadManager *dlManager = m_OrganizerCore.downloadManager(); + + if (dlManager->getOutputDirectory() != settings.getDownloadDirectory()) { + if (dlManager->downloadsInProgress()) { + MessageDialog::showMessage(tr("Can't change download directory while " + "downloads are in progress!"), + this); } else { - QMessageBox::information( - this, tr("Sorry"), - tr("Sorry, can't preview anything. This function currently does not support extracting from bsas.")); - } -} - -void MainWindow::openDataFile() { - if (m_ContextItem != nullptr) { - QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); - QFileInfo binaryInfo; - QString arguments; - switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) { - case 1: { - m_OrganizerCore.spawnBinaryDirect(binaryInfo, arguments, m_OrganizerCore.currentProfile()->name(), - targetInfo.absolutePath(), "", ""); - } break; - case 2: { - ::ShellExecuteW(nullptr, L"open", ToWString(targetInfo.absoluteFilePath()).c_str(), nullptr, nullptr, - SW_SHOWNORMAL); - } break; - default: { - // nop - } break; - } + dlManager->setOutputDirectory(settings.getDownloadDirectory()); } + } + dlManager->setPreferredServers(settings.getPreferredServers()); + + if ((settings.getModDirectory() != oldModDirectory) + || (settings.displayForeign() != oldDisplayForeign)) { + m_OrganizerCore.profileRefresh(); + } + + if (settings.getCacheDirectory() != oldCacheDirectory) { + NexusInterface::instance()->setCacheDirectory(settings.getCacheDirectory()); + } + + if (proxy != settings.useProxy()) { + activateProxy(settings.useProxy()); + } + + NexusInterface::instance()->setNMMVersion(settings.getNMMVersion()); + + updateDownloadListDelegate(); + + m_OrganizerCore.updateVFSParams(settings.logLevel(), settings.crashDumpsType()); + m_OrganizerCore.cycleDiagnostics(); } -void MainWindow::updateAvailable() { - for (QAction* action : ui->toolBar->actions()) { - if (action->text() == tr("Update")) { - action->setEnabled(true); - action->setToolTip(tr("Update available")); - break; - } - } + +void MainWindow::on_actionNexus_triggered() +{ + ::ShellExecuteW(nullptr, L"open", + NexusInterface::instance()->getGameURL().toStdWString().c_str(), + nullptr, nullptr, SW_SHOWNORMAL); } -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 - if (m_StartTime.secsTo(QTime::currentTime()) < 5) { - uint hash = qHash(motd); - if (hash != m_OrganizerCore.settings().getMotDHash()) { - MotDDialog dialog(motd); - dialog.exec(); - m_OrganizerCore.settings().setMotDHash(hash); - } - } - ui->actionEndorseMO->setVisible(false); +void MainWindow::nexusLinkActivated(const QString &link) +{ + ::ShellExecuteW(nullptr, L"open", ToWString(link).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + ui->tabWidget->setCurrentIndex(4); } -void MainWindow::notEndorsedYet() { ui->actionEndorseMO->setVisible(true); } -void MainWindow::on_dataTree_customContextMenuRequested(const QPoint& pos) { - QTreeWidget* dataTree = findChild("dataTree"); - m_ContextItem = dataTree->itemAt(pos.x(), pos.y()); +void MainWindow::linkClicked(const QString &url) +{ + ::ShellExecuteW(nullptr, L"open", ToWString(url).c_str(), nullptr, nullptr, SW_SHOWNORMAL); +} - QMenu menu; - if ((m_ContextItem != nullptr) && (m_ContextItem->childCount() == 0)) { - menu.addAction(tr("Open/Execute"), this, SLOT(openDataFile())); - menu.addAction(tr("Add as Executable"), this, SLOT(addAsExecutable())); - QString fileName = m_ContextItem->text(0); - if (m_PluginContainer.previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { - menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); - } +void MainWindow::installTranslator(const QString &name) +{ + QTranslator *translator = new QTranslator(this); + QString fileName = name + "_" + m_CurrentLanguage; + if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) { + if ((m_CurrentLanguage != "en-US") + && (m_CurrentLanguage != "en_US") + && (m_CurrentLanguage != "en-GB")) { + qDebug("localization file %s not found", qPrintable(fileName)); + } // we don't actually expect localization files for english + } - // offer to hide/unhide file, but not for files from archives - if (!m_ContextItem->data(0, Qt::UserRole + 1).toBool()) { - if (m_ContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) { - menu.addAction(tr("Un-Hide"), this, SLOT(unhideFile())); - } else { - menu.addAction(tr("Hide"), this, SLOT(hideFile())); - } - } + qApp->installTranslator(translator); + m_Translators.push_back(translator); +} - menu.addSeparator(); + +void MainWindow::languageChange(const QString &newLanguage) +{ + for (QTranslator *trans : m_Translators) { + qApp->removeTranslator(trans); + } + m_Translators.clear(); + + m_CurrentLanguage = newLanguage; + + installTranslator("qt"); + installTranslator(ToQString(AppConfig::translationPrefix())); + ui->retranslateUi(this); + qDebug("loaded language %s", qPrintable(newLanguage)); + + ui->profileBox->setItemText(0, QObject::tr("")); + + createHelpWidget(); + + updateDownloadListDelegate(); + updateProblemsButton(); + + ui->listOptionsBtn->setMenu(modListContextMenu()); +} + +void MainWindow::writeDataToFile(QFile &file, const QString &directory, const DirectoryEntry &directoryEntry) +{ + for (FileEntry::Ptr current : directoryEntry.getFiles()) { + bool isArchive = false; + int origin = current->getOrigin(isArchive); + if (isArchive) { + // TODO: don't list files from archives. maybe make this an option? + continue; } - menu.addAction(tr("Write To File..."), this, SLOT(writeDataToFile())); - menu.addAction(tr("Refresh"), this, SLOT(on_btnRefreshData_clicked())); + QString fullName = directory + "\\" + ToQString(current->getName()); + file.write(fullName.toUtf8()); + + file.write("\t("); + file.write(ToQString(m_OrganizerCore.directoryStructure()->getOriginByID(origin).getName()).toUtf8()); + file.write(")\r\n"); + } - menu.exec(dataTree->mapToGlobal(pos)); + // recurse into subdirectories + std::vector::const_iterator current, end; + directoryEntry.getSubDirectories(current, end); + for (; current != end; ++current) { + writeDataToFile(file, directory + "\\" + ToQString((*current)->getName()), **current); + } } -void MainWindow::on_conflictsCheckBox_toggled(bool) { refreshDataTree(); } +void MainWindow::writeDataToFile() +{ + QString fileName = QFileDialog::getSaveFileName(this); + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly)) { + reportError(tr("failed to write to file %1").arg(fileName)); + } -void MainWindow::on_actionUpdate_triggered() { m_OrganizerCore.startMOUpdate(); } + writeDataToFile(file, "data", *m_OrganizerCore.directoryStructure()); + file.close(); -void MainWindow::on_actionEndorseMO_triggered() { - if (QMessageBox::question( - this, tr("Endorse Mod Organizer"), - tr("Do you want to endorse Mod Organizer on %1 now?").arg(NexusInterface::instance()->getGameURL()), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - NexusInterface::instance()->requestToggleEndorsement(m_OrganizerCore.managedGame()->nexusModOrganizerID(), true, - this, QVariant(), QString()); - } + MessageDialog::showMessage(tr("%1 written").arg(QDir::toNativeSeparators(fileName)), this); } -void MainWindow::updateDownloadListDelegate() { - if (m_OrganizerCore.settings().compactDownloads()) { - ui->downloadView->setItemDelegate(new DownloadListWidgetCompactDelegate( - m_OrganizerCore.downloadManager(), m_OrganizerCore.settings().metaDownloads(), ui->downloadView, - ui->downloadView)); + +int MainWindow::getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments) +{ + QString extension = targetInfo.suffix(); + if ((extension.compare("cmd", Qt::CaseInsensitive) == 0) || + (extension.compare("com", Qt::CaseInsensitive) == 0) || + (extension.compare("bat", Qt::CaseInsensitive) == 0)) { + binaryInfo = QFileInfo("C:\\Windows\\System32\\cmd.exe"); + arguments = QString("/C \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); + return 1; + } else if (extension.compare("exe", Qt::CaseInsensitive) == 0) { + binaryInfo = targetInfo; + return 1; + } else if (extension.compare("jar", Qt::CaseInsensitive) == 0) { + // types that need to be injected into + std::wstring targetPathW = ToWString(targetInfo.absoluteFilePath()); + QString binaryPath; + + { // try to find java automatically + WCHAR buffer[MAX_PATH]; + if (::FindExecutableW(targetPathW.c_str(), nullptr, buffer) > (HINSTANCE)32) { + DWORD binaryType = 0UL; + if (!::GetBinaryTypeW(buffer, &binaryType)) { + qDebug("failed to determine binary type of \"%ls\": %lu", buffer, ::GetLastError()); + } else if (binaryType == SCS_32BIT_BINARY) { + binaryPath = ToQString(buffer); + } + } + } + if (binaryPath.isEmpty() && (extension == "jar")) { + // second attempt: look to the registry + QSettings javaReg("HKEY_LOCAL_MACHINE\\Software\\JavaSoft\\Java Runtime Environment", QSettings::NativeFormat); + if (javaReg.contains("CurrentVersion")) { + QString currentVersion = javaReg.value("CurrentVersion").toString(); + binaryPath = javaReg.value(QString("%1/JavaHome").arg(currentVersion)).toString().append("\\bin\\javaw.exe"); + } + } + if (binaryPath.isEmpty()) { + binaryPath = QFileDialog::getOpenFileName(this, tr("Select binary"), QString(), tr("Binary") + " (*.exe)"); + } + if (binaryPath.isEmpty()) { + return 0; + } + binaryInfo = QFileInfo(binaryPath); + if (extension == "jar") { + arguments = QString("-jar \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); } else { - ui->downloadView->setItemDelegate(new DownloadListWidgetDelegate(m_OrganizerCore.downloadManager(), - m_OrganizerCore.settings().metaDownloads(), - ui->downloadView, ui->downloadView)); - } - - DownloadListSortProxy* sortProxy = new DownloadListSortProxy(m_OrganizerCore.downloadManager(), ui->downloadView); - sortProxy->setSourceModel(new DownloadList(m_OrganizerCore.downloadManager(), ui->downloadView)); - connect(ui->downloadFilterEdit, SIGNAL(textChanged(QString)), sortProxy, SLOT(updateFilter(QString))); - connect(ui->downloadFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(downloadFilterChanged(QString))); - - ui->downloadView->setModel(sortProxy); - ui->downloadView->sortByColumn(1, Qt::DescendingOrder); - ui->downloadView->header()->resizeSections(QHeaderView::Fixed); - - connect(ui->downloadView->itemDelegate(), SIGNAL(installDownload(int)), &m_OrganizerCore, - SLOT(installDownload(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(queryInfo(int)), m_OrganizerCore.downloadManager(), - SLOT(queryInfo(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(removeDownload(int, bool)), m_OrganizerCore.downloadManager(), - SLOT(removeDownload(int, bool))); - connect(ui->downloadView->itemDelegate(), SIGNAL(restoreDownload(int)), m_OrganizerCore.downloadManager(), - SLOT(restoreDownload(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(cancelDownload(int)), m_OrganizerCore.downloadManager(), - SLOT(cancelDownload(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(pauseDownload(int)), m_OrganizerCore.downloadManager(), - SLOT(pauseDownload(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(resumeDownload(int)), this, SLOT(resumeDownload(int))); -} - -void MainWindow::modDetailsUpdated(bool) { - if (--m_ModsToUpdate == 0) { - statusBar()->hide(); - m_ModListSortProxy->setCategoryFilter( - boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); - for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { - if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == - CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { - ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); - break; - } - } - m_RefreshProgress->setVisible(false); + arguments = QString("\"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); + } + return 1; + } else { + return 2; + } +} + + +void MainWindow::addAsExecutable() +{ + if (m_ContextItem != nullptr) { + QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); + QFileInfo binaryInfo; + QString arguments; + switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) { + case 1: { + QString name = QInputDialog::getText(this, tr("Enter Name"), + tr("Please enter a name for the executable"), QLineEdit::Normal, + targetInfo.baseName()); + if (!name.isEmpty()) { + //Note: If this already exists, you'll lose custom settings + m_OrganizerCore.executablesList()->addExecutable(name, + binaryInfo.absoluteFilePath(), + arguments, + targetInfo.absolutePath(), + QString(), + Executable::CustomExecutable); + refreshExecutablesList(); + } + } break; + case 2: { + QMessageBox::information(this, tr("Not an executable"), tr("This is not a recognized executable.")); + } break; + default: { + // nop + } break; + } + } +} + + +void MainWindow::originModified(int originID) +{ + FilesOrigin &origin = m_OrganizerCore.directoryStructure()->getOriginByID(originID); + origin.enable(false); + m_OrganizerCore.directoryStructure()->addFromOrigin(origin.getName(), origin.getPath(), origin.getPriority()); + DirectoryRefresher::cleanStructure(m_OrganizerCore.directoryStructure()); +} + + +void MainWindow::hideFile() +{ + QString oldName = m_ContextItem->data(0, Qt::UserRole).toString(); + QString newName = oldName + ModInfo::s_HiddenExt; + + if (QFileInfo(newName).exists()) { + if (QMessageBox::question(this, tr("Replace file?"), tr("There already is a hidden version of this file. Replace it?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (!QFile(newName).remove()) { + QMessageBox::critical(this, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); + return; + } } else { - m_RefreshProgress->setValue(m_RefreshProgress->maximum() - m_ModsToUpdate); + return; } + } + + if (QFile::rename(oldName, newName)) { + originModified(m_ContextItem->data(1, Qt::UserRole + 1).toInt()); + refreshDataTreeKeepExpandedNodes(); + } else { + reportError(tr("failed to rename \"%1\" to \"%2\"").arg(oldName).arg(QDir::toNativeSeparators(newName))); + } } -void MainWindow::nxmUpdatesAvailable(const std::vector& modIDs, QVariant userData, QVariant resultData, int) { - m_ModsToUpdate -= static_cast(modIDs.size()); - QVariantList resultList = resultData.toList(); - for (auto iter = resultList.begin(); iter != resultList.end(); ++iter) { - QVariantMap result = iter->toMap(); - if (result["id"].toInt() == m_OrganizerCore.managedGame()->nexusModOrganizerID()) { - if (!result["voted_by_user"].toBool()) { - ui->actionEndorseMO->setVisible(true); - } + +void MainWindow::unhideFile() +{ + QString oldName = m_ContextItem->data(0, Qt::UserRole).toString(); + QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); + if (QFileInfo(newName).exists()) { + if (QMessageBox::question(this, tr("Replace file?"), tr("There already is a visible version of this file. Replace it?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (!QFile(newName).remove()) { + QMessageBox::critical(this, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); + return; + } + } else { + return; + } + } + if (QFile::rename(oldName, newName)) { + originModified(m_ContextItem->data(1, Qt::UserRole + 1).toInt()); + refreshDataTreeKeepExpandedNodes(); + } else { + reportError(tr("failed to rename \"%1\" to \"%2\"").arg(QDir::toNativeSeparators(oldName)).arg(QDir::toNativeSeparators(newName))); + } +} + +void MainWindow::previewDataFile() +{ + QString fileName = QDir::fromNativeSeparators(m_ContextItem->data(0, Qt::UserRole).toString()); + + // 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 + + // crude: we search for the next slash after the base mod directory to skip everything up to the data-relative directory + int offset = m_OrganizerCore.settings().getModDirectory().size() + 1; + offset = fileName.indexOf("/", offset); + fileName = fileName.mid(offset + 1); + + const FileEntry::Ptr file = m_OrganizerCore.directoryStructure()->searchFile(ToWString(fileName), nullptr); + + if (file.get() == nullptr) { + reportError(tr("file not found: %1").arg(fileName)); + return; + } + + // set up preview dialog + PreviewDialog preview(fileName); + auto addFunc = [&] (int originId) { + FilesOrigin &origin = m_OrganizerCore.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); + if (wid == nullptr) { + reportError(tr("failed to generate preview for %1").arg(filePath)); } else { - std::vector info = ModInfo::getByModID(result["id"].toInt()); - for (auto iter = info.begin(); iter != info.end(); ++iter) { - (*iter)->setNewestVersion(result["version"].toString()); - (*iter)->setNexusDescription(result["description"].toString()); - if (NexusInterface::instance()->getAccessManager()->loggedIn() && result.contains("voted_by_user")) { - // don't use endorsement info if we're not logged in or if the response doesn't contain it - (*iter)->setIsEndorsed(result["voted_by_user"].toBool()); - } - } + preview.addVariant(ToQString(origin.getName()), wid); } - } + } + }; - if (m_ModsToUpdate <= 0) { - statusBar()->hide(); - m_ModListSortProxy->setCategoryFilter( - boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); - for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { - if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == - CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { - ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); - break; - } - } - } else { - m_RefreshProgress->setValue(m_RefreshProgress->maximum() - m_ModsToUpdate); - } + addFunc(file->getOrigin()); + for (auto alt : file->getAlternatives()) { + addFunc(alt.first); + } + if (preview.numVariants() > 0) { + preview.exec(); + } else { + QMessageBox::information(this, tr("Sorry"), tr("Sorry, can't preview anything. This function currently does not support extracting from bsas.")); + } } -void MainWindow::nxmEndorsementToggled(int, QVariant, QVariant resultData, int) { - if (resultData.toBool()) { - ui->actionEndorseMO->setVisible(false); - QMessageBox::question(this, tr("Thank you!"), tr("Thank you for your endorsement!")); - } - - if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(int, QVariant, QVariant, int)), this, - SLOT(nxmEndorsementToggled(int, QVariant, QVariant, int)))) { - qCritical("failed to disconnect endorsement slot"); +void MainWindow::openDataFile() +{ + if (m_ContextItem != nullptr) { + QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); + QFileInfo binaryInfo; + QString arguments; + switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) { + case 1: { + m_OrganizerCore.spawnBinaryDirect( + binaryInfo, arguments, m_OrganizerCore.currentProfile()->name(), + targetInfo.absolutePath(), "", ""); + } break; + case 2: { + ::ShellExecuteW(nullptr, L"open", + ToWString(targetInfo.absoluteFilePath()).c_str(), + nullptr, nullptr, SW_SHOWNORMAL); + } break; + default: { + // nop + } break; } + } } -void MainWindow::nxmDownloadURLs(int, int, QVariant, QVariant resultData, int) { - QVariantList serverList = resultData.toList(); - QList servers; - for (const QVariant& server : serverList) { - QVariantMap serverInfo = server.toMap(); - ServerInfo info; - info.name = serverInfo["Name"].toString(); - info.premium = serverInfo["IsPremium"].toBool(); - info.lastSeen = QDate::currentDate(); - info.preferred = 0; - // other keys: ConnectedUsers, Country, URI - servers.append(info); +void MainWindow::updateAvailable() +{ + for (QAction *action : ui->toolBar->actions()) { + if (action->text() == tr("Update")) { + action->setEnabled(true); + action->setToolTip(tr("Update available")); + break; } - m_OrganizerCore.settings().updateServers(servers); + } } -void MainWindow::nxmRequestFailed(int modID, int, QVariant, int, const QString& errorString) { - if (modID == -1) { - // must be the update-check that failed - m_ModsToUpdate = 0; - statusBar()->hide(); - } - MessageDialog::showMessage(tr("Request to Nexus failed: %1").arg(errorString), this); -} - -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; - QString errorFile; - - for (unsigned int i = 0; i < folder->getNumFiles(); ++i) { - BSA::File::Ptr file = folder->getFile(i); - BSA::EErrorCode res = archive.extract(file, destination.toUtf8().constData()); - if (res != BSA::ERROR_NONE) { - reportError(tr("failed to read %1: %2").arg(file->getName().c_str()).arg(res)); - result = res; - } - progress.setLabelText(file->getName().c_str()); - progress.setValue(progress.value() + 1); - QCoreApplication::processEvents(); - if (progress.wasCanceled()) { - result = BSA::ERROR_CANCELED; - } - } - 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) { - return result; - } +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 + if (m_StartTime.secsTo(QTime::currentTime()) < 5) { + uint hash = qHash(motd); + if (hash != m_OrganizerCore.settings().getMotDHash()) { + MotDDialog dialog(motd); + dialog.exec(); + m_OrganizerCore.settings().setMotDHash(hash); } + } - 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); - if (res != BSA::ERROR_NONE) { - return res; - } - } - return BSA::ERROR_NONE; + ui->actionEndorseMO->setVisible(false); } -bool MainWindow::extractProgress(QProgressDialog& progress, int percentage, std::string fileName) { - progress.setLabelText(fileName.c_str()); - progress.setValue(percentage); - QCoreApplication::processEvents(); - return !progress.wasCanceled(); + +void MainWindow::notEndorsedYet() +{ + ui->actionEndorseMO->setVisible(true); } -void MainWindow::extractBSATriggered() { - QTreeWidgetItem* item = m_ContextItem; - QString targetFolder = FileDialogMemory::getExistingDirectory("extractBSA", this, tr("Extract BSA")); - if (!targetFolder.isEmpty()) { - BSA::Archive archive; - QString originPath = QDir::fromNativeSeparators( - ToQString(m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(item->text(1))).getPath())); - QString archivePath = QString("%1\\%2").arg(originPath).arg(item->text(0)); +void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) +{ + QTreeWidget *dataTree = findChild("dataTree"); + m_ContextItem = dataTree->itemAt(pos.x(), pos.y()); - 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; - } + QMenu menu; + if ((m_ContextItem != nullptr) && (m_ContextItem->childCount() == 0)) { + menu.addAction(tr("Open/Execute"), this, SLOT(openDataFile())); + menu.addAction(tr("Add as Executable"), this, SLOT(addAsExecutable())); - QProgressDialog progress(this); - 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)); - if (result == BSA::ERROR_INVALIDHASHES) { - reportError(tr("This archive contains invalid hashes. Some files may be broken.")); - } + QString fileName = m_ContextItem->text(0); + if (m_PluginContainer.previewGenerator().previewSupported(QFileInfo(fileName).suffix())) { + menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); } -} -void MainWindow::displayColumnSelection(const QPoint& pos) { - QMenu menu; - - // display a list of all headers as checkboxes - QAbstractItemModel* model = ui->modList->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(!ui->modList->header()->isSectionHidden(i)); - QWidgetAction* checkableAction = new QWidgetAction(&menu); - checkableAction->setDefaultWidget(checkBox); - menu.addAction(checkableAction); + // offer to hide/unhide file, but not for files from archives + if (!m_ContextItem->data(0, Qt::UserRole + 1).toBool()) { + if (m_ContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) { + menu.addAction(tr("Un-Hide"), this, SLOT(unhideFile())); + } else { + menu.addAction(tr("Hide"), this, SLOT(hideFile())); + } } - menu.exec(pos); - // 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) { - ui->modList->header()->setSectionHidden(i, !checkBox->isChecked()); - } - } - ++i; - } -} + menu.addSeparator(); + } + menu.addAction(tr("Write To File..."), this, SLOT(writeDataToFile())); + menu.addAction(tr("Refresh"), this, SLOT(on_btnRefreshData_clicked())); -void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int) { - m_ArchiveListWriter.write(); - m_CheckBSATimer.start(500); + menu.exec(dataTree->mapToGlobal(pos)); } -void MainWindow::on_actionProblems_triggered() { - ProblemsDialog problems(m_PluginContainer.plugins(), this); - if (problems.hasProblems()) { - problems.exec(); - updateProblemsButton(); - } +void MainWindow::on_conflictsCheckBox_toggled(bool) +{ + refreshDataTree(); } -void MainWindow::on_actionChange_Game_triggered() { - if (QMessageBox::question(this, tr("Are you sure?"), tr("This will restart MO, continue?"), - QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) { - InstanceManager::instance().clearCurrentInstance(); - qApp->exit(INT_MAX); - } -} -void MainWindow::setCategoryListVisible(bool visible) { - if (visible) { - ui->categoriesGroup->show(); - ui->displayCategoriesBtn->setText(ToQString(L"\u00ab")); - } else { - ui->categoriesGroup->hide(); - ui->displayCategoriesBtn->setText(ToQString(L"\u00bb")); - } +void MainWindow::on_actionUpdate_triggered() +{ + m_OrganizerCore.startMOUpdate(); } -void MainWindow::on_displayCategoriesBtn_toggled(bool checked) { setCategoryListVisible(checked); } -void MainWindow::editCategories() { - CategoriesDialog dialog(this); - if (dialog.exec() == QDialog::Accepted) { - dialog.commitChanges(); - } +void MainWindow::on_actionEndorseMO_triggered() +{ + if (QMessageBox::question(this, tr("Endorse Mod Organizer"), + tr("Do you want to endorse Mod Organizer on %1 now?").arg( + NexusInterface::instance()->getGameURL()), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + NexusInterface::instance()->requestToggleEndorsement( + m_OrganizerCore.managedGame()->nexusModOrganizerID(), true, this, QVariant(), QString()); + } } -void MainWindow::deselectFilters() { ui->categoriesList->clearSelection(); } -void MainWindow::on_categoriesList_customContextMenuRequested(const QPoint& pos) { - QMenu menu; - menu.addAction(tr("Edit Categories..."), this, SLOT(editCategories())); - menu.addAction(tr("Deselect filter"), this, SLOT(deselectFilters())); +void MainWindow::updateDownloadListDelegate() +{ + if (m_OrganizerCore.settings().compactDownloads()) { + ui->downloadView->setItemDelegate( + new DownloadListWidgetCompactDelegate(m_OrganizerCore.downloadManager(), + m_OrganizerCore.settings().metaDownloads(), + ui->downloadView, + ui->downloadView)); + } else { + ui->downloadView->setItemDelegate( + new DownloadListWidgetDelegate(m_OrganizerCore.downloadManager(), + m_OrganizerCore.settings().metaDownloads(), + ui->downloadView, + ui->downloadView)); + } + + DownloadListSortProxy *sortProxy = new DownloadListSortProxy(m_OrganizerCore.downloadManager(), ui->downloadView); + sortProxy->setSourceModel(new DownloadList(m_OrganizerCore.downloadManager(), ui->downloadView)); + connect(ui->downloadFilterEdit, SIGNAL(textChanged(QString)), sortProxy, SLOT(updateFilter(QString))); + connect(ui->downloadFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(downloadFilterChanged(QString))); - menu.exec(ui->categoriesList->mapToGlobal(pos)); + ui->downloadView->setModel(sortProxy); + ui->downloadView->sortByColumn(1, Qt::DescendingOrder); + ui->downloadView->header()->resizeSections(QHeaderView::Fixed); + + connect(ui->downloadView->itemDelegate(), SIGNAL(installDownload(int)), &m_OrganizerCore, SLOT(installDownload(int))); + connect(ui->downloadView->itemDelegate(), SIGNAL(queryInfo(int)), m_OrganizerCore.downloadManager(), SLOT(queryInfo(int))); + connect(ui->downloadView->itemDelegate(), SIGNAL(removeDownload(int, bool)), m_OrganizerCore.downloadManager(), SLOT(removeDownload(int, bool))); + connect(ui->downloadView->itemDelegate(), SIGNAL(restoreDownload(int)), m_OrganizerCore.downloadManager(), SLOT(restoreDownload(int))); + connect(ui->downloadView->itemDelegate(), SIGNAL(cancelDownload(int)), m_OrganizerCore.downloadManager(), SLOT(cancelDownload(int))); + connect(ui->downloadView->itemDelegate(), SIGNAL(pauseDownload(int)), m_OrganizerCore.downloadManager(), SLOT(pauseDownload(int))); + connect(ui->downloadView->itemDelegate(), SIGNAL(resumeDownload(int)), this, SLOT(resumeDownload(int))); } -void MainWindow::updateESPLock(bool locked) { - QItemSelection currentSelection = ui->espList->selectionModel()->selection(); - if (currentSelection.count() == 0) { - // this path is probably useless - m_OrganizerCore.pluginList()->lockESPIndex(m_ContextRow, locked); + +void MainWindow::modDetailsUpdated(bool) +{ + if (--m_ModsToUpdate == 0) { + statusBar()->hide(); + m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); + for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { + if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { + ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); + break; + } + } + m_RefreshProgress->setVisible(false); + } else { + m_RefreshProgress->setValue(m_RefreshProgress->maximum() - m_ModsToUpdate); + } +} + +void MainWindow::nxmUpdatesAvailable(const std::vector &modIDs, QVariant userData, QVariant resultData, int) +{ + m_ModsToUpdate -= static_cast(modIDs.size()); + QVariantList resultList = resultData.toList(); + for (auto iter = resultList.begin(); iter != resultList.end(); ++iter) { + QVariantMap result = iter->toMap(); + if (result["id"].toInt() == m_OrganizerCore.managedGame()->nexusModOrganizerID()) { + if (!result["voted_by_user"].toBool()) { + ui->actionEndorseMO->setVisible(true); + } } else { - Q_FOREACH (const QModelIndex& idx, currentSelection.indexes()) { - m_OrganizerCore.pluginList()->lockESPIndex(mapToModel(m_OrganizerCore.pluginList(), idx).row(), locked); + std::vector info = ModInfo::getByModID(result["id"].toInt()); + for (auto iter = info.begin(); iter != info.end(); ++iter) { + (*iter)->setNewestVersion(result["version"].toString()); + (*iter)->setNexusDescription(result["description"].toString()); + if (NexusInterface::instance()->getAccessManager()->loggedIn() && + result.contains("voted_by_user")) { + // don't use endorsement info if we're not logged in or if the response doesn't contain it + (*iter)->setIsEndorsed(result["voted_by_user"].toBool()); } + } + } + } + + if (m_ModsToUpdate <= 0) { + statusBar()->hide(); + m_ModListSortProxy->setCategoryFilter(boost::assign::list_of(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE)); + for (int i = 0; i < ui->categoriesList->topLevelItemCount(); ++i) { + if (ui->categoriesList->topLevelItem(i)->data(0, Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) { + ui->categoriesList->setCurrentItem(ui->categoriesList->topLevelItem(i)); + break; + } } + } else { + m_RefreshProgress->setValue(m_RefreshProgress->maximum() - m_ModsToUpdate); + } } -void MainWindow::lockESPIndex() { updateESPLock(true); } +void MainWindow::nxmEndorsementToggled(int, QVariant, QVariant resultData, int) +{ + if (resultData.toBool()) { + ui->actionEndorseMO->setVisible(false); + QMessageBox::question(this, tr("Thank you!"), tr("Thank you for your endorsement!")); + } + + if (!disconnect(sender(), SIGNAL(nxmEndorsementToggled(int, QVariant, QVariant, int)), + this, SLOT(nxmEndorsementToggled(int, QVariant, QVariant, int)))) { + qCritical("failed to disconnect endorsement slot"); + } +} + +void MainWindow::nxmDownloadURLs(int, int, QVariant, QVariant resultData, int) +{ + QVariantList serverList = resultData.toList(); + + QList servers; + for (const QVariant &server : serverList) { + QVariantMap serverInfo = server.toMap(); + ServerInfo info; + info.name = serverInfo["Name"].toString(); + info.premium = serverInfo["IsPremium"].toBool(); + info.lastSeen = QDate::currentDate(); + info.preferred = 0; + // other keys: ConnectedUsers, Country, URI + servers.append(info); + } + m_OrganizerCore.settings().updateServers(servers); +} + + +void MainWindow::nxmRequestFailed(int modID, int, QVariant, int, const QString &errorString) +{ + if (modID == -1) { + // must be the update-check that failed + m_ModsToUpdate = 0; + statusBar()->hide(); + } + MessageDialog::showMessage(tr("Request to Nexus failed: %1").arg(errorString), this); +} -void MainWindow::unlockESPIndex() { updateESPLock(false); } -void MainWindow::removeFromToolbar() { - try { - Executable& exe = m_OrganizerCore.executablesList()->find(m_ContextAction->text()); - exe.showOnToolbar(false); - } catch (const std::runtime_error&) { - qDebug("executable doesn't exist any more"); +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; + QString errorFile; + + for (unsigned int i = 0; i < folder->getNumFiles(); ++i) { + BSA::File::Ptr file = folder->getFile(i); + BSA::EErrorCode res = archive.extract(file, destination.toUtf8().constData()); + if (res != BSA::ERROR_NONE) { + reportError(tr("failed to read %1: %2").arg(file->getName().c_str()).arg(res)); + result = res; + } + progress.setLabelText(file->getName().c_str()); + progress.setValue(progress.value() + 1); + QCoreApplication::processEvents(); + if (progress.wasCanceled()) { + result = BSA::ERROR_CANCELED; } + } - updateToolBar(); -} + 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) { + return result; + } + } -void MainWindow::toolBar_customContextMenuRequested(const QPoint& point) { - QAction* action = ui->toolBar->actionAt(point); - if (action != nullptr) { - if (action->objectName().startsWith("custom_")) { - m_ContextAction = action; - QMenu menu; - menu.addAction(tr("Remove"), this, SLOT(removeFromToolbar())); - menu.exec(ui->toolBar->mapToGlobal(point)); - } + 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); + if (res != BSA::ERROR_NONE) { + return res; } + } + return BSA::ERROR_NONE; } -void MainWindow::on_espList_customContextMenuRequested(const QPoint& pos) { - m_ContextRow = m_PluginListSortProxy->mapToSource(ui->espList->indexAt(pos)).row(); - QMenu menu; - menu.addAction(tr("Enable all"), m_OrganizerCore.pluginList(), SLOT(enableAll())); - menu.addAction(tr("Disable all"), m_OrganizerCore.pluginList(), SLOT(disableAll())); +bool MainWindow::extractProgress(QProgressDialog &progress, int percentage, std::string fileName) +{ + progress.setLabelText(fileName.c_str()); + progress.setValue(percentage); + QCoreApplication::processEvents(); + return !progress.wasCanceled(); +} - QItemSelection currentSelection = ui->espList->selectionModel()->selection(); - bool hasLocked = false; - bool hasUnlocked = false; - for (const QModelIndex& idx : currentSelection.indexes()) { - int row = m_PluginListSortProxy->mapToSource(idx).row(); - if (m_OrganizerCore.pluginList()->isEnabled(row)) { - if (m_OrganizerCore.pluginList()->isESPLocked(row)) { - hasLocked = true; - } else { - hasUnlocked = true; - } - } - } - menu.addSeparator(); +void MainWindow::extractBSATriggered() +{ + QTreeWidgetItem *item = m_ContextItem; - if (hasLocked) { - menu.addAction(tr("Unlock load order"), this, SLOT(unlockESPIndex())); - } - if (hasUnlocked) { - menu.addAction(tr("Lock load order"), this, SLOT(lockESPIndex())); + QString targetFolder = FileDialogMemory::getExistingDirectory("extractBSA", this, tr("Extract BSA")); + if (!targetFolder.isEmpty()) { + BSA::Archive archive; + QString originPath = QDir::fromNativeSeparators(ToQString(m_OrganizerCore.directoryStructure()->getOriginByName(ToWString(item->text(1))).getPath())); + QString archivePath = QString("%1\\%2").arg(originPath).arg(item->text(0)); + + 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; } - try { - menu.exec(ui->espList->mapToGlobal(pos)); - } catch (const std::exception& e) { - reportError(tr("Exception: ").arg(e.what())); - } catch (...) { - reportError(tr("Unknown exception")); + QProgressDialog progress(this); + 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)); + if (result == BSA::ERROR_INVALIDHASHES) { + reportError(tr("This archive contains invalid hashes. Some files may be broken.")); } + } } -void MainWindow::on_groupCombo_currentIndexChanged(int index) { - if (m_ModListSortProxy == nullptr) { - return; - } - QAbstractProxyModel* newModel = nullptr; - switch (index) { - case 1: { - newModel = new QtGroupingProxy(m_OrganizerCore.modList(), QModelIndex(), ModList::COL_CATEGORY, Qt::UserRole, 0, - Qt::UserRole + 2); - } break; - case 2: { - newModel = - new QtGroupingProxy(m_OrganizerCore.modList(), QModelIndex(), ModList::COL_MODID, Qt::DisplayRole, - QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, Qt::UserRole + 2); - } break; - default: { newModel = nullptr; } break; - } - if (newModel != nullptr) { -#ifdef TEST_MODELS - new ModelTest(newModel, this); -#endif // TEST_MODELS - m_ModListSortProxy->setSourceModel(newModel); - connect(ui->modList, SIGNAL(expanded(QModelIndex)), newModel, SLOT(expanded(QModelIndex))); - connect(ui->modList, SIGNAL(collapsed(QModelIndex)), newModel, SLOT(collapsed(QModelIndex))); - connect(newModel, SIGNAL(expandItem(QModelIndex)), this, SLOT(expandModList(QModelIndex))); - } else { - m_ModListSortProxy->setSourceModel(m_OrganizerCore.modList()); +void MainWindow::displayColumnSelection(const QPoint &pos) +{ + QMenu menu; + + // display a list of all headers as checkboxes + QAbstractItemModel *model = ui->modList->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(!ui->modList->header()->isSectionHidden(i)); + QWidgetAction *checkableAction = new QWidgetAction(&menu); + checkableAction->setDefaultWidget(checkBox); + menu.addAction(checkableAction); + } + menu.exec(pos); + + // 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) { + ui->modList->header()->setSectionHidden(i, !checkBox->isChecked()); + } } - modFilterActive(m_ModListSortProxy->isFilterActive()); + ++i; + } } -const Executable& MainWindow::getSelectedExecutable() const { - QString name = ui->executablesListBox->itemText(ui->executablesListBox->currentIndex()); - return m_OrganizerCore.executablesList()->find(name); +void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int) +{ + m_ArchiveListWriter.write(); + m_CheckBSATimer.start(500); } -Executable& MainWindow::getSelectedExecutable() { - QString name = ui->executablesListBox->itemText(ui->executablesListBox->currentIndex()); - return m_OrganizerCore.executablesList()->find(name); +void MainWindow::on_actionProblems_triggered() +{ + ProblemsDialog problems(m_PluginContainer.plugins(), this); + if (problems.hasProblems()) { + problems.exec(); + updateProblemsButton(); + } } -void MainWindow::on_linkButton_pressed() { - const Executable& selectedExecutable(getSelectedExecutable()); +void MainWindow::on_actionChange_Game_triggered() +{ + if (QMessageBox::question(this, tr("Are you sure?"), + tr("This will restart MO, continue?"), + QMessageBox::Yes | QMessageBox::Cancel) + == QMessageBox::Yes) { + InstanceManager::instance().clearCurrentInstance(); + qApp->exit(INT_MAX); + } +} - const QIcon addIcon(":/MO/gui/link"); - const QIcon removeIcon(":/MO/gui/remove"); +void MainWindow::setCategoryListVisible(bool visible) +{ + if (visible) { + ui->categoriesGroup->show(); + ui->displayCategoriesBtn->setText(ToQString(L"\u00ab")); + } else { + ui->categoriesGroup->hide(); + ui->displayCategoriesBtn->setText(ToQString(L"\u00bb")); + } +} - const QFileInfo linkDesktopFile(getDesktopLinkfile(selectedExecutable)); - const QFileInfo linkMenuFile(getStartMenuLinkfile(selectedExecutable)); +void MainWindow::on_displayCategoriesBtn_toggled(bool checked) +{ + setCategoryListVisible(checked); +} - ui->linkButton->menu() - ->actions() - .at(static_cast(ShortcutType::Toolbar)) - ->setIcon(selectedExecutable.isShownOnToolbar() ? removeIcon : addIcon); - ui->linkButton->menu() - ->actions() - .at(static_cast(ShortcutType::Desktop)) - ->setIcon(linkDesktopFile.exists() ? removeIcon : addIcon); - ui->linkButton->menu() - ->actions() - .at(static_cast(ShortcutType::StartMenu)) - ->setIcon(linkMenuFile.exists() ? removeIcon : addIcon); +void MainWindow::editCategories() +{ + CategoriesDialog dialog(this); + if (dialog.exec() == QDialog::Accepted) { + dialog.commitChanges(); + } } -void MainWindow::on_showHiddenBox_toggled(bool checked) { m_OrganizerCore.downloadManager()->setShowHidden(checked); } +void MainWindow::deselectFilters() +{ + ui->categoriesList->clearSelection(); +} -void MainWindow::createStdoutPipe(HANDLE* stdOutRead, HANDLE* stdOutWrite) { - SECURITY_ATTRIBUTES secAttributes; - secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); - secAttributes.bInheritHandle = TRUE; - secAttributes.lpSecurityDescriptor = nullptr; +void MainWindow::on_categoriesList_customContextMenuRequested(const QPoint &pos) +{ + QMenu menu; + menu.addAction(tr("Edit Categories..."), this, SLOT(editCategories())); + menu.addAction(tr("Deselect filter"), this, SLOT(deselectFilters())); + + menu.exec(ui->categoriesList->mapToGlobal(pos)); +} - if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) { - qCritical("failed to create stdout reroute"); - } - if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) { - qCritical("failed to correctly set up the stdout reroute"); - *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE; +void MainWindow::updateESPLock(bool locked) +{ + QItemSelection currentSelection = ui->espList->selectionModel()->selection(); + if (currentSelection.count() == 0) { + // this path is probably useless + m_OrganizerCore.pluginList()->lockESPIndex(m_ContextRow, locked); + } else { + Q_FOREACH (const QModelIndex &idx, currentSelection.indexes()) { + m_OrganizerCore.pluginList()->lockESPIndex(mapToModel(m_OrganizerCore.pluginList(), idx).row(), locked); } + } } -std::string MainWindow::readFromPipe(HANDLE stdOutRead) { - static const int chunkSize = 128; - std::string result; - char buffer[chunkSize + 1]; - buffer[chunkSize] = '\0'; +void MainWindow::lockESPIndex() +{ + updateESPLock(true); +} - DWORD read = 1; - while (read > 0) { - if (!::ReadFile(stdOutRead, buffer, chunkSize, &read, nullptr)) { - break; - } - if (read > 0) { - result.append(buffer, read); - if (read < chunkSize) { - break; - } - } - } - return result; +void MainWindow::unlockESPIndex() +{ + updateESPLock(false); } -void MainWindow::processLOOTOut(const std::string& lootOut, std::string& errorMessages, QProgressDialog& dialog) { - std::vector lines; - boost::split(lines, lootOut, boost::is_any_of("\r\n")); - std::tr1::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); - std::tr1::regex exIncompatible("\"([^\"]*)\" is incompatible with \"([^\"]*)\", but both are present\\."); +void MainWindow::removeFromToolbar() +{ + try { + Executable &exe = m_OrganizerCore.executablesList()->find(m_ContextAction->text()); + exe.showOnToolbar(false); + } catch (const std::runtime_error&) { + qDebug("executable doesn't exist any more"); + } - for (const std::string& line : lines) { - if (line.length() > 0) { - size_t progidx = line.find("[progress]"); - size_t erroridx = line.find("[error]"); - if (progidx != std::string::npos) { - dialog.setLabelText(line.substr(progidx + 11).c_str()); - } else if (erroridx != std::string::npos) { - qWarning("%s", line.c_str()); - errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); - } else { - std::tr1::smatch match; - if (std::tr1::regex_match(line, match, exRequires)) { - std::string modName(match[1].first, match[1].second); - std::string dependency(match[2].first, match[2].second); - m_OrganizerCore.pluginList()->addInformation( - modName.c_str(), tr("depends on missing \"%1\"").arg(dependency.c_str())); - } else if (std::tr1::regex_match(line, match, exIncompatible)) { - std::string modName(match[1].first, match[1].second); - std::string dependency(match[2].first, match[2].second); - m_OrganizerCore.pluginList()->addInformation( - modName.c_str(), tr("incompatible with \"%1\"").arg(dependency.c_str())); - } else { - qDebug("[loot] %s", line.c_str()); - } - } - } - } + updateToolBar(); } -void MainWindow::on_bossButton_clicked() { - std::string reportURL; - std::string errorMessages; - // m_OrganizerCore.currentProfile()->writeModlistNow(); - m_OrganizerCore.savePluginList(); - // Create a backup of the load orders w/ LOOT in name - // to make sure that any sorting is easily undo-able. - // Need to figure out how I want to do that. +void MainWindow::toolBar_customContextMenuRequested(const QPoint &point) +{ + QAction *action = ui->toolBar->actionAt(point); + if (action != nullptr) { + if (action->objectName().startsWith("custom_")) { + m_ContextAction = action; + QMenu menu; + menu.addAction(tr("Remove"), this, SLOT(removeFromToolbar())); + menu.exec(ui->toolBar->mapToGlobal(point)); + } + } +} - bool success = false; +void MainWindow::on_espList_customContextMenuRequested(const QPoint &pos) +{ + m_ContextRow = m_PluginListSortProxy->mapToSource(ui->espList->indexAt(pos)).row(); - try { - setEnabled(false); - ON_BLOCK_EXIT([&]() { setEnabled(true); }); - QProgressDialog dialog(this); - dialog.setLabelText(tr("Please wait while LOOT is running")); - dialog.setMaximum(0); - dialog.show(); - - QString outPath = QDir::temp().absoluteFilePath("lootreport.json"); - - QStringList parameters; - parameters << "--game" << m_OrganizerCore.managedGame()->gameShortName() << "--gamePath" - << QString("\"%1\"").arg(m_OrganizerCore.managedGame()->gameDirectory().absolutePath()) - << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(m_OrganizerCore.profilePath()) - << "--out" << QString("\"%1\"").arg(outPath); - - if (m_DidUpdateMasterList) { - parameters << "--skipUpdateMasterlist"; - } - HANDLE stdOutWrite = INVALID_HANDLE_VALUE; - HANDLE stdOutRead = INVALID_HANDLE_VALUE; - createStdoutPipe(&stdOutRead, &stdOutWrite); - try { - m_OrganizerCore.prepareVFS(); - } catch (const std::exception& e) { - QMessageBox::warning(qApp->activeWindow(), tr("Error"), e.what()); - return; - } + QMenu menu; + menu.addAction(tr("Enable all"), m_OrganizerCore.pluginList(), SLOT(enableAll())); + menu.addAction(tr("Disable all"), m_OrganizerCore.pluginList(), SLOT(disableAll())); - HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), parameters.join(" "), - qApp->applicationDirPath() + "/loot", true, stdOutWrite); - - // we don't use the write end - ::CloseHandle(stdOutWrite); - - m_OrganizerCore.pluginList()->clearAdditionalInformation(); - - DWORD retLen; - JOBOBJECT_BASIC_PROCESS_ID_LIST info; - HANDLE processHandle = loot; - - if (loot != INVALID_HANDLE_VALUE) { - bool isJobHandle = true; - ULONG lastProcessID; - DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); - while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { - if (isJobHandle) { - if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > - 0) { - if (info.NumberOfProcessIdsInList == 0) { - qDebug("no more processes in job"); - break; - } else { - if (lastProcessID != info.ProcessIdList[0]) { - lastProcessID = info.ProcessIdList[0]; - if (processHandle != loot) { - ::CloseHandle(processHandle); - } - processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID); - } - } - } else { - // the info-object I passed only provides space for 1 process id. but since this code only cares - // about whether there is more than one that's good enough. ERROR_MORE_DATA simply signals there - // are at least two processes running. any other error probably means the handle is a regular - // process handle, probably caused by running MO in a job without the right to break out. - if (::GetLastError() != ERROR_MORE_DATA) { - isJobHandle = false; - } - } - } + QItemSelection currentSelection = ui->espList->selectionModel()->selection(); + bool hasLocked = false; + bool hasUnlocked = false; + for (const QModelIndex &idx : currentSelection.indexes()) { + int row = m_PluginListSortProxy->mapToSource(idx).row(); + if (m_OrganizerCore.pluginList()->isEnabled(row)) { + if (m_OrganizerCore.pluginList()->isESPLocked(row)) { + hasLocked = true; + } else { + hasUnlocked = true; + } + } + } + + menu.addSeparator(); + + if (hasLocked) { + menu.addAction(tr("Unlock load order"), this, SLOT(unlockESPIndex())); + } + if (hasUnlocked) { + menu.addAction(tr("Lock load order"), this, SLOT(lockESPIndex())); + } + + try { + menu.exec(ui->espList->mapToGlobal(pos)); + } catch (const std::exception &e) { + reportError(tr("Exception: ").arg(e.what())); + } catch (...) { + reportError(tr("Unknown exception")); + } +} + +void MainWindow::on_groupCombo_currentIndexChanged(int index) +{ + if (m_ModListSortProxy == nullptr) { + return; + } + QAbstractProxyModel *newModel = nullptr; + switch (index) { + case 1: { + newModel = new QtGroupingProxy(m_OrganizerCore.modList(), QModelIndex(), ModList::COL_CATEGORY, Qt::UserRole, + 0, Qt::UserRole + 2); + } break; + case 2: { + newModel = new QtGroupingProxy(m_OrganizerCore.modList(), QModelIndex(), ModList::COL_MODID, Qt::DisplayRole, + QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, + Qt::UserRole + 2); + } break; + default: { + newModel = nullptr; + } break; + } - if (dialog.wasCanceled()) { - if (isJobHandle) { - ::TerminateJobObject(loot, 1); - } else { - ::TerminateProcess(loot, 1); - } - } + if (newModel != nullptr) { +#ifdef TEST_MODELS + new ModelTest(newModel, this); +#endif // TEST_MODELS + m_ModListSortProxy->setSourceModel(newModel); + connect(ui->modList, SIGNAL(expanded(QModelIndex)),newModel, SLOT(expanded(QModelIndex))); + connect(ui->modList, SIGNAL(collapsed(QModelIndex)), newModel, SLOT(collapsed(QModelIndex))); + connect(newModel, SIGNAL(expandItem(QModelIndex)), this, SLOT(expandModList(QModelIndex))); + } else { + m_ModListSortProxy->setSourceModel(m_OrganizerCore.modList()); + } + modFilterActive(m_ModListSortProxy->isFilterActive()); +} - // keep processing events so the app doesn't appear dead - QCoreApplication::processEvents(); - std::string lootOut = readFromPipe(stdOutRead); - processLOOTOut(lootOut, errorMessages, dialog); +const Executable &MainWindow::getSelectedExecutable() const +{ + QString name = ui->executablesListBox->itemText(ui->executablesListBox->currentIndex()); + return m_OrganizerCore.executablesList()->find(name); +} - res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); - } +Executable &MainWindow::getSelectedExecutable() +{ + QString name = ui->executablesListBox->itemText(ui->executablesListBox->currentIndex()); + return m_OrganizerCore.executablesList()->find(name); +} - std::string remainder = readFromPipe(stdOutRead).c_str(); - if (remainder.length() > 0) { - processLOOTOut(remainder, errorMessages, dialog); - } - DWORD exitCode = 0UL; - ::GetExitCodeProcess(processHandle, &exitCode); - ::CloseHandle(processHandle); - if (exitCode != 0UL) { - reportError(tr("loot failed. Exit code was: %1").arg(exitCode)); - return; - } else { - success = true; - QFile outFile(outPath); - outFile.open(QIODevice::ReadOnly); - QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll()); - QJsonArray array = doc.array(); - for (auto iter = array.begin(); iter != array.end(); ++iter) { - QJsonObject pluginObj = (*iter).toObject(); - QJsonArray pluginMessages = pluginObj["messages"].toArray(); - for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { - QJsonObject msg = (*msgIter).toObject(); - m_OrganizerCore.pluginList()->addInformation( - pluginObj["name"].toString(), - QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); - } - if (pluginObj["dirty"].toString() == "yes") - m_OrganizerCore.pluginList()->addInformation(pluginObj["name"].toString(), "dirty"); - } - } - } else { - reportError(tr("failed to start loot")); - } - } catch (const std::exception& e) { - reportError(tr("failed to run loot: %1").arg(e.what())); - } +void MainWindow::on_linkButton_pressed() +{ + const Executable &selectedExecutable(getSelectedExecutable()); - if (errorMessages.length() > 0) { - QMessageBox* warn = - new QMessageBox(QMessageBox::Warning, tr("Errors occured"), errorMessages.c_str(), QMessageBox::Ok, this); - warn->setModal(false); - warn->show(); - } + const QIcon addIcon(":/MO/gui/link"); + const QIcon removeIcon(":/MO/gui/remove"); - if (success) { - m_DidUpdateMasterList = true; - /*if (reportURL.length() > 0) { - m_IntegratedBrowser.setWindowTitle("LOOT Report"); - QString report(reportURL.c_str()); - QStringList temp = report.split("?"); - QUrl url = QUrl::fromLocalFile(temp.at(0)); - if (temp.size() > 1) { - url.setQuery(temp.at(1).toUtf8()); - } - m_IntegratedBrowser.openUrl(url); - }*/ - - // if the game specifies load order by file time, our own load order file needs to be removed because it's - // outdated. refreshESPList will then use the file time as the load order. - if (m_OrganizerCore.managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { - qDebug("removing loadorder.txt"); - QFile::remove(m_OrganizerCore.currentProfile()->getLoadOrderFileName()); - } - m_OrganizerCore.refreshESPList(); - m_OrganizerCore.savePluginList(); - } -} + const QFileInfo linkDesktopFile(getDesktopLinkfile(selectedExecutable)); + const QFileInfo linkMenuFile(getStartMenuLinkfile(selectedExecutable)); -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"; + ui->linkButton->menu()->actions().at(static_cast(ShortcutType::Toolbar))->setIcon(selectedExecutable.isShownOnToolbar() ? removeIcon : addIcon); + ui->linkButton->menu()->actions().at(static_cast(ShortcutType::Desktop))->setIcon(linkDesktopFile.exists() ? removeIcon : addIcon); + ui->linkButton->menu()->actions().at(static_cast(ShortcutType::StartMenu))->setIcon(linkMenuFile.exists() ? removeIcon : addIcon); +} -bool MainWindow::createBackup(const QString& filePath, const QDateTime& time) { - QString outPath = filePath + "." + time.toString(PATTERN_BACKUP_DATE); - if (shellCopy(QStringList(filePath), QStringList(outPath), this)) { - QFileInfo fileInfo(filePath); - removeOldFiles(fileInfo.absolutePath(), fileInfo.fileName() + PATTERN_BACKUP_GLOB, 3, QDir::Name); - return true; - } else { - return false; - } +void MainWindow::on_showHiddenBox_toggled(bool checked) +{ + m_OrganizerCore.downloadManager()->setShowHidden(checked); } -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)) { - MessageDialog::showMessage(tr("Backup of load order created"), this); - } -} - -QString MainWindow::queryRestore(const QString& filePath) { - QFileInfo pluginFileInfo(filePath); - QString pattern = pluginFileInfo.fileName() + ".*"; - QFileInfoList files = pluginFileInfo.absoluteDir().entryInfoList(QStringList(pattern), QDir::Files, QDir::Name); - - SelectionDialog dialog(tr("Choose backup to restore"), this); - QRegExp exp(pluginFileInfo.fileName() + PATTERN_BACKUP_REGEX); - QRegExp exp2(pluginFileInfo.fileName() + "\\.(.*)"); - for (const QFileInfo& info : files) { - if (exp.exactMatch(info.fileName())) { - QDateTime time = QDateTime::fromString(exp.cap(1), PATTERN_BACKUP_DATE); - dialog.addChoice(time.toString(), "", exp.cap(1)); - } else if (exp2.exactMatch(info.fileName())) { - dialog.addChoice(exp2.cap(1), "", exp2.cap(1)); - } - } - if (dialog.numChoices() == 0) { - QMessageBox::information(this, tr("No Backups"), tr("There are no backups to restore")); - return QString(); - } +void MainWindow::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) +{ + SECURITY_ATTRIBUTES secAttributes; + secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); + secAttributes.bInheritHandle = TRUE; + secAttributes.lpSecurityDescriptor = nullptr; - if (dialog.exec() == QDialog::Accepted) { - return dialog.getChoiceData().toString(); - } else { - return QString(); - } -} - -void MainWindow::on_restoreButton_clicked() { - QString pluginName = m_OrganizerCore.currentProfile()->getPluginsFileName(); - 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) || - !shellCopy(loadOrderName + "." + choice, loadOrderName, true, this) || - !shellCopy(lockedName + "." + choice, lockedName, true, this)) { - QMessageBox::critical( - this, tr("Restore failed"), - tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); - } - m_OrganizerCore.refreshESPList(); - } -} + if (!::CreatePipe(stdOutRead, stdOutWrite, &secAttributes, 0)) { + qCritical("failed to create stdout reroute"); + } -void MainWindow::on_saveModsButton_clicked() { - m_OrganizerCore.currentProfile()->writeModlistNow(true); - QDateTime now = QDateTime::currentDateTime(); - if (createBackup(m_OrganizerCore.currentProfile()->getModlistFileName(), now)) { - MessageDialog::showMessage(tr("Backup of modlist created"), this); - } + if (!::SetHandleInformation(*stdOutRead, HANDLE_FLAG_INHERIT, 0)) { + qCritical("failed to correctly set up the stdout reroute"); + *stdOutWrite = *stdOutRead = INVALID_HANDLE_VALUE; + } } -void MainWindow::on_restoreModsButton_clicked() { - QString modlistName = m_OrganizerCore.currentProfile()->getModlistFileName(); - QString choice = queryRestore(modlistName); - if (!choice.isEmpty()) { - if (!shellCopy(modlistName + "." + choice, modlistName, true, this)) { - QMessageBox::critical( - this, tr("Restore failed"), - tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); - } - m_OrganizerCore.refreshModList(false); - } -} +std::string MainWindow::readFromPipe(HANDLE stdOutRead) +{ + static const int chunkSize = 128; + std::string result; -void MainWindow::on_actionCopy_Log_to_Clipboard_triggered() { - QStringList lines; - QAbstractItemModel* model = ui->logList->model(); - for (int i = 0; i < model->rowCount(); ++i) { - lines.append(QString("%1 [%2] %3") - .arg(model->index(i, 0).data().toString()) - .arg(model->index(i, 1).data(Qt::UserRole).toString()) - .arg(model->index(i, 1).data().toString())); - } - QApplication::clipboard()->setText(lines.join("\n")); -} + char buffer[chunkSize + 1]; + buffer[chunkSize] = '\0'; -void MainWindow::on_categoriesAndBtn_toggled(bool checked) { - if (checked) { - m_ModListSortProxy->setFilterMode(ModListSortProxy::FILTER_AND); + DWORD read = 1; + while (read > 0) { + if (!::ReadFile(stdOutRead, buffer, chunkSize, &read, nullptr)) { + break; } -} - -void MainWindow::on_categoriesOrBtn_toggled(bool checked) { - if (checked) { - m_ModListSortProxy->setFilterMode(ModListSortProxy::FILTER_OR); + if (read > 0) { + result.append(buffer, read); + if (read < chunkSize) { + break; + } } + } + return result; } -void MainWindow::on_managedArchiveLabel_linkHovered(const QString&) { - QToolTip::showText(QCursor::pos(), ui->managedArchiveLabel->toolTip()); +void MainWindow::processLOOTOut(const std::string &lootOut, std::string &errorMessages, QProgressDialog &dialog) +{ + std::vector lines; + boost::split(lines, lootOut, boost::is_any_of("\r\n")); + + std::tr1::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); + std::tr1::regex exIncompatible("\"([^\"]*)\" is incompatible with \"([^\"]*)\", but both are present\\."); + + for (const std::string &line : lines) { + if (line.length() > 0) { + size_t progidx = line.find("[progress]"); + size_t erroridx = line.find("[error]"); + if (progidx != std::string::npos) { + dialog.setLabelText(line.substr(progidx + 11).c_str()); + } else if (erroridx != std::string::npos) { + qWarning("%s", line.c_str()); + errorMessages.append(boost::algorithm::trim_copy(line.substr(erroridx + 8)) + "\n"); + } else { + std::tr1::smatch match; + if (std::tr1::regex_match(line, match, exRequires)) { + std::string modName(match[1].first, match[1].second); + std::string dependency(match[2].first, match[2].second); + m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("depends on missing \"%1\"").arg(dependency.c_str())); + } else if (std::tr1::regex_match(line, match, exIncompatible)) { + std::string modName(match[1].first, match[1].second); + std::string dependency(match[2].first, match[2].second); + m_OrganizerCore.pluginList()->addInformation(modName.c_str(), tr("incompatible with \"%1\"").arg(dependency.c_str())); + } else { + qDebug("[loot] %s", line.c_str()); + } + } + } + } } -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). - if (ui->downloadTab->isVisible() && - (event->proposedAction() == Qt::CopyAction || event->proposedAction() == Qt::MoveAction) && - event->answerRect().intersects(ui->downloadTab->rect())) { +void MainWindow::on_bossButton_clicked() +{ + std::string reportURL; + std::string errorMessages; - // 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(); + //m_OrganizerCore.currentProfile()->writeModlistNow(); + m_OrganizerCore.savePluginList(); + //Create a backup of the load orders w/ LOOT in name + //to make sure that any sorting is easily undo-able. + //Need to figure out how I want to do that. - if (data->hasUrls()) { - QStringList extensions = m_OrganizerCore.installationManager()->getSupportedExtensions(); + bool success = false; - // 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) { - if (url.isLocalFile()) { - QString local = url.toLocalFile(); - bool fok = false; - for (auto ext : extensions) { - if (local.endsWith(ext, Qt::CaseInsensitive)) { - fok = true; - break; - } - } - if (!fok) { - ok = false; - break; - } + try { + setEnabled(false); + ON_BLOCK_EXIT([&] () { setEnabled(true); }); + QProgressDialog dialog(this); + dialog.setLabelText(tr("Please wait while LOOT is running")); + dialog.setMaximum(0); + dialog.show(); + + QString outPath = QDir::temp().absoluteFilePath("lootreport.json"); + + QStringList parameters; + parameters << "--game" << m_OrganizerCore.managedGame()->gameShortName() + << "--gamePath" << QString("\"%1\"").arg(m_OrganizerCore.managedGame()->gameDirectory().absolutePath()) + << "--pluginListPath" << QString("\"%1/loadorder.txt\"").arg(m_OrganizerCore.profilePath()) + << "--out" << QString("\"%1\"").arg(outPath); + + if (m_DidUpdateMasterList) { + parameters << "--skipUpdateMasterlist"; + } + HANDLE stdOutWrite = INVALID_HANDLE_VALUE; + HANDLE stdOutRead = INVALID_HANDLE_VALUE; + createStdoutPipe(&stdOutRead, &stdOutWrite); + try { + m_OrganizerCore.prepareVFS(); + } catch (const std::exception &e) { + QMessageBox::warning(qApp->activeWindow(), tr("Error"), e.what()); + return; + } + + HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), + parameters.join(" "), + qApp->applicationDirPath() + "/loot", + true, + stdOutWrite); + + // we don't use the write end + ::CloseHandle(stdOutWrite); + + m_OrganizerCore.pluginList()->clearAdditionalInformation(); + + DWORD retLen; + JOBOBJECT_BASIC_PROCESS_ID_LIST info; + HANDLE processHandle = loot; + + if (loot != INVALID_HANDLE_VALUE) { + bool isJobHandle = true; + ULONG lastProcessID; + DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); + while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0)) { + if (isJobHandle) { + if (::QueryInformationJobObject(loot, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { + if (info.NumberOfProcessIdsInList == 0) { + qDebug("no more processes in job"); + break; + } else { + if (lastProcessID != info.ProcessIdList[0]) { + lastProcessID = info.ProcessIdList[0]; + if (processHandle != loot) { + ::CloseHandle(processHandle); } + processHandle = ::OpenProcess(PROCESS_QUERY_INFORMATION, FALSE, lastProcessID); + } } - if (ok) { - event->accept(); + } else { + // the info-object I passed only provides space for 1 process id. but since this code only cares about whether there + // is more than one that's good enough. ERROR_MORE_DATA simply signals there are at least two processes running. + // any other error probably means the handle is a regular process handle, probably caused by running MO in a job without + // the right to break out. + if (::GetLastError() != ERROR_MORE_DATA) { + isJobHandle = false; } + } + } + + if (dialog.wasCanceled()) { + if (isJobHandle) { + ::TerminateJobObject(loot, 1); + } else { + ::TerminateProcess(loot, 1); + } } - } -} -void MainWindow::dropLocalFile(const QUrl& url, const QString& outputDir, bool move) { - QFileInfo file(url.toLocalFile()); - if (!file.exists()) { - qWarning("invalid source file %s", qPrintable(file.absoluteFilePath())); + // keep processing events so the app doesn't appear dead + QCoreApplication::processEvents(); + std::string lootOut = readFromPipe(stdOutRead); + processLOOTOut(lootOut, errorMessages, dialog); + + res = ::MsgWaitForMultipleObjects(1, &loot, false, 100, QS_KEY | QS_MOUSE); + } + + std::string remainder = readFromPipe(stdOutRead).c_str(); + if (remainder.length() > 0) { + processLOOTOut(remainder, errorMessages, dialog); + } + DWORD exitCode = 0UL; + ::GetExitCodeProcess(processHandle, &exitCode); + ::CloseHandle(processHandle); + if (exitCode != 0UL) { + reportError(tr("loot failed. Exit code was: %1").arg(exitCode)); return; - } - QString target = outputDir + "/" + file.fileName(); - if (QFile::exists(target)) { - 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); - box.addButton(tr("Rename new file"), QMessageBox::YesRole); - box.addButton(tr("Ignore file"), QMessageBox::RejectRole); - - 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; + } else { + success = true; + QFile outFile(outPath); + outFile.open(QIODevice::ReadOnly); + QJsonDocument doc = QJsonDocument::fromJson(outFile.readAll()); + QJsonArray array = doc.array(); + for (auto iter = array.begin(); iter != array.end(); ++iter) { + QJsonObject pluginObj = (*iter).toObject(); + QJsonArray pluginMessages = pluginObj["messages"].toArray(); + for (auto msgIter = pluginMessages.begin(); msgIter != pluginMessages.end(); ++msgIter) { + QJsonObject msg = (*msgIter).toObject(); + m_OrganizerCore.pluginList()->addInformation(pluginObj["name"].toString(), + QString("%1: %2").arg(msg["type"].toString(), msg["message"].toString())); + } + if (pluginObj["dirty"].toString() == "yes") + m_OrganizerCore.pluginList()->addInformation(pluginObj["name"].toString(), "dirty"); } - } - bool success = false; - if (move) { - success = shellMove(file.absoluteFilePath(), target, true, this); + } } else { - success = shellCopy(file.absoluteFilePath(), target, true, this); - } - if (!success) { - qCritical("file operation failed: %s", qPrintable(windowsErrorString(::GetLastError()))); - } + reportError(tr("failed to start loot")); + } + } catch (const std::exception &e) { + reportError(tr("failed to run loot: %1").arg(e.what())); + } + + if (errorMessages.length() > 0) { + QMessageBox *warn = new QMessageBox(QMessageBox::Warning, tr("Errors occured"), errorMessages.c_str(), QMessageBox::Ok, this); + warn->setModal(false); + warn->show(); + } + + if (success) { + m_DidUpdateMasterList = true; + /*if (reportURL.length() > 0) { + m_IntegratedBrowser.setWindowTitle("LOOT Report"); + QString report(reportURL.c_str()); + QStringList temp = report.split("?"); + QUrl url = QUrl::fromLocalFile(temp.at(0)); + if (temp.size() > 1) { + url.setQuery(temp.at(1).toUtf8()); + } + m_IntegratedBrowser.openUrl(url); + }*/ + + // if the game specifies load order by file time, our own load order file needs to be removed because it's outdated. + // refreshESPList will then use the file time as the load order. + if (m_OrganizerCore.managedGame()->loadOrderMechanism() == IPluginGame::LoadOrderMechanism::FileTime) { + qDebug("removing loadorder.txt"); + QFile::remove(m_OrganizerCore.currentProfile()->getLoadOrderFileName()); + } + m_OrganizerCore.refreshESPList(); + m_OrganizerCore.savePluginList(); + } } -bool MainWindow::registerWidgetState(const QString& name, QHeaderView* view, const char* oldSettingName) { - // register the view so it's geometry gets saved at exit - m_PersistedGeometry.push_back(std::make_pair(name, view)); - - // also, restore the geometry if it was saved before - QSettings& settings = m_OrganizerCore.settings().directInterface(); - QString key = QString("geometry/%1").arg(name); - QByteArray data; +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"; - if ((oldSettingName != nullptr) && settings.contains(oldSettingName)) { - data = settings.value(oldSettingName).toByteArray(); - settings.remove(oldSettingName); - } else if (settings.contains(key)) { - data = settings.value(key).toByteArray(); - } - if (!data.isEmpty()) { - view->restoreState(data); - return true; +bool MainWindow::createBackup(const QString &filePath, const QDateTime &time) +{ + QString outPath = filePath + "." + time.toString(PATTERN_BACKUP_DATE); + if (shellCopy(QStringList(filePath), QStringList(outPath), this)) { + QFileInfo fileInfo(filePath); + removeOldFiles(fileInfo.absolutePath(), fileInfo.fileName() + PATTERN_BACKUP_GLOB, 3, QDir::Name); + return true; + } else { + return false; + } +} + +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)) { + MessageDialog::showMessage(tr("Backup of load order created"), this); + } +} + +QString MainWindow::queryRestore(const QString &filePath) +{ + QFileInfo pluginFileInfo(filePath); + QString pattern = pluginFileInfo.fileName() + ".*"; + QFileInfoList files = pluginFileInfo.absoluteDir().entryInfoList(QStringList(pattern), QDir::Files, QDir::Name); + + SelectionDialog dialog(tr("Choose backup to restore"), this); + QRegExp exp(pluginFileInfo.fileName() + PATTERN_BACKUP_REGEX); + QRegExp exp2(pluginFileInfo.fileName() + "\\.(.*)"); + for(const QFileInfo &info : files) { + if (exp.exactMatch(info.fileName())) { + QDateTime time = QDateTime::fromString(exp.cap(1), PATTERN_BACKUP_DATE); + dialog.addChoice(time.toString(), "", exp.cap(1)); + } else if (exp2.exactMatch(info.fileName())) { + dialog.addChoice(exp2.cap(1), "", exp2.cap(1)); + } + } + + if (dialog.numChoices() == 0) { + QMessageBox::information(this, tr("No Backups"), tr("There are no backups to restore")); + return QString(); + } + + if (dialog.exec() == QDialog::Accepted) { + return dialog.getChoiceData().toString(); + } else { + return QString(); + } +} + +void MainWindow::on_restoreButton_clicked() +{ + QString pluginName = m_OrganizerCore.currentProfile()->getPluginsFileName(); + 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) || + !shellCopy(loadOrderName + "." + choice, loadOrderName, true, this) || + !shellCopy(lockedName + "." + choice, lockedName, true, this)) { + QMessageBox::critical(this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + } + m_OrganizerCore.refreshESPList(); + } +} + +void MainWindow::on_saveModsButton_clicked() +{ + m_OrganizerCore.currentProfile()->writeModlistNow(true); + QDateTime now = QDateTime::currentDateTime(); + if (createBackup(m_OrganizerCore.currentProfile()->getModlistFileName(), now)) { + MessageDialog::showMessage(tr("Backup of modlist created"), this); + } +} + +void MainWindow::on_restoreModsButton_clicked() +{ + QString modlistName = m_OrganizerCore.currentProfile()->getModlistFileName(); + QString choice = queryRestore(modlistName); + if (!choice.isEmpty()) { + if (!shellCopy(modlistName + "." + choice, modlistName, true, this)) { + QMessageBox::critical(this, tr("Restore failed"), + tr("Failed to restore the backup. Errorcode: %1").arg(windowsErrorString(::GetLastError()))); + } + m_OrganizerCore.refreshModList(false); + } +} + +void MainWindow::on_actionCopy_Log_to_Clipboard_triggered() +{ + QStringList lines; + QAbstractItemModel *model = ui->logList->model(); + for (int i = 0; i < model->rowCount(); ++i) { + lines.append(QString("%1 [%2] %3").arg(model->index(i, 0).data().toString()) + .arg(model->index(i, 1).data(Qt::UserRole).toString()) + .arg(model->index(i, 1).data().toString())); + } + QApplication::clipboard()->setText(lines.join("\n")); +} + +void MainWindow::on_categoriesAndBtn_toggled(bool checked) +{ + if (checked) { + m_ModListSortProxy->setFilterMode(ModListSortProxy::FILTER_AND); + } +} + +void MainWindow::on_categoriesOrBtn_toggled(bool checked) +{ + if (checked) { + m_ModListSortProxy->setFilterMode(ModListSortProxy::FILTER_OR); + } +} + +void MainWindow::on_managedArchiveLabel_linkHovered(const QString&) +{ + QToolTip::showText(QCursor::pos(), + ui->managedArchiveLabel->toolTip()); +} + +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). + 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 (data->hasUrls()) { + QStringList extensions = + m_OrganizerCore.installationManager()->getSupportedExtensions(); + + //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) { + if (url.isLocalFile()) { + QString local = url.toLocalFile(); + bool fok = false; + for (auto ext : extensions) { + if (local.endsWith(ext, Qt::CaseInsensitive)) { + fok = true; + break; + } + } + if (! fok) { + ok = false; + break; + } + } + } + if (ok) { + event->accept(); + } + } + } +} + +void MainWindow::dropLocalFile(const QUrl &url, const QString &outputDir, bool move) +{ + QFileInfo file(url.toLocalFile()); + if (!file.exists()) { + qWarning("invalid source file %s", qPrintable(file.absoluteFilePath())); + return; + } + QString target = outputDir + "/" + file.fileName(); + if (QFile::exists(target)) { + 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); + box.addButton(tr("Rename new file"), QMessageBox::YesRole); + box.addButton(tr("Ignore file"), QMessageBox::RejectRole); + + 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; + } + } + + bool success = false; + if (move) { + success = shellMove(file.absoluteFilePath(), target, true, this); + } else { + success = shellCopy(file.absoluteFilePath(), target, true, this); + } + if (!success) { + qCritical("file operation failed: %s", qPrintable(windowsErrorString(::GetLastError()))); + } +} + +bool MainWindow::registerWidgetState(const QString &name, QHeaderView *view, const char *oldSettingName) { + // register the view so it's geometry gets saved at exit + m_PersistedGeometry.push_back(std::make_pair(name, view)); + + // also, restore the geometry if it was saved before + QSettings &settings = m_OrganizerCore.settings().directInterface(); + + QString key = QString("geometry/%1").arg(name); + QByteArray data; + + if ((oldSettingName != nullptr) && settings.contains(oldSettingName)) { + data = settings.value(oldSettingName).toByteArray(); + settings.remove(oldSettingName); + } else if (settings.contains(key)) { + data = settings.value(key).toByteArray(); + } + + if (!data.isEmpty()) { + view->restoreState(data); + return true; + } else { + return false; + } +} + +void MainWindow::dropEvent(QDropEvent *event) +{ + Qt::DropAction action = event->proposedAction(); + QString outputDir = m_OrganizerCore.downloadManager()->getOutputDirectory(); + if (action == Qt::MoveAction) { + //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()) { + if (url.isLocalFile()) { + dropLocalFile(url, outputDir, action == Qt::MoveAction); } else { - return false; + m_OrganizerCore.downloadManager()->startDownloadURLs(QStringList() << url.url()); } + } + event->accept(); } -void MainWindow::dropEvent(QDropEvent* event) { - Qt::DropAction action = event->proposedAction(); - QString outputDir = m_OrganizerCore.downloadManager()->getOutputDirectory(); - if (action == Qt::MoveAction) { - // 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()) { - if (url.isLocalFile()) { - dropLocalFile(url, outputDir, action == Qt::MoveAction); - } else { - m_OrganizerCore.downloadManager()->startDownloadURLs(QStringList() << url.url()); - } - } - event->accept(); + +void MainWindow::on_clickBlankButton_clicked() +{ + deselectFilters(); } -void MainWindow::on_clickBlankButton_clicked() { deselectFilters(); } +void MainWindow::on_clearFiltersButton_clicked() +{ + deselectFilters(); +} -void MainWindow::on_clearFiltersButton_clicked() { deselectFilters(); } diff --git a/src/mainwindow.h b/src/mainwindow.h index c0748bdb..b7eca72e 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -20,8 +20,8 @@ along with Mod Organizer. If not, see . #ifndef MAINWINDOW_H #define MAINWINDOW_H -#include "browserdialog.h" #include "bsafolder.h" +#include "browserdialog.h" #include "delayedfilewriter.h" #include "errorcodes.h" #include "imoinfo.h" @@ -31,37 +31,26 @@ along with Mod Organizer. If not, see . #include "savegameinfo.h" #include "tutorialcontrol.h" -// Note the commented headers here can be replaced with forward references, -// when I get round to cleaning up main.cpp +//Note the commented headers here can be replaced with forward references, +//when I get round to cleaning up main.cpp struct Executable; class CategoryFactory; class LockedDialogBase; class OrganizerCore; #include "plugincontainer.h" //class PluginContainer; class PluginListSortProxy; -namespace BSA { -class Archive; -} +namespace BSA { class Archive; } #include "iplugingame.h" //namespace MOBase { class IPluginGame; } -namespace MOBase { -class IPluginModPage; -} -namespace MOBase { -class IPluginTool; -} -namespace MOBase { -class ISaveGame; -} +namespace MOBase { class IPluginModPage; } +namespace MOBase { class IPluginTool; } +namespace MOBase { class ISaveGame; } -namespace MOShared { -class DirectoryEntry; -} +namespace MOShared { class DirectoryEntry; } #include #include #include #include -#include #include #include #include @@ -71,6 +60,7 @@ class DirectoryEntry; #include #include #include +#include #include #include @@ -95,7 +85,7 @@ class QWidget; #include #endif -// Sigh - just for HANDLE +//Sigh - just for HANDLE #define WIN32_LEAN_AND_MEAN #include @@ -105,479 +95,492 @@ class QWidget; #include namespace Ui { -class MainWindow; + class MainWindow; } -class MainWindow : public QMainWindow, public IUserInterface { - Q_OBJECT - friend class OrganizerProxy; + +class MainWindow : public QMainWindow, public IUserInterface +{ + Q_OBJECT + + friend class OrganizerProxy; public: - explicit MainWindow(QSettings& initSettings, OrganizerCore& organizerCore, PluginContainer& pluginContainer, - QWidget* parent = 0); - ~MainWindow(); - void storeSettings(QSettings& settings) override; - void readSettings(); + explicit MainWindow(QSettings &initSettings, + OrganizerCore &organizerCore, PluginContainer &pluginContainer, + QWidget *parent = 0); + ~MainWindow(); + + void storeSettings(QSettings &settings) override; + void readSettings(); - virtual ILockedWaitingForProcess* lock() override; - virtual void unlock() override; + virtual ILockedWaitingForProcess* lock() override; + virtual void unlock() override; - bool addProfile(); - void updateBSAList(const QStringList& defaultArchives, const QStringList& activeArchives); - void refreshDataTree(); - void refreshDataTreeKeepExpandedNodes(); - void refreshSaveList(); + bool addProfile(); + void updateBSAList(const QStringList &defaultArchives, const QStringList &activeArchives); + void refreshDataTree(); + void refreshDataTreeKeepExpandedNodes(); + void refreshSaveList(); - void setModListSorting(int index); - void setESPListSorting(int index); + void setModListSorting(int index); + void setESPListSorting(int index); - void saveArchiveList(); + void saveArchiveList(); - void registerPluginTool(MOBase::IPluginTool* tool); - void registerModPage(MOBase::IPluginModPage* modPage); + void registerPluginTool(MOBase::IPluginTool *tool); + void registerModPage(MOBase::IPluginModPage *modPage); - void addPrimaryCategoryCandidates(QMenu* primaryCategoryMenu, ModInfo::Ptr info); + void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info); - void createStdoutPipe(HANDLE* stdOutRead, HANDLE* stdOutWrite); - std::string readFromPipe(HANDLE stdOutRead); - void processLOOTOut(const std::string& lootOut, std::string& errorMessages, QProgressDialog& dialog); + void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite); + std::string readFromPipe(HANDLE stdOutRead); + void processLOOTOut(const std::string &lootOut, std::string &errorMessages, QProgressDialog &dialog); - void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); + void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); - QString getOriginDisplayName(int originID); + QString getOriginDisplayName(int originID); - void installTranslator(const QString& name); + void installTranslator(const QString &name); - virtual void disconnectPlugins(); + virtual void disconnectPlugins(); - void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab); + void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab); - virtual bool closeWindow(); - virtual void setWindowEnabled(bool enabled); + 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 displayColumnSelection(const QPoint& pos); + void displayColumnSelection(const QPoint &pos); - void modorder_changed(); - void refresher_progress(int percent); - void directory_refreshed(); + void modorder_changed(); + void refresher_progress(int percent); + void directory_refreshed(); - void toolPluginInvoke(); - void modPagePluginInvoke(); + void toolPluginInvoke(); + void modPagePluginInvoke(); signals: - /** - * @brief emitted after the information dialog has been closed - */ - void modInfoDisplayed(); + /** + * @brief emitted after the information dialog has been closed + */ + void modInfoDisplayed(); + + /** + * @brief emitted when the selected style changes + */ + void styleChanged(const QString &styleFile); - /** - * @brief emitted when the selected style changes - */ - void styleChanged(const QString& styleFile); - void modListDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight); + void modListDataChanged(const QModelIndex &topLeft, const QModelIndex &bottomRight); protected: - virtual void showEvent(QShowEvent* event); - virtual void closeEvent(QCloseEvent* event); - virtual bool eventFilter(QObject* obj, QEvent* event); - virtual void resizeEvent(QResizeEvent* event); - virtual void dragEnterEvent(QDragEnterEvent* event); - virtual void dropEvent(QDropEvent* event); + + virtual void showEvent(QShowEvent *event); + virtual void closeEvent(QCloseEvent *event); + virtual bool eventFilter(QObject *obj, QEvent *event); + virtual void resizeEvent(QResizeEvent *event); + virtual void dragEnterEvent(QDragEnterEvent *event); + virtual void dropEvent(QDropEvent *event); private slots: - void on_actionChange_Game_triggered(); + void on_actionChange_Game_triggered(); private slots: - void on_clickBlankButton_clicked(); + void on_clickBlankButton_clicked(); private: - void cleanup(); - void actionToToolButton(QAction*& sourceAction); + void cleanup(); + + void actionToToolButton(QAction *&sourceAction); - void updateToolBar(); - void activateSelectedProfile(); + void updateToolBar(); + void activateSelectedProfile(); - void setExecutableIndex(int index); + void setExecutableIndex(int index); - void startSteam(); + void startSteam(); - void updateTo(QTreeWidgetItem* subTree, const std::wstring& directorySoFar, - const MOShared::DirectoryEntry& directoryEntry, bool conflictsOnly); - bool refreshProfiles(bool selectProfile = true); - void refreshExecutablesList(); - void installMod(QString fileName = ""); + void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly); + bool refreshProfiles(bool selectProfile = true); + void refreshExecutablesList(); + void installMod(QString fileName = ""); - QList - findFileInfos(const QString& path, const std::function& filter) const; + QList findFileInfos(const QString &path, const std::function &filter) const; - bool modifyExecutablesDialog(); - void displayModInformation(int row, int tab = 0); - void testExtractBSA(int modIndex); + bool modifyExecutablesDialog(); + void displayModInformation(int row, int tab = 0); + void testExtractBSA(int modIndex); - void writeDataToFile(QFile& file, const QString& directory, const MOShared::DirectoryEntry& directoryEntry); + void writeDataToFile(QFile &file, const QString &directory, const MOShared::DirectoryEntry &directoryEntry); - void refreshFilters(); + void refreshFilters(); - /** - * Sets category selections from menu; for multiple mods, this will only apply - * the changes made in the menu (which is the delta between the current menu selection and the reference mod) - * @param menu the menu after editing by the user - * @param modRow index of the mod to edit - * @param referenceRow row of the reference mod - */ - void addRemoveCategoriesFromMenu(QMenu* menu, int modRow, int referenceRow); + /** + * Sets category selections from menu; for multiple mods, this will only apply + * the changes made in the menu (which is the delta between the current menu selection and the reference mod) + * @param menu the menu after editing by the user + * @param modRow index of the mod to edit + * @param referenceRow row of the reference mod + */ + void addRemoveCategoriesFromMenu(QMenu *menu, int modRow, int referenceRow); - /** - * Sets category selections from menu; for multiple mods, this will completely - * replace the current set of categories on each selected with those selected in the menu - * @param menu the menu after editing by the user - * @param modRow index of the mod to edit - */ - void replaceCategoriesFromMenu(QMenu* menu, int modRow); + /** + * Sets category selections from menu; for multiple mods, this will completely + * replace the current set of categories on each selected with those selected in the menu + * @param menu the menu after editing by the user + * @param modRow index of the mod to edit + */ + void replaceCategoriesFromMenu(QMenu *menu, int modRow); - bool populateMenuCategories(QMenu* menu, int targetID); + bool populateMenuCategories(QMenu *menu, int targetID); - void updateDownloadListDelegate(); + void updateDownloadListDelegate(); - // remove invalid category-references from mods - void fixCategories(); + // remove invalid category-references from mods + void fixCategories(); - void createHelpWidget(); + void createHelpWidget(); - bool extractProgress(QProgressDialog& extractProgress, int percentage, std::string fileName); + bool extractProgress(QProgressDialog &extractProgress, int percentage, std::string fileName); - size_t checkForProblems(); + size_t checkForProblems(); - int getBinaryExecuteInfo(const QFileInfo& targetInfo, QFileInfo& binaryInfo, QString& arguments); - QTreeWidgetItem* addFilterItem(QTreeWidgetItem* root, const QString& name, int categoryID, - ModListSortProxy::FilterType type); - void addContentFilters(); - void addCategoryFilters(QTreeWidgetItem* root, const std::set& categoriesUsed, int targetID); + int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments); + QTreeWidgetItem *addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID, ModListSortProxy::FilterType type); + void addContentFilters(); + void addCategoryFilters(QTreeWidgetItem *root, const std::set &categoriesUsed, int targetID); - void setCategoryListVisible(bool visible); + void setCategoryListVisible(bool visible); - void displaySaveGameInfo(QListWidgetItem* newItem); + void displaySaveGameInfo(QListWidgetItem *newItem); - HANDLE nextChildProcess(); + HANDLE nextChildProcess(); - bool errorReported(QString& logFile); + bool errorReported(QString &logFile); - void updateESPLock(bool locked); + void updateESPLock(bool locked); - static void setupNetworkProxy(bool activate); - void activateProxy(bool activate); - void setBrowserGeometry(const QByteArray& geometry); + static void setupNetworkProxy(bool activate); + void activateProxy(bool activate); + void setBrowserGeometry(const QByteArray &geometry); - 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* modListContextMenu(); + QMenu *modListContextMenu(); - std::set enabledArchives(); + std::set enabledArchives(); - void scheduleUpdateButton(); + void scheduleUpdateButton(); - QDir currentSavesDir() const; + QDir currentSavesDir() const; - void startMonitorSaves(); - void stopMonitorSaves(); + void startMonitorSaves(); + void stopMonitorSaves(); - void dropLocalFile(const QUrl& url, const QString& outputDir, bool move); + void dropLocalFile(const QUrl &url, const QString &outputDir, bool move); - bool registerWidgetState(const QString& name, QHeaderView* view, const char* oldSettingName = nullptr); + bool registerWidgetState(const QString &name, QHeaderView *view, const char *oldSettingName = nullptr); 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; - bool m_WasVisible; + Ui::MainWindow *ui; + + bool m_WasVisible; - MOBase::TutorialControl m_Tutorial; + MOBase::TutorialControl m_Tutorial; - int m_OldProfileIndex; + int m_OldProfileIndex; - std::vector m_ModNameList; // the mod-list to go with the directory structure - QProgressBar* m_RefreshProgress; - bool m_Refreshing; + std::vector m_ModNameList; // the mod-list to go with the directory structure + QProgressBar *m_RefreshProgress; + bool m_Refreshing; - QStringList m_DefaultArchives; + QStringList m_DefaultArchives; - QAbstractItemModel* m_ModListGroupingProxy; - ModListSortProxy* m_ModListSortProxy; + QAbstractItemModel *m_ModListGroupingProxy; + ModListSortProxy *m_ModListSortProxy; - PluginListSortProxy* m_PluginListSortProxy; + PluginListSortProxy *m_PluginListSortProxy; - int m_OldExecutableIndex; + int m_OldExecutableIndex; - int m_ContextRow; - QPersistentModelIndex m_ContextIdx; - QTreeWidgetItem* m_ContextItem; - QAction* m_ContextAction; + int m_ContextRow; + QPersistentModelIndex m_ContextIdx; + QTreeWidgetItem *m_ContextItem; + QAction *m_ContextAction; - CategoryFactory& m_CategoryFactory; + CategoryFactory &m_CategoryFactory; - int m_ModsToUpdate; + int m_ModsToUpdate; - bool m_LoginAttempted; + bool m_LoginAttempted; - QTimer m_CheckBSATimer; - QTimer m_SaveMetaTimer; - QTimer m_UpdateProblemsTimer; + QTimer m_CheckBSATimer; + QTimer m_SaveMetaTimer; + QTimer m_UpdateProblemsTimer; - QTime m_StartTime; - // SaveGameInfoWidget *m_CurrentSaveView; - MOBase::ISaveGameInfoWidget* m_CurrentSaveView; + QTime m_StartTime; + //SaveGameInfoWidget *m_CurrentSaveView; + MOBase::ISaveGameInfoWidget *m_CurrentSaveView; - OrganizerCore& m_OrganizerCore; - PluginContainer& m_PluginContainer; + OrganizerCore &m_OrganizerCore; + PluginContainer &m_PluginContainer; - QString m_CurrentLanguage; - std::vector m_Translators; + QString m_CurrentLanguage; + std::vector m_Translators; - BrowserDialog m_IntegratedBrowser; + BrowserDialog m_IntegratedBrowser; - QFileSystemWatcher m_SavesWatcher; + QFileSystemWatcher m_SavesWatcher; - std::vector m_RemoveWidget; + std::vector m_RemoveWidget; - QByteArray m_ArchiveListHash; + QByteArray m_ArchiveListHash; - bool m_DidUpdateMasterList; + bool m_DidUpdateMasterList; - LockedDialogBase* m_LockDialog{nullptr}; - uint64_t m_LockCount{0}; + LockedDialogBase *m_LockDialog { nullptr }; + uint64_t m_LockCount { 0 }; - bool m_closing{false}; + bool m_closing{ false }; - std::vector> m_PersistedGeometry; + std::vector> m_PersistedGeometry; - MOBase::DelayedFileWriter m_ArchiveListWriter; + MOBase::DelayedFileWriter m_ArchiveListWriter; - enum class ShortcutType { Toolbar, Desktop, StartMenu }; + enum class ShortcutType { + Toolbar, + Desktop, + StartMenu + }; - void addWindowsLink(ShortcutType const); + void addWindowsLink(ShortcutType const); - Executable const& getSelectedExecutable() const; - Executable& getSelectedExecutable(); + Executable const &getSelectedExecutable() const; + Executable &getSelectedExecutable(); private slots: - void updateWindowTitle(const QString& accountName, bool premium); + void updateWindowTitle(const QString &accountName, bool premium); + + 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(); + void issueTriggered(); + void wikiTriggered(); + void tutorialTriggered(); + void extractBSATriggered(); - // main window actions - void helpTriggered(); - void issueTriggered(); - void wikiTriggered(); - void tutorialTriggered(); - void extractBSATriggered(); + // modlist context menu + void installMod_clicked(); + void createEmptyMod_clicked(); + void restoreBackup_clicked(); + void renameMod_clicked(); + void removeMod_clicked(); + void reinstallMod_clicked(); + void endorse_clicked(); + void dontendorse_clicked(); + void unendorse_clicked(); + void ignoreMissingData_clicked(); + void visitOnNexus_clicked(); + void visitWebPage_clicked(); + void openExplorer_clicked(); + void information_clicked(); + // savegame context menu + void deleteSavegame_clicked(); + void fixMods_clicked(SaveGameInfo::MissingAssets const &missingAssets); + // data-tree context menu + void writeDataToFile(); + void openDataFile(); + void addAsExecutable(); + void previewDataFile(); + void hideFile(); + void unhideFile(); - // modlist context menu - void installMod_clicked(); - void createEmptyMod_clicked(); - void restoreBackup_clicked(); - void renameMod_clicked(); - void removeMod_clicked(); - void reinstallMod_clicked(); - void endorse_clicked(); - void dontendorse_clicked(); - void unendorse_clicked(); - void ignoreMissingData_clicked(); - void visitOnNexus_clicked(); - void visitWebPage_clicked(); - void openExplorer_clicked(); - void information_clicked(); - // savegame context menu - void deleteSavegame_clicked(); - void fixMods_clicked(SaveGameInfo::MissingAssets const& missingAssets); - // data-tree context menu - void writeDataToFile(); - void openDataFile(); - void addAsExecutable(); - void previewDataFile(); - void hideFile(); - void unhideFile(); + void linkToolbar(); + void linkDesktop(); + void linkMenu(); - void linkToolbar(); - void linkDesktop(); - void linkMenu(); + void languageChange(const QString &newLanguage); + void saveSelectionChanged(QListWidgetItem *newItem); - void languageChange(const QString& newLanguage); - void saveSelectionChanged(QListWidgetItem* newItem); + 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); + void createModFromOverwrite(); + void clearOverwrite(); - void createModFromOverwrite(); - void clearOverwrite(); + void procError(QProcess::ProcessError error); + void procFinished(int exitCode, QProcess::ExitStatus exitStatus); - void procError(QProcess::ProcessError error); - void procFinished(int exitCode, QProcess::ExitStatus exitStatus); + // nexus related + void checkModsForUpdates(); + void nexusLinkActivated(const QString &link); - // nexus related - void checkModsForUpdates(); - void nexusLinkActivated(const QString& link); + void loginFailed(const QString &message); - void loginFailed(const QString& message); + void linkClicked(const QString &url); - void linkClicked(const QString& url); + void updateAvailable(); - void updateAvailable(); + void motdReceived(const QString &motd); + void notEndorsedYet(); - void motdReceived(const QString& motd); - void notEndorsedYet(); + void originModified(int originID); - void originModified(int originID); + void addRemoveCategories_MenuHandler(); + void replaceCategories_MenuHandler(); - void addRemoveCategories_MenuHandler(); - void replaceCategories_MenuHandler(); + void addPrimaryCategoryCandidates(); - void addPrimaryCategoryCandidates(); + void modDetailsUpdated(bool success); - void modDetailsUpdated(bool success); + void modInstalled(const QString &modName); - void modInstalled(const QString& modName); + void nxmUpdatesAvailable(const std::vector &modIDs, QVariant userData, QVariant resultData, int requestID); + void nxmEndorsementToggled(int, QVariant, QVariant resultData, int); + void nxmDownloadURLs(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorString); - void nxmUpdatesAvailable(const std::vector& modIDs, QVariant userData, QVariant resultData, int requestID); - void nxmEndorsementToggled(int, QVariant, QVariant resultData, int); - void nxmDownloadURLs(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString& errorString); + void editCategories(); + void deselectFilters(); - void editCategories(); - void deselectFilters(); + void displayModInformation(const QString &modName, int tab); + void modOpenNext(); + void modOpenPrev(); - void displayModInformation(const QString& modName, int tab); - void modOpenNext(); - void modOpenPrev(); + 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 hideSaveGameInfo(); - void hideSaveGameInfo(); + void hookUpWindowTutorials(); - void hookUpWindowTutorials(); + void resumeDownload(int downloadIndex); + void endorseMod(ModInfo::Ptr mod); + void cancelModListEditor(); - void resumeDownload(int downloadIndex); - void endorseMod(ModInfo::Ptr mod); - void cancelModListEditor(); + void lockESPIndex(); + void unlockESPIndex(); - void lockESPIndex(); - void unlockESPIndex(); + void enableVisibleMods(); + void disableVisibleMods(); + void exportModListCSV(); + void openInstanceFolder(); + void openDownloadsFolder(); + void openProfileFolder(); + void openGameFolder(); + void openMyGamesFolder(); + void startExeAction(); - void enableVisibleMods(); - void disableVisibleMods(); - void exportModListCSV(); - void openInstanceFolder(); - void openDownloadsFolder(); - void openProfileFolder(); - void openGameFolder(); - void openMyGamesFolder(); - void startExeAction(); + void checkBSAList(); - void checkBSAList(); + void updateProblemsButton(); - void updateProblemsButton(); + void saveModMetas(); - void saveModMetas(); + void updateStyle(const QString &style); - void updateStyle(const QString& style); + void modlistChanged(const QModelIndex &index, int role); + void fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName); - void modlistChanged(const QModelIndex& index, int role); - void fileMoved(const QString& filePath, const QString& oldOriginName, const QString& newOriginName); - void modFilterActive(bool active); - void espFilterChanged(const QString& filter); - void downloadFilterChanged(const QString& filter); + void modFilterActive(bool active); + void espFilterChanged(const QString &filter); + void downloadFilterChanged(const QString &filter); - void expandModList(const QModelIndex& index); + void expandModList(const QModelIndex &index); - /** - * @brief resize columns in mod list and plugin list to content - */ - void resizeLists(bool modListCustom, bool pluginListCustom); + /** + * @brief resize columns in mod list and plugin list to content + */ + void resizeLists(bool modListCustom, bool pluginListCustom); - /** - * @brief allow columns in mod list and plugin list to be resized - */ - void allowListResize(); + /** + * @brief allow columns in mod list and plugin list to be resized + */ + void allowListResize(); - void toolBar_customContextMenuRequested(const QPoint& point); - void removeFromToolbar(); - void overwriteClosed(int); + void toolBar_customContextMenuRequested(const QPoint &point); + void removeFromToolbar(); + void overwriteClosed(int); - void changeVersioningScheme(); - void ignoreUpdate(); - void unignoreUpdate(); + void changeVersioningScheme(); + void ignoreUpdate(); + void unignoreUpdate(); - void refreshSavesIfOpen(); - void expandDataTreeItem(QTreeWidgetItem* item); - void about(); - void delayedRemove(); + void refreshSavesIfOpen(); + void expandDataTreeItem(QTreeWidgetItem *item); + void about(); + void delayedRemove(); - void modlistSelectionChanged(const QModelIndex& current, const QModelIndex& previous); - void modListSortIndicatorChanged(int column, Qt::SortOrder order); + void modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); + void modListSortIndicatorChanged(int column, Qt::SortOrder order); - void modlistSelectionsChanged(const QItemSelection& current); - void esplistSelectionsChanged(const QItemSelection& current); + void modlistSelectionsChanged(const QItemSelection ¤t); + void esplistSelectionsChanged(const QItemSelection ¤t); private slots: // ui slots - // actions - void on_actionAdd_Profile_triggered(); - void on_actionInstallMod_triggered(); - void on_actionModify_Executables_triggered(); - void on_actionNexus_triggered(); - void on_actionProblems_triggered(); - void on_actionSettings_triggered(); - void on_actionUpdate_triggered(); - void on_actionEndorseMO_triggered(); - - void on_clearFiltersButton_clicked(); - void on_btnRefreshData_clicked(); - void on_categoriesList_customContextMenuRequested(const QPoint& pos); - void on_conflictsCheckBox_toggled(bool checked); - void on_dataTree_customContextMenuRequested(const QPoint& pos); - void on_executablesListBox_currentIndexChanged(int index); - void on_modList_customContextMenuRequested(const QPoint& pos); - void on_modList_doubleClicked(const QModelIndex& index); - void on_profileBox_currentIndexChanged(int index); - void on_savegameList_customContextMenuRequested(const QPoint& pos); - void on_startButton_clicked(); - void on_tabWidget_currentChanged(int index); - - void on_espList_customContextMenuRequested(const QPoint& pos); - void on_displayCategoriesBtn_toggled(bool checked); - void on_groupCombo_currentIndexChanged(int index); - void on_categoriesList_itemSelectionChanged(); - void on_linkButton_pressed(); - void on_showHiddenBox_toggled(bool checked); - void on_bsaList_itemChanged(QTreeWidgetItem* item, int column); - void on_bossButton_clicked(); - - void on_saveButton_clicked(); - void on_restoreButton_clicked(); - void on_restoreModsButton_clicked(); - void on_saveModsButton_clicked(); - void on_actionCopy_Log_to_Clipboard_triggered(); - void on_categoriesAndBtn_toggled(bool checked); - void on_categoriesOrBtn_toggled(bool checked); - void on_managedArchiveLabel_linkHovered(const QString& link); + // actions + void on_actionAdd_Profile_triggered(); + void on_actionInstallMod_triggered(); + void on_actionModify_Executables_triggered(); + void on_actionNexus_triggered(); + void on_actionProblems_triggered(); + void on_actionSettings_triggered(); + void on_actionUpdate_triggered(); + void on_actionEndorseMO_triggered(); + + void on_clearFiltersButton_clicked(); + void on_btnRefreshData_clicked(); + void on_categoriesList_customContextMenuRequested(const QPoint &pos); + void on_conflictsCheckBox_toggled(bool checked); + void on_dataTree_customContextMenuRequested(const QPoint &pos); + void on_executablesListBox_currentIndexChanged(int index); + void on_modList_customContextMenuRequested(const QPoint &pos); + void on_modList_doubleClicked(const QModelIndex &index); + void on_profileBox_currentIndexChanged(int index); + void on_savegameList_customContextMenuRequested(const QPoint &pos); + void on_startButton_clicked(); + void on_tabWidget_currentChanged(int index); + + void on_espList_customContextMenuRequested(const QPoint &pos); + void on_displayCategoriesBtn_toggled(bool checked); + void on_groupCombo_currentIndexChanged(int index); + void on_categoriesList_itemSelectionChanged(); + void on_linkButton_pressed(); + void on_showHiddenBox_toggled(bool checked); + void on_bsaList_itemChanged(QTreeWidgetItem *item, int column); + void on_bossButton_clicked(); + + void on_saveButton_clicked(); + void on_restoreButton_clicked(); + void on_restoreModsButton_clicked(); + void on_saveModsButton_clicked(); + void on_actionCopy_Log_to_Clipboard_triggered(); + void on_categoriesAndBtn_toggled(bool checked); + void on_categoriesOrBtn_toggled(bool checked); + void on_managedArchiveLabel_linkHovered(const QString &link); }; + + #endif // MAINWINDOW_H diff --git a/src/messagedialog.cpp b/src/messagedialog.cpp index 33594d83..90b0a9d3 100644 --- a/src/messagedialog.cpp +++ b/src/messagedialog.cpp @@ -19,65 +19,74 @@ along with Mod Organizer. If not, see . #include "messagedialog.h" #include "ui_messagedialog.h" -#include #include +#include #include -MessageDialog::MessageDialog(const QString& text, QWidget* reference) : QDialog(reference), ui(new Ui::MessageDialog) { - ui->setupUi(this); +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"; + // 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); - } + 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); - } +MessageDialog::~MessageDialog() +{ + delete ui; } -void MessageDialog::showMessage(const QString& text, QWidget* reference, bool bringToFront) { - qDebug("%s", qPrintable(text)); - if (reference != nullptr) { - if (bringToFront || (qApp->activeWindow() != nullptr)) { - MessageDialog* dialog = new MessageDialog(text, reference); - dialog->show(); - reference->activateWindow(); - } + +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) +{ + qDebug("%s", qPrintable(text)); + if (reference != nullptr) { + if (bringToFront || (qApp->activeWindow() != nullptr)) { + MessageDialog *dialog = new MessageDialog(text, reference); + dialog->show(); + reference->activateWindow(); } + } } diff --git a/src/messagedialog.h b/src/messagedialog.h index a37e53e3..7e9b9c3b 100644 --- a/src/messagedialog.h +++ b/src/messagedialog.h @@ -23,43 +23,45 @@ along with Mod Organizer. If not, see . #include namespace Ui { -class MessageDialog; + class MessageDialog; } /** * borderless dialog used to display short messages that will automatically * vanish after a moment **/ -class MessageDialog : public QDialog { +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 - **/ + /** + * @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); - explicit MessageDialog(const QString& text, QWidget* reference); + ~MessageDialog(); - ~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); + /** + * 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); + + virtual void resizeEvent(QResizeEvent *event); private: - Ui::MessageDialog* ui; + Ui::MessageDialog *ui; }; #endif // MESSAGEDIALOG_H diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 9397dadd..9ddd9e54 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -18,111 +18,126 @@ along with Mod Organizer. If not, see . */ #include "moapplication.h" -#include -#include -#include #include #include -#if QT_VERSION < QT_VERSION_CHECK(5, 0, 0) -#include +#include +#include +#include +#if QT_VERSION < QT_VERSION_CHECK(5,0,0) #include +#include #endif -#include #include #include +#include #include + #include + using MOBase::reportError; + class ProxyStyle : public QProxyStyle { public: - ProxyStyle(QStyle* baseStyle = 0) : QProxyStyle(baseStyle) {} - - void drawPrimitive(PrimitiveElement element, const QStyleOption* option, QPainter* painter, - const QWidget* widget) const { - if (element == QStyle::PE_IndicatorItemViewItemDrop) { - painter->setRenderHint(QPainter::Antialiasing, true); - - QColor col(option->palette.foreground().color()); - QPen pen(col); - pen.setWidth(2); - col.setAlpha(50); - QBrush brush(col); - - painter->setPen(pen); - painter->setBrush(brush); - if (option->rect.height() == 0) { - QPoint tri[3] = {option->rect.topLeft(), option->rect.topLeft() + QPoint(-5, 5), - option->rect.topLeft() + QPoint(-5, -5)}; - painter->drawPolygon(tri, 3); - painter->drawLine(QPoint(option->rect.topLeft().x(), option->rect.topLeft().y()), - option->rect.topRight()); - } else { - painter->drawRoundedRect(option->rect, 5, 5); - } - } else { - QProxyStyle::drawPrimitive(element, option, painter, widget); - } + ProxyStyle(QStyle *baseStyle = 0) + : QProxyStyle(baseStyle) + { + } + + void drawPrimitive(PrimitiveElement element, const QStyleOption *option, QPainter *painter, const QWidget *widget) const { + if(element == QStyle::PE_IndicatorItemViewItemDrop) { + painter->setRenderHint(QPainter::Antialiasing, true); + + QColor col(option->palette.foreground().color()); + QPen pen(col); + pen.setWidth(2); + col.setAlpha(50); + QBrush brush(col); + + painter->setPen(pen); + painter->setBrush(brush); + if(option->rect.height() == 0) { + QPoint tri[3] = { + option->rect.topLeft(), + option->rect.topLeft() + QPoint(-5, 5), + option->rect.topLeft() + QPoint(-5, -5) + }; + painter->drawPolygon(tri, 3); + painter->drawLine(QPoint(option->rect.topLeft().x(), option->rect.topLeft().y()), option->rect.topRight()); + } else { + painter->drawRoundedRect(option->rect, 5, 5); + } + } else { + QProxyStyle::drawPrimitive(element, option, painter, widget); } + } }; -MOApplication::MOApplication(int& argc, char** argv) : QApplication(argc, argv) { - connect(&m_StyleWatcher, SIGNAL(fileChanged(QString)), SLOT(updateStyle(QString))); - m_DefaultStyle = style()->objectName(); - setStyle(new ProxyStyle(style())); + +MOApplication::MOApplication(int &argc, char **argv) + : QApplication(argc, argv) +{ + connect(&m_StyleWatcher, SIGNAL(fileChanged(QString)), SLOT(updateStyle(QString))); + m_DefaultStyle = style()->objectName(); + setStyle(new ProxyStyle(style())); } -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); - } + +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 { - setStyle(new ProxyStyle(QStyleFactory::create(m_DefaultStyle))); - setStyleSheet(""); + updateStyle(styleName); } - return true; + } 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) { - qCritical("uncaught exception in handler (object %s, eventtype %d): %s", - receiver->objectName().toUtf8().constData(), event->type(), e.what()); - reportError(tr("an error occured: %1").arg(e.what())); - return false; - } catch (...) { - qCritical("uncaught non-std exception in handler (object %s, eventtype %d)", - receiver->objectName().toUtf8().constData(), event->type()); - reportError(tr("an error occured")); - return false; - } + +bool MOApplication::notify(QObject *receiver, QEvent *event) +{ + try { + return QApplication::notify(receiver, event); + } catch (const std::exception &e) { + qCritical("uncaught exception in handler (object %s, eventtype %d): %s", + receiver->objectName().toUtf8().constData(), event->type(), e.what()); + reportError(tr("an error occured: %1").arg(e.what())); + return false; + } catch (...) { + qCritical("uncaught non-std exception in handler (object %s, eventtype %d)", + receiver->objectName().toUtf8().constData(), event->type()); + reportError(tr("an error occured")); + return false; + } } -void MOApplication::updateStyle(const QString& fileName) { - if (fileName == "Fusion") { - setStyle(QStyleFactory::create("fusion")); - setStyleSheet(""); + +void MOApplication::updateStyle(const QString &fileName) +{ + if (fileName == "Fusion") { + setStyle(QStyleFactory::create("fusion")); + setStyleSheet(""); + } else { + setStyle(new ProxyStyle(QStyleFactory::create(m_DefaultStyle))); + if (QFile::exists(fileName)) { + setStyleSheet(QString("file:///%1").arg(fileName)); } else { - setStyle(new ProxyStyle(QStyleFactory::create(m_DefaultStyle))); - if (QFile::exists(fileName)) { - setStyleSheet(QString("file:///%1").arg(fileName)); - } else { - qWarning("invalid stylesheet: %s", qPrintable(fileName)); - } + qWarning("invalid stylesheet: %s", qPrintable(fileName)); } + } } diff --git a/src/moapplication.h b/src/moapplication.h index bb8de414..9db130af 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -23,24 +23,29 @@ along with Mod Organizer. If not, see . #include #include + class MOApplication : public QApplication { - Q_OBJECT +Q_OBJECT public: - MOApplication(int& argc, char** argv); - virtual bool notify(QObject* receiver, QEvent* event); + MOApplication(int &argc, char **argv); + + virtual bool notify (QObject *receiver, QEvent *event); public slots: - bool setStyleFile(const QString& style); + bool setStyleFile(const QString &style); private slots: - void updateStyle(const QString& fileName); + void updateStyle(const QString &fileName); private: - QFileSystemWatcher m_StyleWatcher; - QString m_DefaultStyle; + + QFileSystemWatcher m_StyleWatcher; + QString m_DefaultStyle; + }; + #endif // MOAPPLICATION_H diff --git a/src/modeltest.cpp b/src/modeltest.cpp index d645d464..90061261 100644 --- a/src/modeltest.cpp +++ b/src/modeltest.cpp @@ -39,6 +39,7 @@ ** ****************************************************************************/ + #include #include "modeltest.h" @@ -50,48 +51,61 @@ #undef QCOMPARE #define QCOMPARE(x, y) Q_ASSERT(x == y) -Q_DECLARE_METATYPE(QModelIndex) +Q_DECLARE_METATYPE ( QModelIndex ) /*! Connect to all of the models signals. Whenever anything happens recheck everything. */ -ModelTest::ModelTest(QAbstractItemModel* _model, QObject* parent) - : QObject(parent), model(_model), fetchingMore(false) { +ModelTest::ModelTest ( QAbstractItemModel *_model, QObject *parent ) : QObject ( parent ), model ( _model ), fetchingMore ( false ) +{ if (!model) qFatal("%s: model must not be null", Q_FUNC_INFO); - connect(model, SIGNAL(columnsAboutToBeInserted(const QModelIndex&, int, int)), this, SLOT(runAllTests())); - connect(model, SIGNAL(columnsAboutToBeRemoved(const QModelIndex&, int, int)), this, SLOT(runAllTests())); - connect(model, SIGNAL(columnsInserted(const QModelIndex&, int, int)), this, SLOT(runAllTests())); - connect(model, SIGNAL(columnsRemoved(const QModelIndex&, int, int)), this, SLOT(runAllTests())); - connect(model, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SLOT(runAllTests())); - connect(model, SIGNAL(headerDataChanged(Qt::Orientation, int, int)), this, SLOT(runAllTests())); - connect(model, SIGNAL(layoutAboutToBeChanged()), this, SLOT(runAllTests())); - connect(model, SIGNAL(layoutChanged()), this, SLOT(runAllTests())); - connect(model, SIGNAL(modelReset()), this, SLOT(runAllTests())); - connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)), this, SLOT(runAllTests())); - connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), this, SLOT(runAllTests())); - connect(model, SIGNAL(rowsInserted(const QModelIndex&, int, int)), this, SLOT(runAllTests())); - connect(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), this, SLOT(runAllTests())); + connect ( model, SIGNAL ( columnsAboutToBeInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( columnsAboutToBeRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( columnsInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( columnsRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( dataChanged ( const QModelIndex &, const QModelIndex & ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( headerDataChanged ( Qt::Orientation, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( layoutAboutToBeChanged () ), this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( layoutChanged () ), this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( modelReset () ), this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( rowsAboutToBeInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( rowsAboutToBeRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( rowsInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( rowsRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); // Special checks for inserting/removing - connect(model, SIGNAL(layoutAboutToBeChanged()), this, SLOT(layoutAboutToBeChanged())); - connect(model, SIGNAL(layoutChanged()), this, SLOT(layoutChanged())); - - connect(model, SIGNAL(rowsAboutToBeInserted(const QModelIndex&, int, int)), this, - SLOT(rowsAboutToBeInserted(const QModelIndex&, int, int))); - connect(model, SIGNAL(rowsAboutToBeRemoved(const QModelIndex&, int, int)), this, - SLOT(rowsAboutToBeRemoved(const QModelIndex&, int, int))); - connect(model, SIGNAL(rowsInserted(const QModelIndex&, int, int)), this, - SLOT(rowsInserted(const QModelIndex&, int, int))); - connect(model, SIGNAL(rowsRemoved(const QModelIndex&, int, int)), this, - SLOT(rowsRemoved(const QModelIndex&, int, int))); + connect ( model, SIGNAL ( layoutAboutToBeChanged() ), + this, SLOT ( layoutAboutToBeChanged() ) ); + connect ( model, SIGNAL ( layoutChanged() ), + this, SLOT ( layoutChanged() ) ); + + connect ( model, SIGNAL ( rowsAboutToBeInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( rowsAboutToBeInserted ( const QModelIndex &, int, int ) ) ); + connect ( model, SIGNAL ( rowsAboutToBeRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( rowsAboutToBeRemoved ( const QModelIndex &, int, int ) ) ); + connect ( model, SIGNAL ( rowsInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( rowsInserted ( const QModelIndex &, int, int ) ) ); + connect ( model, SIGNAL ( rowsRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( rowsRemoved ( const QModelIndex &, int, int ) ) ); runAllTests(); } -void ModelTest::runAllTests() { - if (fetchingMore) +void ModelTest::runAllTests() +{ + if ( fetchingMore ) return; nonDestructiveBasicTest(); rowCount(); @@ -106,33 +120,34 @@ void ModelTest::runAllTests() { nonDestructiveBasicTest tries to call a number of the basic functions (not all) to make sure the model doesn't outright segfault, testing the functions that makes sense. */ -void ModelTest::nonDestructiveBasicTest() { - QVERIFY(model->buddy(QModelIndex()) == QModelIndex()); - model->canFetchMore(QModelIndex()); - QVERIFY(model->columnCount(QModelIndex()) >= 0); - QVERIFY(model->data(QModelIndex()) == QVariant()); +void ModelTest::nonDestructiveBasicTest() +{ + QVERIFY( model->buddy ( QModelIndex() ) == QModelIndex() ); + model->canFetchMore ( QModelIndex() ); + QVERIFY( model->columnCount ( QModelIndex() ) >= 0 ); + QVERIFY( model->data ( QModelIndex() ) == QVariant() ); fetchingMore = true; - model->fetchMore(QModelIndex()); + model->fetchMore ( QModelIndex() ); fetchingMore = false; - Qt::ItemFlags flags = model->flags(QModelIndex()); - QVERIFY(flags == Qt::ItemIsDropEnabled || flags == 0); - model->hasChildren(QModelIndex()); - model->hasIndex(0, 0); - model->headerData(0, Qt::Horizontal); - model->index(0, 0); - model->itemData(QModelIndex()); + Qt::ItemFlags flags = model->flags ( QModelIndex() ); + QVERIFY( flags == Qt::ItemIsDropEnabled || flags == 0 ); + model->hasChildren ( QModelIndex() ); + model->hasIndex ( 0, 0 ); + model->headerData ( 0, Qt::Horizontal ); + model->index ( 0, 0 ); + model->itemData ( QModelIndex() ); QVariant cache; - model->match(QModelIndex(), -1, cache); + model->match ( QModelIndex(), -1, cache ); model->mimeTypes(); - QVERIFY(model->parent(QModelIndex()) == QModelIndex()); - QVERIFY(model->rowCount() >= 0); + QVERIFY( model->parent ( QModelIndex() ) == QModelIndex() ); + QVERIFY( model->rowCount() >= 0 ); QVariant variant; - model->setData(QModelIndex(), variant, -1); - model->setHeaderData(-1, Qt::Horizontal, QVariant()); - model->setHeaderData(999999, Qt::Horizontal, QVariant()); + model->setData ( QModelIndex(), variant, -1 ); + model->setHeaderData ( -1, Qt::Horizontal, QVariant() ); + model->setHeaderData ( 999999, Qt::Horizontal, QVariant() ); QMap roles; - model->sibling(0, 0, QModelIndex()); - model->span(QModelIndex()); + model->sibling ( 0, 0, QModelIndex() ); + model->span ( QModelIndex() ); model->supportedDropActions(); } @@ -141,28 +156,29 @@ void ModelTest::nonDestructiveBasicTest() { Models that are dynamically populated are not as fully tested here. */ -void ModelTest::rowCount() { - // qDebug() << "rc"; +void ModelTest::rowCount() +{ +// qDebug() << "rc"; // check top row - QModelIndex topIndex = model->index(0, 0, QModelIndex()); - if (!model->hasChildren(topIndex) && model->rowCount(topIndex) > 0) { - qDebug() << "it's gonna blow: " << topIndex; - } + QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); + if (!model->hasChildren ( topIndex ) && model->rowCount(topIndex) > 0) { + qDebug() << "it's gonna blow: " << topIndex; + } - int rows = model->rowCount(topIndex); - QVERIFY(rows >= 0); - if (rows > 0) { - QVERIFY(model->hasChildren(topIndex)); + int rows = model->rowCount ( topIndex ); + QVERIFY( rows >= 0 ); + if ( rows > 0 ) { + QVERIFY( model->hasChildren ( topIndex ) ); } - QModelIndex secondLevelIndex = model->index(0, 0, topIndex); - if (secondLevelIndex.isValid()) { // not the top level + QModelIndex secondLevelIndex = model->index ( 0, 0, topIndex ); + if ( secondLevelIndex.isValid() ) { // not the top level // check a row count where parent is valid - rows = model->rowCount(secondLevelIndex); - QVERIFY(rows >= 0); - if (rows > 0) - QVERIFY(model->hasChildren(secondLevelIndex)); + rows = model->rowCount ( secondLevelIndex ); + QVERIFY( rows >= 0 ); + if ( rows > 0 ) + QVERIFY( model->hasChildren ( secondLevelIndex ) ); } // The models rowCount() is tested more extensively in checkChildren(), @@ -172,15 +188,16 @@ void ModelTest::rowCount() { /*! Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren() */ -void ModelTest::columnCount() { +void ModelTest::columnCount() +{ // check top row - QModelIndex topIndex = model->index(0, 0, QModelIndex()); - QVERIFY(model->columnCount(topIndex) >= 0); + QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); + QVERIFY( model->columnCount ( topIndex ) >= 0 ); // check a column count where parent is valid - QModelIndex childIndex = model->index(0, 0, topIndex); - if (childIndex.isValid()) - QVERIFY(model->columnCount(childIndex) >= 0); + QModelIndex childIndex = model->index ( 0, 0, topIndex ); + if ( childIndex.isValid() ) + QVERIFY( model->columnCount ( childIndex ) >= 0 ); // columnCount() is tested more extensively in checkChildren(), // but this catches the big mistakes @@ -189,22 +206,23 @@ void ModelTest::columnCount() { /*! Tests model's implementation of QAbstractItemModel::hasIndex() */ -void ModelTest::hasIndex() { - // qDebug() << "hi"; +void ModelTest::hasIndex() +{ +// qDebug() << "hi"; // Make sure that invalid values returns an invalid index - QVERIFY(!model->hasIndex(-2, -2)); - QVERIFY(!model->hasIndex(-2, 0)); - QVERIFY(!model->hasIndex(0, -2)); + QVERIFY( !model->hasIndex ( -2, -2 ) ); + QVERIFY( !model->hasIndex ( -2, 0 ) ); + QVERIFY( !model->hasIndex ( 0, -2 ) ); int rows = model->rowCount(); int columns = model->columnCount(); // check out of bounds - QVERIFY(!model->hasIndex(rows, columns)); - QVERIFY(!model->hasIndex(rows + 1, columns + 1)); + QVERIFY( !model->hasIndex ( rows, columns ) ); + QVERIFY( !model->hasIndex ( rows + 1, columns + 1 ) ); - if (rows > 0) - QVERIFY(model->hasIndex(0, 0)); + if ( rows > 0 ) + QVERIFY( model->hasIndex ( 0, 0 ) ); // hasIndex() is tested more extensively in checkChildren(), // but this catches the big mistakes @@ -213,31 +231,32 @@ void ModelTest::hasIndex() { /*! Tests model's implementation of QAbstractItemModel::index() */ -void ModelTest::index() { - // qDebug() << "i"; +void ModelTest::index() +{ +// qDebug() << "i"; // Make sure that invalid values returns an invalid index - /* QVERIFY( model->index ( -2, -2 ) == QModelIndex() ); - QVERIFY( model->index ( -2, 0 ) == QModelIndex() ); - QVERIFY( model->index ( 0, -2 ) == QModelIndex() );*/ - QVERIFY(!model->index(-2, -2).isValid()); - QVERIFY(!model->index(-2, 0).isValid()); - QVERIFY(!model->index(0, -2).isValid()); +/* QVERIFY( model->index ( -2, -2 ) == QModelIndex() ); + QVERIFY( model->index ( -2, 0 ) == QModelIndex() ); + QVERIFY( model->index ( 0, -2 ) == QModelIndex() );*/ + QVERIFY( !model->index ( -2, -2 ).isValid() ); + QVERIFY( !model->index ( -2, 0 ).isValid() ); + QVERIFY( !model->index ( 0, -2 ).isValid() ); int rows = model->rowCount(); int columns = model->columnCount(); - if (rows == 0) + if ( rows == 0 ) return; // Catch off by one errors - // QVERIFY( model->index ( rows, columns ) == QModelIndex() ); - QVERIFY(!model->index(rows, columns).isValid()); - QVERIFY(model->index(0, 0).isValid()); + //QVERIFY( model->index ( rows, columns ) == QModelIndex() ); + QVERIFY( !model->index ( rows, columns ).isValid() ); + QVERIFY( model->index ( 0, 0 ).isValid() ); // Make sure that the same index is *always* returned - QModelIndex a = model->index(0, 0); - QModelIndex b = model->index(0, 0); - QVERIFY(a == b); + QModelIndex a = model->index ( 0, 0 ); + QModelIndex b = model->index ( 0, 0 ); + QVERIFY( a == b ); // index() is tested more extensively in checkChildren(), // but this catches the big mistakes @@ -246,13 +265,14 @@ void ModelTest::index() { /*! Tests model's implementation of QAbstractItemModel::parent() */ -void ModelTest::parent() { - // qDebug() << "p"; +void ModelTest::parent() +{ +// qDebug() << "p"; // Make sure the model wont crash and will return an invalid QModelIndex // when asked for the parent of an invalid index. - QVERIFY(model->parent(QModelIndex()) == QModelIndex()); + QVERIFY( model->parent ( QModelIndex() ) == QModelIndex() ); - if (model->rowCount() == 0) + if ( model->rowCount() == 0 ) return; // Column 0 | Column 1 | @@ -262,29 +282,29 @@ void ModelTest::parent() { // Common error test #1, make sure that a top level index has a parent // that is a invalid QModelIndex. - QModelIndex topIndex = model->index(0, 0, QModelIndex()); - QVERIFY(model->parent(topIndex) == QModelIndex()); + QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); + QVERIFY( model->parent ( topIndex ) == QModelIndex() ); // Common error test #2, make sure that a second level index has a parent // that is the first level index. - if (model->rowCount(topIndex) > 0) { - QModelIndex childIndex = model->index(0, 0, topIndex); - QVERIFY(model->parent(childIndex) == topIndex); + if ( model->rowCount ( topIndex ) > 0 ) { + QModelIndex childIndex = model->index ( 0, 0, topIndex ); + QVERIFY( model->parent ( childIndex ) == topIndex ); } // Common error test #3, the second column should NOT have the same children // as the first column in a row. // Usually the second column shouldn't have children. - QModelIndex topIndex1 = model->index(0, 1, QModelIndex()); - if (model->rowCount(topIndex1) > 0) { - QModelIndex childIndex = model->index(0, 0, topIndex); - QModelIndex childIndex1 = model->index(0, 0, topIndex1); - QVERIFY(childIndex != childIndex1); + QModelIndex topIndex1 = model->index ( 0, 1, QModelIndex() ); + if ( model->rowCount ( topIndex1 ) > 0 ) { + QModelIndex childIndex = model->index ( 0, 0, topIndex ); + QModelIndex childIndex1 = model->index ( 0, 0, topIndex1 ); + QVERIFY( childIndex != childIndex1 ); } // Full test, walk n levels deep through the model making sure that all // parent's children correctly specify their parent. - checkChildren(QModelIndex()); + checkChildren ( QModelIndex() ); } /*! @@ -301,88 +321,90 @@ void ModelTest::parent() { found the basic bugs because it is easier to figure out the problem in those tests then this one. */ -void ModelTest::checkChildren(const QModelIndex& parent, int currentDepth) { +void ModelTest::checkChildren ( const QModelIndex &parent, int currentDepth ) +{ // First just try walking back up the tree. QModelIndex p = parent; - while (p.isValid()) + while ( p.isValid() ) p = p.parent(); // For models that are dynamically populated - if (model->canFetchMore(parent)) { + if ( model->canFetchMore ( parent ) ) { fetchingMore = true; - model->fetchMore(parent); + model->fetchMore ( parent ); fetchingMore = false; } - int rows = model->rowCount(parent); - int columns = model->columnCount(parent); + int rows = model->rowCount ( parent ); + int columns = model->columnCount ( parent ); - if (rows > 0) - QVERIFY(model->hasChildren(parent)); + if ( rows > 0 ) + QVERIFY( model->hasChildren ( parent ) ); // Some further testing against rows(), columns(), and hasChildren() - QVERIFY(rows >= 0); - QVERIFY(columns >= 0); - if (rows > 0) - QVERIFY(model->hasChildren(parent)); + QVERIFY( rows >= 0 ); + QVERIFY( columns >= 0 ); + if ( rows > 0 ) + QVERIFY( model->hasChildren ( parent ) ); - // qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows + //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows // << "columns:" << columns << "parent column:" << parent.column(); - QVERIFY(!model->hasIndex(rows + 1, 0, parent)); - for (int r = 0; r < rows; ++r) { - if (model->canFetchMore(parent)) { + QVERIFY( !model->hasIndex ( rows + 1, 0, parent ) ); + for ( int r = 0; r < rows; ++r ) { + if ( model->canFetchMore ( parent ) ) { fetchingMore = true; - model->fetchMore(parent); + model->fetchMore ( parent ); fetchingMore = false; } - QVERIFY(!model->hasIndex(r, columns + 1, parent)); - for (int c = 0; c < columns; ++c) { - QVERIFY(model->hasIndex(r, c, parent)); - QModelIndex index = model->index(r, c, parent); + QVERIFY( !model->hasIndex ( r, columns + 1, parent ) ); + for ( int c = 0; c < columns; ++c ) { + QVERIFY( model->hasIndex ( r, c, parent ) ); + QModelIndex index = model->index ( r, c, parent ); // rowCount() and columnCount() said that it existed... - QVERIFY(index.isValid()); + QVERIFY( index.isValid() ); // index() should always return the same index when called twice in a row - QModelIndex modifiedIndex = model->index(r, c, parent); - QVERIFY(index == modifiedIndex); + QModelIndex modifiedIndex = model->index ( r, c, parent ); + QVERIFY( index == modifiedIndex ); // Make sure we get the same index if we request it twice in a row - QModelIndex a = model->index(r, c, parent); - QModelIndex b = model->index(r, c, parent); - QVERIFY(a == b); + QModelIndex a = model->index ( r, c, parent ); + QModelIndex b = model->index ( r, c, parent ); + QVERIFY( a == b ); // Some basic checking on the index that is returned - QVERIFY(index.model() == model); - QCOMPARE(index.row(), r); - QCOMPARE(index.column(), c); + QVERIFY( index.model() == model ); + QCOMPARE( index.row(), r ); + QCOMPARE( index.column(), c ); // While you can technically return a QVariant usually this is a sign // of a bug in data(). Disable if this really is ok in your model. - // QVERIFY( model->data ( index, Qt::DisplayRole ).isValid() ); +// QVERIFY( model->data ( index, Qt::DisplayRole ).isValid() ); // If the next test fails here is some somewhat useful debug you play with. if (model->parent(index) != parent) { - qDebug() << r << c << currentDepth << model->data(index).toString() << model->data(parent).toString(); + qDebug() << r << c << currentDepth << model->data(index).toString() + << model->data(parent).toString(); qDebug() << index << parent << model->parent(index); - // And a view that you can even use to show the model. - // QTreeView view; - // view.setModel(model); - // view.show(); +// And a view that you can even use to show the model. +// QTreeView view; +// view.setModel(model); +// view.show(); } // Check that we can get back our real parent. - QCOMPARE(model->parent(index), parent); + QCOMPARE( model->parent ( index ), parent ); // recursively go down the children - if (model->hasChildren(index) && currentDepth < 10) { - // qDebug() << r << c << "has children" << model->rowCount(index); - checkChildren(index, ++currentDepth); - } /* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/ + if ( model->hasChildren ( index ) && currentDepth < 10 ) { + //qDebug() << r << c << "has children" << model->rowCount(index); + checkChildren ( index, ++currentDepth ); + }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/ // make sure that after testing the children that the index doesn't change. - QModelIndex newerIndex = model->index(r, c, parent); - QVERIFY(index == newerIndex); + QModelIndex newerIndex = model->index ( r, c, parent ); + QVERIFY( index == newerIndex ); } } } @@ -390,68 +412,71 @@ void ModelTest::checkChildren(const QModelIndex& parent, int currentDepth) { /*! Tests model's implementation of QAbstractItemModel::data() */ -void ModelTest::data() { +void ModelTest::data() +{ // Invalid index should return an invalid qvariant - QVERIFY(!model->data(QModelIndex()).isValid()); + QVERIFY( !model->data ( QModelIndex() ).isValid() ); - if (model->rowCount() == 0) + if ( model->rowCount() == 0 ) return; // A valid index should have a valid QVariant data - QVERIFY(model->index(0, 0).isValid()); + QVERIFY( model->index ( 0, 0 ).isValid() ); // shouldn't be able to set data on an invalid index - QVERIFY(!model->setData(QModelIndex(), QLatin1String("foo"), Qt::DisplayRole)); + QVERIFY( !model->setData ( QModelIndex(), QLatin1String ( "foo" ), Qt::DisplayRole ) ); // General Purpose roles that should return a QString - QVariant variant = model->data(model->index(0, 0), Qt::ToolTipRole); - if (variant.isValid()) { - QVERIFY(variant.canConvert(QMetaType::QString)); + QVariant variant = model->data ( model->index ( 0, 0 ), Qt::ToolTipRole ); + if ( variant.isValid() ) { + QVERIFY( variant.canConvert(QMetaType::QString) ); } - variant = model->data(model->index(0, 0), Qt::StatusTipRole); - if (variant.isValid()) { - QVERIFY(variant.canConvert(QMetaType::QString)); + variant = model->data ( model->index ( 0, 0 ), Qt::StatusTipRole ); + if ( variant.isValid() ) { + QVERIFY( variant.canConvert(QMetaType::QString) ); } - variant = model->data(model->index(0, 0), Qt::WhatsThisRole); - if (variant.isValid()) { - QVERIFY(variant.canConvert(QMetaType::QString)); + variant = model->data ( model->index ( 0, 0 ), Qt::WhatsThisRole ); + if ( variant.isValid() ) { + QVERIFY( variant.canConvert(QMetaType::QString) ); } // General Purpose roles that should return a QSize - variant = model->data(model->index(0, 0), Qt::SizeHintRole); - if (variant.isValid()) { - QVERIFY(variant.canConvert(QMetaType::QSize)); + variant = model->data ( model->index ( 0, 0 ), Qt::SizeHintRole ); + if ( variant.isValid() ) { + QVERIFY( variant.canConvert(QMetaType::QSize) ); } // General Purpose roles that should return a QFont - QVariant fontVariant = model->data(model->index(0, 0), Qt::FontRole); - if (fontVariant.isValid()) { - QVERIFY(fontVariant.canConvert(QMetaType::QFont)); + QVariant fontVariant = model->data ( model->index ( 0, 0 ), Qt::FontRole ); + if ( fontVariant.isValid() ) { + QVERIFY( fontVariant.canConvert(QMetaType::QFont) ); } // Check that the alignment is one we know about - QVariant textAlignmentVariant = model->data(model->index(0, 0), Qt::TextAlignmentRole); - if (textAlignmentVariant.isValid()) { + QVariant textAlignmentVariant = model->data ( model->index ( 0, 0 ), Qt::TextAlignmentRole ); + if ( textAlignmentVariant.isValid() ) { int alignment = textAlignmentVariant.toInt(); - QCOMPARE(alignment, (alignment & (Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask))); + QCOMPARE( alignment, ( alignment & ( Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask ) ) ); } // General Purpose roles that should return a QColor - QVariant colorVariant = model->data(model->index(0, 0), Qt::BackgroundColorRole); - if (colorVariant.isValid()) { - QVERIFY(colorVariant.canConvert(QMetaType::QColor)); + QVariant colorVariant = model->data ( model->index ( 0, 0 ), Qt::BackgroundColorRole ); + if ( colorVariant.isValid() ) { + QVERIFY( colorVariant.canConvert(QMetaType::QColor) ); } - colorVariant = model->data(model->index(0, 0), Qt::TextColorRole); - if (colorVariant.isValid()) { - QVERIFY(colorVariant.canConvert(QMetaType::QColor)); + colorVariant = model->data ( model->index ( 0, 0 ), Qt::TextColorRole ); + if ( colorVariant.isValid() ) { + QVERIFY( colorVariant.canConvert(QMetaType::QColor) ); } // Check that the "check state" is one we know about. - QVariant checkStateVariant = model->data(model->index(0, 0), Qt::CheckStateRole); - if (checkStateVariant.isValid()) { + QVariant checkStateVariant = model->data ( model->index ( 0, 0 ), Qt::CheckStateRole ); + if ( checkStateVariant.isValid() ) { int state = checkStateVariant.toInt(); - QVERIFY(state == Qt::Unchecked || state == Qt::PartiallyChecked || state == Qt::Checked); + QVERIFY( state == Qt::Unchecked || + state == Qt::PartiallyChecked || + state == Qt::Checked ); } } @@ -460,20 +485,19 @@ void ModelTest::data() { \sa rowsInserted() */ -void ModelTest::rowsAboutToBeInserted(const QModelIndex& parent, int start, int end) { - // Q_UNUSED(end); - // qDebug() << "rowsAboutToBeInserted" << "start=" << start << "end=" << end << "parent=" << model->data ( parent - // ).toString() - // << "current count of parent=" << model->rowCount ( parent ); // << "display of last=" << model->data( - // model->index(start-1, 0, parent) ); - // qDebug() << model->index(start-1, 0, parent) << model->data( model->index(start-1, 0, parent) ); +void ModelTest::rowsAboutToBeInserted ( const QModelIndex &parent, int start, int end ) +{ +// Q_UNUSED(end); +// qDebug() << "rowsAboutToBeInserted" << "start=" << start << "end=" << end << "parent=" << model->data ( parent ).toString() +// << "current count of parent=" << model->rowCount ( parent ); // << "display of last=" << model->data( model->index(start-1, 0, parent) ); +// qDebug() << model->index(start-1, 0, parent) << model->data( model->index(start-1, 0, parent) ); Changing c; c.parent = parent; - c.oldSize = model->rowCount(parent); - c.last = model->data(model->index(start - 1, 0, parent)); - c.next = model->data(model->index(start, 0, parent)); - qDebug() << start << " - " << parent << " - " << model->data(model->index(start, 0, parent), Qt::UserRole); - insert.push(c); + c.oldSize = model->rowCount ( parent ); + c.last = model->data ( model->index ( start - 1, 0, parent ) ); + c.next = model->data ( model->index ( start, 0, parent ) ); +qDebug() << start << " - " << parent << " - " << model->data ( model->index ( start, 0, parent ), Qt::UserRole ); + insert.push ( c ); } /*! @@ -481,44 +505,46 @@ void ModelTest::rowsAboutToBeInserted(const QModelIndex& parent, int start, int \sa rowsAboutToBeInserted() */ -void ModelTest::rowsInserted(const QModelIndex& parent, int start, int end) { +void ModelTest::rowsInserted ( const QModelIndex & parent, int start, int end ) +{ Changing c = insert.pop(); - QVERIFY(c.parent == parent); - // qDebug() << "rowsInserted" << "start=" << start << "end=" << end << "oldsize=" << c.oldSize - // << "parent=" << model->data ( parent ).toString() << "current rowcount of parent=" << model->rowCount ( parent - // ); + QVERIFY( c.parent == parent ); +// qDebug() << "rowsInserted" << "start=" << start << "end=" << end << "oldsize=" << c.oldSize +// << "parent=" << model->data ( parent ).toString() << "current rowcount of parent=" << model->rowCount ( parent ); - // for (int ii=start; ii <= end; ii++) - // { - // qDebug() << "itemWasInserted:" << ii << model->data ( model->index ( ii, 0, parent )); - // } - // qDebug(); +// for (int ii=start; ii <= end; ii++) +// { +// qDebug() << "itemWasInserted:" << ii << model->data ( model->index ( ii, 0, parent )); +// } +// qDebug(); - QVERIFY(c.oldSize + (end - start + 1) == model->rowCount(parent)); - QVERIFY(c.last == model->data(model->index(start - 1, 0, c.parent))); + QVERIFY( c.oldSize + ( end - start + 1 ) == model->rowCount ( parent ) ); + QVERIFY( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) ); if (c.next != model->data(model->index(end + 1, 0, c.parent))) { qDebug() << start << end; - for (int i = 0; i < model->rowCount(); ++i) + for (int i=0; i < model->rowCount(); ++i) qDebug() << model->index(i, 0).data().toString(); qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent)); } - if (c.next != model->data(model->index(end + 1, 0, c.parent))) { - qDebug("break"); - } - QVERIFY(c.next == model->data(model->index(end + 1, 0, c.parent))); +if (c.next != model->data ( model->index ( end + 1, 0, c.parent ) )) { + qDebug("break"); +} + QVERIFY( c.next == model->data ( model->index ( end + 1, 0, c.parent ) ) ); } -void ModelTest::layoutAboutToBeChanged() { - for (int i = 0; i < qBound(0, model->rowCount(), 100); ++i) - changing.append(QPersistentModelIndex(model->index(i, 0))); +void ModelTest::layoutAboutToBeChanged() +{ + for ( int i = 0; i < qBound ( 0, model->rowCount(), 100 ); ++i ) + changing.append ( QPersistentModelIndex ( model->index ( i, 0 ) ) ); } -void ModelTest::layoutChanged() { - for (int i = 0; i < changing.count(); ++i) { +void ModelTest::layoutChanged() +{ + for ( int i = 0; i < changing.count(); ++i ) { QPersistentModelIndex p = changing[i]; - QVERIFY(p == model->index(p.row(), p.column(), p.parent())); + QVERIFY( p == model->index ( p.row(), p.column(), p.parent() ) ); } changing.clear(); } @@ -528,14 +554,15 @@ void ModelTest::layoutChanged() { \sa rowsRemoved() */ -void ModelTest::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { - qDebug() << "ratbr" << parent << start << end; +void ModelTest::rowsAboutToBeRemoved ( const QModelIndex &parent, int start, int end ) +{ +qDebug() << "ratbr" << parent << start << end; Changing c; c.parent = parent; - c.oldSize = model->rowCount(parent); - c.last = model->data(model->index(start - 1, 0, parent)); - c.next = model->data(model->index(end + 1, 0, parent)); - remove.push(c); + c.oldSize = model->rowCount ( parent ); + c.last = model->data ( model->index ( start - 1, 0, parent ) ); + c.next = model->data ( model->index ( end + 1, 0, parent ) ); + remove.push ( c ); } /*! @@ -543,11 +570,14 @@ void ModelTest::rowsAboutToBeRemoved(const QModelIndex& parent, int start, int e \sa rowsAboutToBeRemoved() */ -void ModelTest::rowsRemoved(const QModelIndex& parent, int start, int end) { - qDebug() << "rr" << parent << start << end; +void ModelTest::rowsRemoved ( const QModelIndex & parent, int start, int end ) +{ + qDebug() << "rr" << parent << start << end; Changing c = remove.pop(); - QVERIFY(c.parent == parent); - QVERIFY(c.oldSize - (end - start + 1) == model->rowCount(parent)); - QVERIFY(c.last == model->data(model->index(start - 1, 0, c.parent))); - QVERIFY(c.next == model->data(model->index(start, 0, c.parent))); + QVERIFY( c.parent == parent ); + QVERIFY( c.oldSize - ( end - start + 1 ) == model->rowCount ( parent ) ); + QVERIFY( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) ); + QVERIFY( c.next == model->data ( model->index ( start, 0, c.parent ) ) ); } + + diff --git a/src/modeltest.h b/src/modeltest.h index b0197a1a..68090f3f 100644 --- a/src/modeltest.h +++ b/src/modeltest.h @@ -39,54 +39,56 @@ ** ****************************************************************************/ + #ifndef MODELTEST_H #define MODELTEST_H -#include #include +#include #include -class ModelTest : public QObject { - Q_OBJECT +class ModelTest : public QObject +{ + Q_OBJECT public: - ModelTest(QAbstractItemModel* model, QObject* parent = 0); + ModelTest( QAbstractItemModel *model, QObject *parent = 0 ); private Q_SLOTS: - void nonDestructiveBasicTest(); - void rowCount(); - void columnCount(); - void hasIndex(); - void index(); - void parent(); - void data(); + void nonDestructiveBasicTest(); + void rowCount(); + void columnCount(); + void hasIndex(); + void index(); + void parent(); + void data(); protected Q_SLOTS: - void runAllTests(); - void layoutAboutToBeChanged(); - void layoutChanged(); - void rowsAboutToBeInserted(const QModelIndex& parent, int start, int end); - void rowsInserted(const QModelIndex& parent, int start, int end); - void rowsAboutToBeRemoved(const QModelIndex& parent, int start, int end); - void rowsRemoved(const QModelIndex& parent, int start, int end); + void runAllTests(); + void layoutAboutToBeChanged(); + void layoutChanged(); + void rowsAboutToBeInserted( const QModelIndex &parent, int start, int end ); + void rowsInserted( const QModelIndex & parent, int start, int end ); + void rowsAboutToBeRemoved( const QModelIndex &parent, int start, int end ); + void rowsRemoved( const QModelIndex & parent, int start, int end ); private: - void checkChildren(const QModelIndex& parent, int currentDepth = 0); + void checkChildren( const QModelIndex &parent, int currentDepth = 0 ); - QAbstractItemModel* model; + QAbstractItemModel *model; - struct Changing { - QModelIndex parent; - int oldSize; - QVariant last; - QVariant next; - }; - QStack insert; - QStack remove; + struct Changing { + QModelIndex parent; + int oldSize; + QVariant last; + QVariant next; + }; + QStack insert; + QStack remove; - bool fetchingMore; + bool fetchingMore; - QList changing; + QList changing; }; #endif diff --git a/src/modflagicondelegate.cpp b/src/modflagicondelegate.cpp index c80f7b8c..61df0a0d 100644 --- a/src/modflagicondelegate.cpp +++ b/src/modflagicondelegate.cpp @@ -1,85 +1,87 @@ -#include "modflagicondelegate.h" + #include "modflagicondelegate.h" #include -ModInfo::EFlag ModFlagIconDelegate::m_ConflictFlags[4] = { - ModInfo::FLAG_CONFLICT_MIXED, ModInfo::FLAG_CONFLICT_OVERWRITE, ModInfo::FLAG_CONFLICT_OVERWRITTEN, - ModInfo::FLAG_CONFLICT_REDUNDANT}; -ModFlagIconDelegate::ModFlagIconDelegate(QObject* parent) : IconDelegate(parent) {} +ModInfo::EFlag ModFlagIconDelegate::m_ConflictFlags[4] = { ModInfo::FLAG_CONFLICT_MIXED + , ModInfo::FLAG_CONFLICT_OVERWRITE + , ModInfo::FLAG_CONFLICT_OVERWRITTEN + , ModInfo::FLAG_CONFLICT_REDUNDANT }; -QList ModFlagIconDelegate::getIcons(const QModelIndex& index) const { - QList result; - QVariant modid = index.data(Qt::UserRole + 1); - if (modid.isValid()) { - ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); - std::vector flags = info->getFlags(); +ModFlagIconDelegate::ModFlagIconDelegate(QObject *parent) + : IconDelegate(parent) +{ +} - { // insert conflict icon first to provide nicer alignment - auto iter = std::find_first_of(flags.begin(), flags.end(), m_ConflictFlags, m_ConflictFlags + 4); - if (iter != flags.end()) { - result.append(getFlagIcon(*iter)); - flags.erase(iter); - } else { - result.append(QString()); - } - } +QList ModFlagIconDelegate::getIcons(const QModelIndex &index) const { + QList result; + QVariant modid = index.data(Qt::UserRole + 1); + if (modid.isValid()) { + ModInfo::Ptr info = ModInfo::getByIndex(modid.toInt()); + std::vector flags = info->getFlags(); - for (auto iter = flags.begin(); iter != flags.end(); ++iter) { - result.append(getFlagIcon(*iter)); - } + { // insert conflict icon first to provide nicer alignment + auto iter = std::find_first_of(flags.begin(), flags.end(), + m_ConflictFlags, m_ConflictFlags + 4); + if (iter != flags.end()) { + result.append(getFlagIcon(*iter)); + flags.erase(iter); + } else { + result.append(QString()); + } } - return result; -} -QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) const { - switch (flag) { - case ModInfo::FLAG_BACKUP: - return ":/MO/gui/emblem_backup"; - case ModInfo::FLAG_INVALID: - return ":/MO/gui/problem"; - case ModInfo::FLAG_NOTENDORSED: - return ":/MO/gui/emblem_notendorsed"; - case ModInfo::FLAG_NOTES: - return ":/MO/gui/emblem_notes"; - case ModInfo::FLAG_CONFLICT_OVERWRITE: - return ":/MO/gui/emblem_conflict_overwrite"; - case ModInfo::FLAG_CONFLICT_OVERWRITTEN: - return ":/MO/gui/emblem_conflict_overwritten"; - case ModInfo::FLAG_CONFLICT_MIXED: - return ":/MO/gui/emblem_conflict_mixed"; - case ModInfo::FLAG_CONFLICT_REDUNDANT: - return ":MO/gui/emblem_conflict_redundant"; - default: - return QString(); + for (auto iter = flags.begin(); iter != flags.end(); ++iter) { + result.append(getFlagIcon(*iter)); } + } + return result; } -size_t ModFlagIconDelegate::getNumIcons(const QModelIndex& index) const { - unsigned int modIdx = index.data(Qt::UserRole + 1).toInt(); - if (modIdx < ModInfo::getNumMods()) { - ModInfo::Ptr info = ModInfo::getByIndex(modIdx); - std::vector flags = info->getFlags(); - size_t count = flags.size(); - if (std::find_first_of(flags.begin(), flags.end(), m_ConflictFlags, m_ConflictFlags + 4) == flags.end()) { - ++count; - } - return count; - } else { - return 0; - } +QString ModFlagIconDelegate::getFlagIcon(ModInfo::EFlag flag) const +{ + switch (flag) { + case ModInfo::FLAG_BACKUP: return ":/MO/gui/emblem_backup"; + case ModInfo::FLAG_INVALID: return ":/MO/gui/problem"; + case ModInfo::FLAG_NOTENDORSED: return ":/MO/gui/emblem_notendorsed"; + case ModInfo::FLAG_NOTES: return ":/MO/gui/emblem_notes"; + case ModInfo::FLAG_CONFLICT_OVERWRITE: return ":/MO/gui/emblem_conflict_overwrite"; + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return ":/MO/gui/emblem_conflict_overwritten"; + case ModInfo::FLAG_CONFLICT_MIXED: return ":/MO/gui/emblem_conflict_mixed"; + case ModInfo::FLAG_CONFLICT_REDUNDANT: return ":MO/gui/emblem_conflict_redundant"; + default: return QString(); + } } -QSize ModFlagIconDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const { - size_t count = getNumIcons(modelIndex); - unsigned int index = modelIndex.data(Qt::UserRole + 1).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())); +size_t ModFlagIconDelegate::getNumIcons(const QModelIndex &index) const +{ + unsigned int modIdx = index.data(Qt::UserRole + 1).toInt(); + if (modIdx < ModInfo::getNumMods()) { + ModInfo::Ptr info = ModInfo::getByIndex(modIdx); + std::vector flags = info->getFlags(); + size_t count = flags.size(); + if (std::find_first_of(flags.begin(), flags.end(), m_ConflictFlags, m_ConflictFlags + 4) == flags.end()) { + ++count; } - return result; + return count; + } else { + return 0; + } } + + +QSize ModFlagIconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const +{ + size_t count = getNumIcons(modelIndex); + unsigned int index = modelIndex.data(Qt::UserRole + 1).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 5ee43a90..b31a250b 100644 --- a/src/modflagicondelegate.h +++ b/src/modflagicondelegate.h @@ -3,19 +3,18 @@ #include "icondelegate.h" -class ModFlagIconDelegate : public IconDelegate { +class ModFlagIconDelegate : public IconDelegate +{ public: - explicit ModFlagIconDelegate(QObject* parent = 0); - virtual QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const; - + explicit ModFlagIconDelegate(QObject *parent = 0); + virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; private: - virtual QList getIcons(const QModelIndex& index) const; - virtual size_t getNumIcons(const QModelIndex& index) const; - - QString getFlagIcon(ModInfo::EFlag flag) const; + virtual QList getIcons(const QModelIndex &index) const; + virtual size_t getNumIcons(const QModelIndex &index) const; + QString getFlagIcon(ModInfo::EFlag flag) const; private: - static ModInfo::EFlag m_ConflictFlags[4]; + static ModInfo::EFlag m_ConflictFlags[4]; }; #endif // MODFLAGICONDELEGATE_H diff --git a/src/modinfo.cpp b/src/modinfo.cpp index 0b9d65f8..c14eedb7 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -20,22 +20,22 @@ 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 "categories.h" -#include "filenamestring.h" #include "installationtester.h" +#include "categories.h" #include "modinfodialog.h" #include "overwriteinfodialog.h" +#include "filenamestring.h" #include "versioninfo.h" -#include #include +#include +#include #include #include -#include #include #include @@ -45,317 +45,353 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; + std::vector ModInfo::s_Collection; std::map ModInfo::s_ModsByName; -std::map> ModInfo::s_ModsByModID; +std::map > ModInfo::s_ModsByModID; int ModInfo::s_NextID; QMutex ModInfo::s_Mutex(QMutex::Recursive); QString ModInfo::s_HiddenExt(".mohidden"); -static bool ByName(const ModInfo::Ptr& LHS, const ModInfo::Ptr& RHS) { - return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0; -} -ModInfo::Ptr ModInfo::createFrom(const QDir& dir, DirectoryEntry** directoryStructure) { - QMutexLocker locker(&s_Mutex); - // int id = s_NextID++; - static QRegExp backupExp(".*backup[0-9]*"); - ModInfo::Ptr result; - if (backupExp.exactMatch(dir.dirName())) { - result = ModInfo::Ptr(new ModInfoBackup(dir, directoryStructure)); - } else { - result = ModInfo::Ptr(new ModInfoRegular(dir, directoryStructure)); - } - s_Collection.push_back(result); - return result; +static bool ByName(const ModInfo::Ptr &LHS, const ModInfo::Ptr &RHS) +{ + return QString::compare(LHS->name(), RHS->name(), Qt::CaseInsensitive) < 0; } -ModInfo::Ptr ModInfo::createFromPlugin(const QString& modName, const QString& espName, const QStringList& bsaNames, - ModInfo::EModType modType, DirectoryEntry** directoryStructure) { - QMutexLocker locker(&s_Mutex); - ModInfo::Ptr result = ModInfo::Ptr(new ModInfoForeign(modName, espName, bsaNames, modType, directoryStructure)); - s_Collection.push_back(result); - return result; -} -QString ModInfo::getContentTypeName(int contentType) { - switch (contentType) { - case CONTENT_PLUGIN: - return tr("Plugins"); - case CONTENT_TEXTURE: - return tr("Textures"); - case CONTENT_MESH: - return tr("Meshes"); - case CONTENT_BSA: - return tr("Bethesda Archive"); - case CONTENT_INTERFACE: - return tr("UI Changes"); - case CONTENT_SOUND: - return tr("Sound Effects"); - case CONTENT_SCRIPT: - return tr("Scripts"); - case CONTENT_SKSE: - return tr("SKSE Plugins"); - case CONTENT_SKYPROC: - return tr("SkyProc Tools"); - case CONTENT_MCM: - return tr("MCM Data"); - default: - throw MyException(tr("invalid content type %1").arg(contentType)); - } +ModInfo::Ptr ModInfo::createFrom(const QDir &dir, DirectoryEntry **directoryStructure) +{ + QMutexLocker locker(&s_Mutex); +// int id = s_NextID++; + static QRegExp backupExp(".*backup[0-9]*"); + ModInfo::Ptr result; + if (backupExp.exactMatch(dir.dirName())) { + result = ModInfo::Ptr(new ModInfoBackup(dir, directoryStructure)); + } else { + result = ModInfo::Ptr(new ModInfoRegular(dir, directoryStructure)); + } + s_Collection.push_back(result); + return result; } -void ModInfo::createFromOverwrite() { - QMutexLocker locker(&s_Mutex); - - s_Collection.push_back(ModInfo::Ptr(new ModInfoOverwrite)); +ModInfo::Ptr ModInfo::createFromPlugin(const QString &modName, + const QString &espName, + const QStringList &bsaNames, + ModInfo::EModType modType, + DirectoryEntry **directoryStructure) { + QMutexLocker locker(&s_Mutex); + ModInfo::Ptr result = ModInfo::Ptr( + new ModInfoForeign(modName, espName, bsaNames, modType, directoryStructure)); + s_Collection.push_back(result); + return result; } -unsigned int ModInfo::getNumMods() { - QMutexLocker locker(&s_Mutex); - return static_cast(s_Collection.size()); +QString ModInfo::getContentTypeName(int contentType) +{ + switch (contentType) { + case CONTENT_PLUGIN: return tr("Plugins"); + case CONTENT_TEXTURE: return tr("Textures"); + case CONTENT_MESH: return tr("Meshes"); + case CONTENT_BSA: return tr("Bethesda Archive"); + case CONTENT_INTERFACE: return tr("UI Changes"); + case CONTENT_SOUND: return tr("Sound Effects"); + case CONTENT_SCRIPT: return tr("Scripts"); + case CONTENT_SKSE: return tr("SKSE Plugins"); + case CONTENT_SKYPROC: return tr("SkyProc Tools"); + case CONTENT_MCM: return tr("MCM Data"); + default: throw MyException(tr("invalid content type %1").arg(contentType)); + } } -ModInfo::Ptr ModInfo::getByIndex(unsigned int index) { - QMutexLocker locker(&s_Mutex); +void ModInfo::createFromOverwrite() +{ + QMutexLocker locker(&s_Mutex); - 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")]; - return s_Collection[index]; + s_Collection.push_back(ModInfo::Ptr(new ModInfoOverwrite)); } -std::vector ModInfo::getByModID(int modID) { - QMutexLocker locker(&s_Mutex); +unsigned int ModInfo::getNumMods() +{ + QMutexLocker locker(&s_Mutex); + return static_cast(s_Collection.size()); +} - auto iter = s_ModsByModID.find(modID); - if (iter == s_ModsByModID.end()) { - return std::vector(); - } - std::vector result; - for (auto idxIter = iter->second.begin(); idxIter != iter->second.end(); ++idxIter) { - result.push_back(getByIndex(*idxIter)); - } +ModInfo::Ptr ModInfo::getByIndex(unsigned int index) +{ + QMutexLocker locker(&s_Mutex); - return result; + 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")]; + return s_Collection[index]; } -bool ModInfo::removeMod(unsigned int index) { - QMutexLocker locker(&s_Mutex); - if (index >= s_Collection.size()) { - throw MyException(tr("remove: invalid mod index %1").arg(index)); - } - // update the indices first - ModInfo::Ptr modInfo = s_Collection[index]; - s_ModsByName.erase(s_ModsByName.find(modInfo->name())); - - auto iter = s_ModsByModID.find(modInfo->getNexusID()); - if (iter != s_ModsByModID.end()) { - std::vector indices = iter->second; - indices.erase(std::remove(indices.begin(), indices.end(), index), indices.end()); - s_ModsByModID[modInfo->getNexusID()] = indices; - } +std::vector ModInfo::getByModID(int modID) +{ + QMutexLocker locker(&s_Mutex); - // physically remove the mod directory - // TODO the return value is ignored because the indices were already removed here, so stopping - // would cause data inconsistencies. Instead we go through with the removal but the mod will show up - // again if the user refreshes - modInfo->remove(); + auto iter = s_ModsByModID.find(modID); + if (iter == s_ModsByModID.end()) { + return std::vector(); + } - // finally, remove the mod from the collection - s_Collection.erase(s_Collection.begin() + index); + std::vector result; + for (auto idxIter = iter->second.begin(); idxIter != iter->second.end(); ++idxIter) { + result.push_back(getByIndex(*idxIter)); + } - // and update the indices - updateIndices(); - return true; + return result; } -unsigned int ModInfo::getIndex(const QString& name) { - QMutexLocker locker(&s_Mutex); - - std::map::iterator iter = s_ModsByName.find(name); - if (iter == s_ModsByName.end()) { - return UINT_MAX; - } - return iter->second; +bool ModInfo::removeMod(unsigned int index) +{ + QMutexLocker locker(&s_Mutex); + + if (index >= s_Collection.size()) { + throw MyException(tr("remove: invalid mod index %1").arg(index)); + } + // update the indices first + ModInfo::Ptr modInfo = s_Collection[index]; + s_ModsByName.erase(s_ModsByName.find(modInfo->name())); + + auto iter = s_ModsByModID.find(modInfo->getNexusID()); + if (iter != s_ModsByModID.end()) { + std::vector indices = iter->second; + indices.erase(std::remove(indices.begin(), indices.end(), index), indices.end()); + s_ModsByModID[modInfo->getNexusID()] = indices; + } + + // physically remove the mod directory + //TODO the return value is ignored because the indices were already removed here, so stopping + // would cause data inconsistencies. Instead we go through with the removal but the mod will show up + // again if the user refreshes + modInfo->remove(); + + // finally, remove the mod from the collection + s_Collection.erase(s_Collection.begin() + index); + + // and update the indices + updateIndices(); + return true; } -unsigned int ModInfo::findMod(const boost::function& filter) { - for (unsigned int i = 0U; i < s_Collection.size(); ++i) { - if (filter(s_Collection[i])) { - return i; - } - } + +unsigned int ModInfo::getIndex(const QString &name) +{ + QMutexLocker locker(&s_Mutex); + + std::map::iterator iter = s_ModsByName.find(name); + if (iter == s_ModsByName.end()) { return UINT_MAX; + } + + return iter->second; } -void ModInfo::updateFromDisc(const QString& modDirectory, DirectoryEntry** directoryStructure, bool displayForeign, - MOBase::IPluginGame const* game) { - QMutexLocker lock(&s_Mutex); - s_Collection.clear(); - s_NextID = 0; - - { // list all directories in the mod directory and make a mod out of each - QDir mods(QDir::fromNativeSeparators(modDirectory)); - mods.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); - QDirIterator modIter(mods); - while (modIter.hasNext()) { - createFrom(QDir(modIter.next()), directoryStructure); - } +unsigned int ModInfo::findMod(const boost::function &filter) +{ + for (unsigned int i = 0U; i < s_Collection.size(); ++i) { + if (filter(s_Collection[i])) { + return i; } + } + return UINT_MAX; +} + - 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); - - createFromPlugin(unmanaged->displayName(modName), unmanaged->referenceFile(modName).absoluteFilePath(), - unmanaged->secondaryFiles(modName), modType, directoryStructure); - } +void ModInfo::updateFromDisc(const QString &modDirectory, + DirectoryEntry **directoryStructure, + bool displayForeign, + MOBase::IPluginGame const *game) +{ + QMutexLocker lock(&s_Mutex); + s_Collection.clear(); + s_NextID = 0; + + { // list all directories in the mod directory and make a mod out of each + QDir mods(QDir::fromNativeSeparators(modDirectory)); + mods.setFilter(QDir::Dirs | QDir::NoDotAndDotDot); + QDirIterator modIter(mods); + while (modIter.hasNext()) { + createFrom(QDir(modIter.next()), directoryStructure); + } + } + + 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); + + createFromPlugin(unmanaged->displayName(modName), + unmanaged->referenceFile(modName).absoluteFilePath(), + unmanaged->secondaryFiles(modName), + modType, + directoryStructure); } + } - createFromOverwrite(); + createFromOverwrite(); - std::sort(s_Collection.begin(), s_Collection.end(), ByName); + std::sort(s_Collection.begin(), s_Collection.end(), ByName); - updateIndices(); + 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(); - int modID = s_Collection[i]->getNexusID(); - s_ModsByName[modName] = i; - s_ModsByModID[modID].push_back(i); - } +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(); + int modID = s_Collection[i]->getNexusID(); + s_ModsByName[modName] = i; + s_ModsByModID[modID].push_back(i); + } } -ModInfo::ModInfo() : m_Valid(false), m_PrimaryCategory(-1) {} -void ModInfo::checkChunkForUpdate(const std::vector& modIDs, QObject* receiver) { - if (modIDs.size() != 0) { - NexusInterface::instance()->requestUpdates(modIDs, receiver, QVariant(), QString()); - } +ModInfo::ModInfo() + : m_Valid(false), m_PrimaryCategory(-1) +{ +} + + +void ModInfo::checkChunkForUpdate(const std::vector &modIDs, QObject *receiver) +{ + if (modIDs.size() != 0) { + NexusInterface::instance()->requestUpdates(modIDs, receiver, QVariant(), QString()); + } } -int ModInfo::checkAllForUpdate(QObject* receiver) { - // technically this should be 255 but those requests can take nexus fairly long, produce - // large output and may have been the cause of issue #1166 - static const int chunkSize = 64; - int result = 0; - std::vector modIDs; +int ModInfo::checkAllForUpdate(QObject *receiver) +{ + // technically this should be 255 but those requests can take nexus fairly long, produce + // large output and may have been the cause of issue #1166 + static const int chunkSize = 64; - // I ought to store this, it's used elsewhere - IPluginGame const* game = qApp->property("managed_game").value(); + int result = 0; + std::vector modIDs; - modIDs.push_back(game->nexusModOrganizerID()); + //I ought to store this, it's used elsewhere + IPluginGame const *game = qApp->property("managed_game").value(); - for (const ModInfo::Ptr& mod : s_Collection) { - if (mod->canBeUpdated()) { - modIDs.push_back(mod->getNexusID()); - if (modIDs.size() >= chunkSize) { - checkChunkForUpdate(modIDs, receiver); - modIDs.clear(); - } - } + modIDs.push_back(game->nexusModOrganizerID()); + + for (const ModInfo::Ptr &mod : s_Collection) { + if (mod->canBeUpdated()) { + modIDs.push_back(mod->getNexusID()); + if (modIDs.size() >= chunkSize) { + checkChunkForUpdate(modIDs, receiver); + modIDs.clear(); + } } + } - checkChunkForUpdate(modIDs, receiver); + checkChunkForUpdate(modIDs, receiver); - return result; + return result; } -void ModInfo::setVersion(const VersionInfo& version) { m_Version = version; } +void ModInfo::setVersion(const VersionInfo &version) +{ + m_Version = version; +} -void ModInfo::setPluginSelected(const bool& isSelected) { m_PluginSelected = isSelected; } +void ModInfo::setPluginSelected(const bool &isSelected) +{ + m_PluginSelected = isSelected; +} -void ModInfo::addCategory(const QString& categoryName) { - int id = CategoryFactory::instance().getCategoryID(categoryName); - if (id == -1) { - id = CategoryFactory::instance().addCategory(categoryName, std::vector(), 0); - } - setCategory(id, true); +void ModInfo::addCategory(const QString &categoryName) +{ + int id = CategoryFactory::instance().getCategoryID(categoryName); + if (id == -1) { + id = CategoryFactory::instance().addCategory(categoryName, std::vector(), 0); + } + setCategory(id, true); } -bool ModInfo::removeCategory(const QString& categoryName) { - int id = CategoryFactory::instance().getCategoryID(categoryName); - if (id == -1) { - return false; - } - if (!categorySet(id)) { - return false; - } - setCategory(id, false); - return true; +bool ModInfo::removeCategory(const QString &categoryName) +{ + int id = CategoryFactory::instance().getCategoryID(categoryName); + if (id == -1) { + return false; + } + if (!categorySet(id)) { + return false; + } + setCategory(id, false); + return true; } -QStringList ModInfo::categories() { - QStringList result; +QStringList ModInfo::categories() +{ + QStringList result; - CategoryFactory& catFac = CategoryFactory::instance(); - for (int id : m_Categories) { - result.append(catFac.getCategoryName(catFac.getCategoryIndex(id))); - } + CategoryFactory &catFac = CategoryFactory::instance(); + for (int id : m_Categories) { + result.append(catFac.getCategoryName(catFac.getCategoryIndex(id))); + } - return result; + return result; } -bool ModInfo::hasFlag(ModInfo::EFlag flag) const { - std::vector flags = getFlags(); - return std::find(flags.begin(), flags.end(), flag) != flags.end(); +bool ModInfo::hasFlag(ModInfo::EFlag flag) const +{ + std::vector flags = getFlags(); + return std::find(flags.begin(), flags.end(), flag) != flags.end(); } -bool ModInfo::hasContent(ModInfo::EContent content) const { - std::vector contents = getContents(); - return std::find(contents.begin(), contents.end(), content) != contents.end(); +bool ModInfo::hasContent(ModInfo::EContent content) const +{ + std::vector contents = getContents(); + return std::find(contents.begin(), contents.end(), content) != contents.end(); } -bool ModInfo::categorySet(int categoryID) const { - for (std::set::const_iterator iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { - if ((*iter == categoryID) || (CategoryFactory::instance().isDecendantOf(*iter, categoryID))) { - return true; - } +bool ModInfo::categorySet(int categoryID) const +{ + for (std::set::const_iterator iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { + if ((*iter == categoryID) || + (CategoryFactory::instance().isDecendantOf(*iter, categoryID))) { + return true; } + } - return false; + return false; } -void ModInfo::testValid() { - m_Valid = false; - QDirIterator dirIter(absolutePath()); - while (dirIter.hasNext()) { - dirIter.next(); - if (dirIter.fileInfo().isDir()) { - if (InstallationTester::isTopLevelDirectory(dirIter.fileName())) { - m_Valid = true; - break; - } - } else { - if (InstallationTester::isTopLevelSuffix(dirIter.fileName())) { - m_Valid = true; - break; - } - } +void ModInfo::testValid() +{ + m_Valid = false; + QDirIterator dirIter(absolutePath()); + while (dirIter.hasNext()) { + dirIter.next(); + if (dirIter.fileInfo().isDir()) { + if (InstallationTester::isTopLevelDirectory(dirIter.fileName())) { + m_Valid = true; + break; + } + } else { + if (InstallationTester::isTopLevelSuffix(dirIter.fileName())) { + m_Valid = true; + break; + } } + } - // NOTE: in Qt 4.7 it seems that QDirIterator leaves a file handle open if it is not iterated to the - // end - while (dirIter.hasNext()) { - dirIter.next(); - } + // NOTE: in Qt 4.7 it seems that QDirIterator leaves a file handle open if it is not iterated to the + // end + while (dirIter.hasNext()) { + dirIter.next(); + } } diff --git a/src/modinfo.h b/src/modinfo.h index 2c5cdf9a..0bdf6e43 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -36,584 +36,595 @@ class QDir; #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. - * + * * Represents meta information about a single mod. The class interface is used * to manage the mod collection * **/ -class ModInfo : public QObject, public MOBase::IModInterface { +class ModInfo : public QObject, public MOBase::IModInterface +{ - Q_OBJECT + Q_OBJECT public: - typedef QSharedPointer Ptr; - - static QString s_HiddenExt; - - enum EFlag { - FLAG_INVALID, - FLAG_BACKUP, - FLAG_OVERWRITE, - FLAG_FOREIGN, - FLAG_NOTENDORSED, - FLAG_NOTES, - FLAG_CONFLICT_OVERWRITE, - FLAG_CONFLICT_OVERWRITTEN, - FLAG_CONFLICT_MIXED, - FLAG_CONFLICT_REDUNDANT, - FLAG_PLUGIN_SELECTED - }; - - enum EContent { - CONTENT_PLUGIN, - CONTENT_TEXTURE, - CONTENT_MESH, - CONTENT_BSA, - CONTENT_INTERFACE, - CONTENT_SOUND, - CONTENT_SCRIPT, - CONTENT_SKSE, - CONTENT_SKYPROC, - CONTENT_MCM - }; - - static const int NUM_CONTENT_TYPES = CONTENT_SKYPROC + 1; - - enum EHighlight { - HIGHLIGHT_NONE = 0, - HIGHLIGHT_INVALID = 1, - HIGHLIGHT_CENTER = 2, - HIGHLIGHT_IMPORTANT = 4, - HIGHLIGHT_PLUGIN = 8 - }; - - enum EEndorsedState { ENDORSED_FALSE, ENDORSED_TRUE, ENDORSED_UNKNOWN, ENDORSED_NEVER }; - - enum EModType { MOD_DEFAULT, MOD_DLC, MOD_CC }; + + typedef QSharedPointer Ptr; + + static QString s_HiddenExt; + + enum EFlag { + FLAG_INVALID, + FLAG_BACKUP, + FLAG_OVERWRITE, + FLAG_FOREIGN, + FLAG_NOTENDORSED, + FLAG_NOTES, + FLAG_CONFLICT_OVERWRITE, + FLAG_CONFLICT_OVERWRITTEN, + FLAG_CONFLICT_MIXED, + FLAG_CONFLICT_REDUNDANT, + FLAG_PLUGIN_SELECTED + }; + + enum EContent { + CONTENT_PLUGIN, + CONTENT_TEXTURE, + CONTENT_MESH, + CONTENT_BSA, + CONTENT_INTERFACE, + CONTENT_SOUND, + CONTENT_SCRIPT, + CONTENT_SKSE, + CONTENT_SKYPROC, + CONTENT_MCM + }; + + static const int NUM_CONTENT_TYPES = CONTENT_SKYPROC + 1; + + enum EHighlight { + HIGHLIGHT_NONE = 0, + HIGHLIGHT_INVALID = 1, + HIGHLIGHT_CENTER = 2, + HIGHLIGHT_IMPORTANT = 4, + HIGHLIGHT_PLUGIN = 8 + }; + + enum EEndorsedState { + ENDORSED_FALSE, + ENDORSED_TRUE, + ENDORSED_UNKNOWN, + ENDORSED_NEVER + }; + + enum EModType { + MOD_DEFAULT, + MOD_DLC, + MOD_CC + }; + public: - /** - * @brief read the mod directory and Mod ModInfo objects for all subdirectories - **/ - static void updateFromDisc(const QString& modDirectory, MOShared::DirectoryEntry** directoryStructure, - bool displayForeign, MOBase::IPluginGame const* game); - - static void clear() { - s_Collection.clear(); - s_ModsByName.clear(); - s_ModsByModID.clear(); - } - - /** - * @brief retrieve the number of mods - * - * @return number of mods - **/ - static unsigned int getNumMods(); - - /** - * @brief retrieve a ModInfo object based on its index - * - * @param index the index to look up. the maximum is getNumMods() - 1 - * @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 - **/ - static ModInfo::Ptr getByIndex(unsigned int index); - - /** - * @brief retrieve a ModInfo object based on its nexus mod id - * - * @param modID the nexus mod id to look up - * @return a reference counting pointer to the mod info - * @todo in its current form, this function is broken! There may be multiple mods with the same nexus id, - * this function will return only one of them - **/ - static std::vector getByModID(int modID); - - /** - * @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 - * @param index index of the mod to delete - * @return true if removal was successful, fals otherwise - **/ - static bool removeMod(unsigned int index); - - /** - * @brief retrieve the mod index by the mod name - * - * @param name name of the mod to look up - * @return the index of the mod. If the mod doesn't exist, UINT_MAX is returned - **/ - static unsigned int getIndex(const QString& name); - - /** - * @brief find the first mod that fulfills the filter function (after no particular order) - * @param filter a function to filter by. should return true for a match - * @return index of the matching mod or UINT_MAX if there wasn't a match - */ - static unsigned int findMod(const boost::function& filter); - - /** - * @brief check a bunch of mods for updates - * @param modIDs list of mods (Nexus Mod IDs) to check for updates - * @return - */ - static void checkChunkForUpdate(const std::vector& modIDs, QObject* receiver); - - /** - * @brief query nexus information for every mod and update the "newest version" information - **/ - static int checkAllForUpdate(QObject* receiver); - - /** - * @brief create a new mod from the specified directory and add it to the collection - * @param dir directory to create from - * @return pointer to the info-structure of the newly created/added mod - */ - static ModInfo::Ptr createFrom(const QDir& dir, MOShared::DirectoryEntry** directoryStructure); - - /** - * @brief create a new "foreign-managed" mod from a tuple of plugin and archives - * @param espName name of the plugin - * @param bsaNames names of archives - * @return a new mod - */ - static ModInfo::Ptr createFromPlugin(const QString& modName, const QString& espName, const QStringList& bsaNames, - ModInfo::EModType modType, MOShared::DirectoryEntry** directoryStructure); - - /** - * @brief retieve a name for one of the CONTENT_ enums - * @param contentType the content value - * @return a display string - */ - static QString getContentTypeName(int contentType); - - virtual bool isRegular() const { return false; } - - virtual bool isEmpty() const { return false; } - - /** - * @brief test if there is a newer version of the mod - * - * test if there is a newer version of the mod. This does NOT cause - * information to be retrieved from the nexus, it will only test version information already - * available locally. Use checkAllForUpdate() to update this version information - * - * @return true if there is a newer version - **/ - virtual bool updateAvailable() const = 0; - - /** - * @return true if the update currently available is ignored - */ - virtual bool updateIgnored() const = 0; - - /** - * @brief test if the "newest" version of the mod is older than the installed version - * - * test if there is a newer version of the mod. This does NOT cause - * information to be retrieved from the nexus, it will only test version information already - * available locally. Use checkAllForUpdate() to update this version information - * - * @return true if the newest version is older than the installed one - **/ - virtual bool downgradeAvailable() const = 0; - - /** - * @brief request an update of nexus description for this mod. - * - * 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 - * - **/ - virtual bool updateNXMInfo() = 0; - - /** - * @brief assign or unassign the specified category - * - * Every mod can have an arbitrary number of categories assigned to it - * - * @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 - **/ - virtual void setCategory(int categoryID, bool active) = 0; - - /** - * @brief change the notes (manually set information) for this mod - * @param notes new notes - */ - virtual void setNotes(const QString& notes) = 0; - - /** - * @brief set/change the nexus mod id of this mod - * - * @param modID the nexus mod id - **/ - virtual void setNexusID(int modID) = 0; - - /** - * @brief set/change the version of this mod - * @param version new version of the mod - */ - virtual void setVersion(const MOBase::VersionInfo& version); - - /** - * @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 set the newest version of this mod on the nexus - * - * this can be used to overwrite the version of a mod without actually - * 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 - **/ - virtual void setNewestVersion(const MOBase::VersionInfo& version) = 0; - - /** - * @brief sets the repository that was used to download the mod - */ - virtual void setRepository(const QString&) {} - - /** - * @brief changes/updates the nexus description text - * @param description the current description text - */ - virtual void setNexusDescription(const QString& description) = 0; - - /** - * @brief sets the file this mod was installed from - * @param fileName name of the file - */ - virtual void setInstallationFile(const QString& fileName) = 0; - - /** - * @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 - */ - virtual void addNexusCategory(int categoryID) = 0; - - virtual void addCategory(const QString& categoryName) override; - virtual bool removeCategory(const QString& categoryName) override; - virtual QStringList categories() override; - - /** - * update the endorsement state for the mod. This only changes the - * buffered state, it does not sync with Nexus - * @param endorsed the new endorsement state - */ - virtual void setIsEndorsed(bool endorsed) = 0; - - /** - * 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() = 0; - - /** - * @brief delete the mod from the disc. This does not update the global ModInfo structure or indices - * @return true if the mod was successfully removed - **/ - virtual bool remove() = 0; - - /** - * @brief endorse or un-endorse the mod. This will sync with nexus! - * @param doEndorse if true, the mod is endorsed, if false, it's un-endorsed. - * @note if doEndorse doesn't differ from the current value, nothing happens. - */ - virtual void endorse(bool doEndorse) = 0; - - /** - * @brief clear all caches held for this mod - */ - virtual void clearCaches() {} - - /** - * @brief getter for the mod name - * - * @return the mod name - **/ - virtual QString name() const = 0; - - /** - * @brief getter for an internal name. This is usually the same as the regular name, but with special mod types it - * might be this is used to distinguish between mods that have the same visible name - * @return internal mod name - */ - virtual QString internalName() const { return name(); } - - /** - * @brief getter for the mod path - * - * @return the (absolute) path to the mod - **/ - virtual QString absolutePath() const = 0; - - /** - * @brief getter for the installation file - * - * @return file used to install this mod from - */ - virtual QString getInstallationFile() const = 0; - - /** - * @return version object for machine based comparisons - **/ - virtual MOBase::VersionInfo getVersion() const { return m_Version; } - - /** - * @brief getter for the newest version number of this mod - * - * @return newest version of the mod - **/ - virtual MOBase::VersionInfo getNewestVersion() const = 0; - - /** - * @return the repository from which the file was downloaded. Only relevant regular mods - */ - virtual QString repository() const { return ""; } - - /** - * @brief ignore the newest version for updates - */ - virtual void ignoreUpdate(bool ignore) = 0; - - /** - * @brief getter for the nexus mod id - * - * @return the nexus mod id. may be 0 if the mod id isn't known or doesn't exist - **/ - virtual int getNexusID() const = 0; - - /** - * @return the fixed priority of mods of this type or INT_MIN if the priority of mods - * needs to be user-modifiable. Can be < 0 to force a priority below user-modifable mods - * or INT_MAX to force priority above all user-modifiables - */ - virtual int getFixedPriority() const = 0; - - /** - * @return true if the mod is always enabled - */ - virtual bool alwaysEnabled() const { return false; } - - /** - * @return true if the mod can be updated - */ - virtual bool canBeUpdated() const { return false; } - - /** - * @return true if the mod can be enabled/disabled - */ - virtual bool canBeEnabled() const { return false; } - - /** - * @return a list of flags for this mod - */ - virtual std::vector getFlags() const = 0; - - /** - * @return a list of content types contained in a mod - */ - virtual std::vector getContents() const { return std::vector(); } - - /** - * @brief test if the specified flag is set for this mod - * @param flag the flag to test - * @return true if the flag is set, false otherwise - */ - bool hasFlag(EFlag flag) const; - - /** - * @brief test if the mods contains the specified content - * @param content the content to test - * @return true if the content is there, false otherwise - */ - bool hasContent(ModInfo::EContent content) const; - - /** - * @return an indicator if and how this mod should be highlighted by the UI - */ - virtual int getHighlight() const { return HIGHLIGHT_NONE; } - - /** - * @return list of names of ini tweaks - **/ - virtual std::vector getIniTweaks() const = 0; - - /** - * @return a description about the mod, to be displayed in the ui - */ - virtual QString getDescription() const = 0; - - /** - * @return notes for this mod - */ - virtual QString notes() const = 0; - - /** - * @return creation time of this mod - */ - virtual QDateTime creationTime() const = 0; - - /** - * @return nexus description of the mod (html) - */ - virtual QString getNexusDescription() const = 0; - - /** - * @return last time nexus was queried for infos on this mod - */ - virtual QDateTime getLastNexusQuery() const = 0; - - /** - * @return a list of files that, if they exist in the data directory are treated as files in THIS mod - */ - virtual QStringList stealFiles() const { return QStringList(); } - - /** - * @return a list of archives belonging to this mod (as absolute file paths) - */ - virtual QStringList archives() const = 0; - - /** - * @brief adds the information that a file has been installed into this mod - * @param modId id of the mod installed - * @param fileId id of the file installed - */ - virtual void addInstalledFile(int modId, int fileId) = 0; - - /** - * @brief test if the mod belongs to the specified category - * - * @param categoryID the category to test for. - * @return true if the mod belongs to the specified category - * @note this does not verify the id actually identifies a category - **/ - bool categorySet(int categoryID) const; - - /** - * @brief retrive the whole list of categories (as ids) this mod belongs to - * - * @return list of categories - **/ - const std::set& getCategories() const { return m_Categories; } - - /** - * @return id of the primary category of this mod - */ - int getPrimaryCategory() const { return m_PrimaryCategory; } - - /** - * @brief sets the new primary category of the mod - * @param categoryID the category to set - */ - 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 - **/ - bool isValid() const { return m_Valid; } - - /** - * @return true if the file has been endorsed on nexus - */ - virtual EEndorsedState endorsedState() const { return ENDORSED_NEVER; } - - /** - * @brief updates the valid-flag for this mod - */ - void testValid(); - - /** - * @brief reads meta information from disk - */ - virtual void readMeta() {} - - /** - * @brief stores meta information back to disk - */ - virtual void saveMeta() {} - - /** - * @return retrieve list of mods (as mod index) that are overwritten by this one. Updates may be delayed - */ - virtual std::set getModOverwrite() { return std::set(); } - - /** - * @return list of mods (as mod index) that overwrite this one. Updates may be delayed - */ - virtual std::set getModOverwritten() { return std::set(); } - - /** - * @brief update conflict information - */ - virtual void doConflictCheck() const {} - - /** - * @brief set the URL for a mod - */ - virtual void setURL(QString const&) {} - - /** - * @returns the URL for a mod - */ - virtual QString getURL() const { return ""; } + + /** + * @brief read the mod directory and Mod ModInfo objects for all subdirectories + **/ + static void updateFromDisc(const QString &modDirectory, + MOShared::DirectoryEntry **directoryStructure, + bool displayForeign, + MOBase::IPluginGame const *game); + + static void clear() { s_Collection.clear(); s_ModsByName.clear(); s_ModsByModID.clear(); } + + /** + * @brief retrieve the number of mods + * + * @return number of mods + **/ + static unsigned int getNumMods(); + + /** + * @brief retrieve a ModInfo object based on its index + * + * @param index the index to look up. the maximum is getNumMods() - 1 + * @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 + **/ + static ModInfo::Ptr getByIndex(unsigned int index); + + /** + * @brief retrieve a ModInfo object based on its nexus mod id + * + * @param modID the nexus mod id to look up + * @return a reference counting pointer to the mod info + * @todo in its current form, this function is broken! There may be multiple mods with the same nexus id, + * this function will return only one of them + **/ + static std::vector getByModID(int modID); + + /** + * @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 + * @param index index of the mod to delete + * @return true if removal was successful, fals otherwise + **/ + static bool removeMod(unsigned int index); + + /** + * @brief retrieve the mod index by the mod name + * + * @param name name of the mod to look up + * @return the index of the mod. If the mod doesn't exist, UINT_MAX is returned + **/ + static unsigned int getIndex(const QString &name); + + /** + * @brief find the first mod that fulfills the filter function (after no particular order) + * @param filter a function to filter by. should return true for a match + * @return index of the matching mod or UINT_MAX if there wasn't a match + */ + static unsigned int findMod(const boost::function &filter); + + /** + * @brief check a bunch of mods for updates + * @param modIDs list of mods (Nexus Mod IDs) to check for updates + * @return + */ + static void checkChunkForUpdate(const std::vector &modIDs, QObject *receiver); + + /** + * @brief query nexus information for every mod and update the "newest version" information + **/ + static int checkAllForUpdate(QObject *receiver); + + /** + * @brief create a new mod from the specified directory and add it to the collection + * @param dir directory to create from + * @return pointer to the info-structure of the newly created/added mod + */ + static ModInfo::Ptr createFrom(const QDir &dir, MOShared::DirectoryEntry **directoryStructure); + + /** + * @brief create a new "foreign-managed" mod from a tuple of plugin and archives + * @param espName name of the plugin + * @param bsaNames names of archives + * @return a new mod + */ + static ModInfo::Ptr createFromPlugin(const QString &modName, const QString &espName, const QStringList &bsaNames, ModInfo::EModType modType, MOShared::DirectoryEntry **directoryStructure); + + /** + * @brief retieve a name for one of the CONTENT_ enums + * @param contentType the content value + * @return a display string + */ + static QString getContentTypeName(int contentType); + + virtual bool isRegular() const { return false; } + + virtual bool isEmpty() const { return false; } + + /** + * @brief test if there is a newer version of the mod + * + * test if there is a newer version of the mod. This does NOT cause + * information to be retrieved from the nexus, it will only test version information already + * available locally. Use checkAllForUpdate() to update this version information + * + * @return true if there is a newer version + **/ + virtual bool updateAvailable() const = 0; + + /** + * @return true if the update currently available is ignored + */ + virtual bool updateIgnored() const = 0; + + /** + * @brief test if the "newest" version of the mod is older than the installed version + * + * test if there is a newer version of the mod. This does NOT cause + * information to be retrieved from the nexus, it will only test version information already + * available locally. Use checkAllForUpdate() to update this version information + * + * @return true if the newest version is older than the installed one + **/ + virtual bool downgradeAvailable() const = 0; + + /** + * @brief request an update of nexus description for this mod. + * + * 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 + * + **/ + virtual bool updateNXMInfo() = 0; + + /** + * @brief assign or unassign the specified category + * + * Every mod can have an arbitrary number of categories assigned to it + * + * @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 + **/ + virtual void setCategory(int categoryID, bool active) = 0; + + /** + * @brief change the notes (manually set information) for this mod + * @param notes new notes + */ + virtual void setNotes(const QString ¬es) = 0; + + /** + * @brief set/change the nexus mod id of this mod + * + * @param modID the nexus mod id + **/ + virtual void setNexusID(int modID) = 0; + + /** + * @brief set/change the version of this mod + * @param version new version of the mod + */ + virtual void setVersion(const MOBase::VersionInfo &version); + + /** + * @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 set the newest version of this mod on the nexus + * + * this can be used to overwrite the version of a mod without actually + * 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 + **/ + virtual void setNewestVersion(const MOBase::VersionInfo &version) = 0; + + /** + * @brief sets the repository that was used to download the mod + */ + virtual void setRepository(const QString &) {} + + /** + * @brief changes/updates the nexus description text + * @param description the current description text + */ + virtual void setNexusDescription(const QString &description) = 0; + + /** + * @brief sets the file this mod was installed from + * @param fileName name of the file + */ + virtual void setInstallationFile(const QString &fileName) = 0; + + /** + * @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 + */ + virtual void addNexusCategory(int categoryID) = 0; + + virtual void addCategory(const QString &categoryName) override; + virtual bool removeCategory(const QString &categoryName) override; + virtual QStringList categories() override; + + /** + * update the endorsement state for the mod. This only changes the + * buffered state, it does not sync with Nexus + * @param endorsed the new endorsement state + */ + virtual void setIsEndorsed(bool endorsed) = 0; + + /** + * 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() = 0; + + /** + * @brief delete the mod from the disc. This does not update the global ModInfo structure or indices + * @return true if the mod was successfully removed + **/ + virtual bool remove() = 0; + + /** + * @brief endorse or un-endorse the mod. This will sync with nexus! + * @param doEndorse if true, the mod is endorsed, if false, it's un-endorsed. + * @note if doEndorse doesn't differ from the current value, nothing happens. + */ + virtual void endorse(bool doEndorse) = 0; + + /** + * @brief clear all caches held for this mod + */ + virtual void clearCaches() {} + + /** + * @brief getter for the mod name + * + * @return the mod name + **/ + virtual QString name() const = 0; + + /** + * @brief getter for an internal name. This is usually the same as the regular name, but with special mod types it might be + * this is used to distinguish between mods that have the same visible name + * @return internal mod name + */ + virtual QString internalName() const { return name(); } + + /** + * @brief getter for the mod path + * + * @return the (absolute) path to the mod + **/ + virtual QString absolutePath() const = 0; + + /** + * @brief getter for the installation file + * + * @return file used to install this mod from + */ + virtual QString getInstallationFile() const = 0; + + /** + * @return version object for machine based comparisons + **/ + virtual MOBase::VersionInfo getVersion() const { return m_Version; } + + /** + * @brief getter for the newest version number of this mod + * + * @return newest version of the mod + **/ + virtual MOBase::VersionInfo getNewestVersion() const = 0; + + /** + * @return the repository from which the file was downloaded. Only relevant regular mods + */ + virtual QString repository() const { return ""; } + + /** + * @brief ignore the newest version for updates + */ + virtual void ignoreUpdate(bool ignore) = 0; + + /** + * @brief getter for the nexus mod id + * + * @return the nexus mod id. may be 0 if the mod id isn't known or doesn't exist + **/ + virtual int getNexusID() const = 0; + + /** + * @return the fixed priority of mods of this type or INT_MIN if the priority of mods + * needs to be user-modifiable. Can be < 0 to force a priority below user-modifable mods + * or INT_MAX to force priority above all user-modifiables + */ + virtual int getFixedPriority() const = 0; + + /** + * @return true if the mod is always enabled + */ + virtual bool alwaysEnabled() const { return false; } + + /** + * @return true if the mod can be updated + */ + virtual bool canBeUpdated() const { return false; } + + /** + * @return true if the mod can be enabled/disabled + */ + virtual bool canBeEnabled() const { return false; } + + /** + * @return a list of flags for this mod + */ + virtual std::vector getFlags() const = 0; + + /** + * @return a list of content types contained in a mod + */ + virtual std::vector getContents() const { return std::vector(); } + + /** + * @brief test if the specified flag is set for this mod + * @param flag the flag to test + * @return true if the flag is set, false otherwise + */ + bool hasFlag(EFlag flag) const; + + /** + * @brief test if the mods contains the specified content + * @param content the content to test + * @return true if the content is there, false otherwise + */ + bool hasContent(ModInfo::EContent content) const; + + /** + * @return an indicator if and how this mod should be highlighted by the UI + */ + virtual int getHighlight() const { return HIGHLIGHT_NONE; } + + /** + * @return list of names of ini tweaks + **/ + virtual std::vector getIniTweaks() const = 0; + + /** + * @return a description about the mod, to be displayed in the ui + */ + virtual QString getDescription() const = 0; + + /** + * @return notes for this mod + */ + virtual QString notes() const = 0; + + /** + * @return creation time of this mod + */ + virtual QDateTime creationTime() const = 0; + + /** + * @return nexus description of the mod (html) + */ + virtual QString getNexusDescription() const = 0; + + /** + * @return last time nexus was queried for infos on this mod + */ + virtual QDateTime getLastNexusQuery() const = 0; + + /** + * @return a list of files that, if they exist in the data directory are treated as files in THIS mod + */ + virtual QStringList stealFiles() const { return QStringList(); } + + /** + * @return a list of archives belonging to this mod (as absolute file paths) + */ + virtual QStringList archives() const = 0; + + /** + * @brief adds the information that a file has been installed into this mod + * @param modId id of the mod installed + * @param fileId id of the file installed + */ + virtual void addInstalledFile(int modId, int fileId) = 0; + + /** + * @brief test if the mod belongs to the specified category + * + * @param categoryID the category to test for. + * @return true if the mod belongs to the specified category + * @note this does not verify the id actually identifies a category + **/ + bool categorySet(int categoryID) const; + + /** + * @brief retrive the whole list of categories (as ids) this mod belongs to + * + * @return list of categories + **/ + const std::set &getCategories() const { return m_Categories; } + + /** + * @return id of the primary category of this mod + */ + int getPrimaryCategory() const { return m_PrimaryCategory; } + + /** + * @brief sets the new primary category of the mod + * @param categoryID the category to set + */ + 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 + **/ + bool isValid() const { return m_Valid; } + + /** + * @return true if the file has been endorsed on nexus + */ + virtual EEndorsedState endorsedState() const { return ENDORSED_NEVER; } + + /** + * @brief updates the valid-flag for this mod + */ + void testValid(); + + /** + * @brief reads meta information from disk + */ + virtual void readMeta() {} + + /** + * @brief stores meta information back to disk + */ + virtual void saveMeta() {} + + /** + * @return retrieve list of mods (as mod index) that are overwritten by this one. Updates may be delayed + */ + virtual std::set getModOverwrite() { return std::set(); } + + /** + * @return list of mods (as mod index) that overwrite this one. Updates may be delayed + */ + virtual std::set getModOverwritten() { return std::set(); } + + /** + * @brief update conflict information + */ + virtual void doConflictCheck() const {} + + /** + * @brief set the URL for a mod + */ + virtual void setURL(QString const &) {} + + /** + * @returns the URL for a mod + */ + virtual QString getURL() const { return ""; } signals: - /** - * @brief emitted whenever the information of a mod changes - * - * @param success true if the mod details were updated successfully, false if not - **/ - void modDetailsUpdated(bool success); + /** + * @brief emitted whenever the information of a mod changes + * + * @param success true if the mod details were updated successfully, false if not + **/ + void modDetailsUpdated(bool success); protected: - ModInfo(); - static void updateIndices(); + ModInfo(); + + static void updateIndices(); private: - static void createFromOverwrite(); + + static void createFromOverwrite(); protected: - static std::vector s_Collection; - static std::map s_ModsByName; - int m_PrimaryCategory; - std::set m_Categories; + static std::vector s_Collection; + static std::map s_ModsByName; + + int m_PrimaryCategory; + std::set m_Categories; - MOBase::VersionInfo m_Version; + MOBase::VersionInfo m_Version; - bool m_PluginSelected = false; + bool m_PluginSelected = false; private: - static QMutex s_Mutex; - static std::map> s_ModsByModID; - static int s_NextID; - bool m_Valid; + static QMutex s_Mutex; + static std::map > s_ModsByModID; + static int s_NextID; + + bool m_Valid; + }; + #endif // MODINFO_H diff --git a/src/modinfobackup.cpp b/src/modinfobackup.cpp index ddd23b43..a57b84fe 100644 --- a/src/modinfobackup.cpp +++ b/src/modinfobackup.cpp @@ -1,12 +1,20 @@ #include "modinfobackup.h" -std::vector ModInfoBackup::getFlags() const { - std::vector result = ModInfoRegular::getFlags(); - result.insert(result.begin(), ModInfo::FLAG_BACKUP); - return result; +std::vector ModInfoBackup::getFlags() const +{ + std::vector result = ModInfoRegular::getFlags(); + result.insert(result.begin(), ModInfo::FLAG_BACKUP); + return result; } -QString ModInfoBackup::getDescription() const { return tr("This is the backup of a mod"); } -ModInfoBackup::ModInfoBackup(const QDir& path, MOShared::DirectoryEntry** directoryStructure) - : ModInfoRegular(path, directoryStructure) {} +QString ModInfoBackup::getDescription() const +{ + return tr("This is the backup of a mod"); +} + + +ModInfoBackup::ModInfoBackup(const QDir &path, MOShared::DirectoryEntry **directoryStructure) + : ModInfoRegular(path, directoryStructure) +{ +} diff --git a/src/modinfobackup.h b/src/modinfobackup.h index 7c8ce874..adbac783 100644 --- a/src/modinfobackup.h +++ b/src/modinfobackup.h @@ -3,33 +3,38 @@ #include "modinforegular.h" -class ModInfoBackup : public ModInfoRegular { +class ModInfoBackup : public ModInfoRegular +{ - friend class ModInfo; + friend class ModInfo; public: - virtual bool updateAvailable() const { return false; } - virtual bool updateIgnored() const { return false; } - virtual bool downgradeAvailable() const { return false; } - virtual bool updateNXMInfo() { return false; } - virtual void setNexusID(int) {} - virtual void endorse(bool) {} - virtual int getFixedPriority() const { return -1; } - virtual void ignoreUpdate(bool) {} - virtual bool canBeUpdated() const { return false; } - virtual bool canBeEnabled() const { return false; } - virtual std::vector getIniTweaks() const { return std::vector(); } - virtual std::vector getFlags() const; - virtual QString getDescription() const; - virtual QDateTime getLastNexusQuery() const { return QDateTime(); } - virtual void getNexusFiles(QList::const_iterator&, - QList::const_iterator&) {} - virtual QString getNexusDescription() const { return QString(); } - - virtual void addInstalledFile(int, int) {} + + virtual bool updateAvailable() const { return false; } + virtual bool updateIgnored() const { return false; } + virtual bool downgradeAvailable() const { return false; } + virtual bool updateNXMInfo() { return false; } + virtual void setNexusID(int) {} + virtual void endorse(bool) {} + virtual int getFixedPriority() const { return -1; } + virtual void ignoreUpdate(bool) {} + virtual bool canBeUpdated() const { return false; } + virtual bool canBeEnabled() const { return false; } + virtual std::vector getIniTweaks() const { return std::vector(); } + virtual std::vector getFlags() const; + virtual QString getDescription() const; + virtual QDateTime getLastNexusQuery() const { return QDateTime(); } + virtual void getNexusFiles(QList::const_iterator&, + QList::const_iterator&) {} + virtual QString getNexusDescription() const { return QString(); } + + virtual void addInstalledFile(int, int) {} private: - ModInfoBackup(const QDir& path, MOShared::DirectoryEntry** directoryStructure); + + ModInfoBackup(const QDir &path, MOShared::DirectoryEntry **directoryStructure); + }; + #endif // MODINFOBACKUP_H diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 9afecbfa..ccd2a122 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -18,645 +18,714 @@ along with Mod Organizer. If not, see . */ #include "modinfodialog.h" -#include "descriptionpage.h" #include "ui_modinfodialog.h" +#include "descriptionpage.h" -#include "bbcode.h" -#include "categories.h" #include "iplugingame.h" -#include "messagedialog.h" #include "nexusinterface.h" -#include "questionboxmemory.h" #include "report.h" -#include "settings.h" #include "utility.h" +#include "messagedialog.h" +#include "bbcode.h" +#include "questionboxmemory.h" +#include "settings.h" +#include "categories.h" #include #include -#include +#include #include -#include #include +#include +#include +#include #include -#include #include #include + using namespace MOBase; using namespace MOShared; -class ModFileListWidget : public QListWidgetItem { - friend bool operator<(const ModFileListWidget& LHS, const ModFileListWidget& RHS); +class ModFileListWidget : public QListWidgetItem { + friend bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS); public: - ModFileListWidget(const QString& text, int sortValue, QListWidget* parent = 0) - : QListWidgetItem(text, parent, QListWidgetItem::UserType + 1), m_SortValue(sortValue) {} - + ModFileListWidget(const QString &text, int sortValue, QListWidget *parent = 0) + : QListWidgetItem(text, parent, QListWidgetItem::UserType + 1), m_SortValue(sortValue) {} private: - int m_SortValue; + int m_SortValue; }; -static bool operator<(const ModFileListWidget& LHS, const ModFileListWidget& RHS) { - return LHS.m_SortValue < RHS.m_SortValue; + +static bool operator<(const ModFileListWidget &LHS, const ModFileListWidget &RHS) +{ + return LHS.m_SortValue < RHS.m_SortValue; } -ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry* directory, bool unmanaged, QWidget* parent) - : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), m_ThumbnailMapper(this), - m_RequestStarted(false), m_DeleteAction(nullptr), m_RenameAction(nullptr), m_OpenAction(nullptr), - m_Directory(directory), m_Origin(nullptr) { - ui->setupUi(this); - this->setWindowTitle(modInfo->name()); - this->setWindowModality(Qt::WindowModal); - m_RootPath = modInfo->absolutePath(); +ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, bool unmanaged, QWidget *parent) + : TutorableDialog("ModInfoDialog", parent), ui(new Ui::ModInfoDialog), m_ModInfo(modInfo), + m_ThumbnailMapper(this), m_RequestStarted(false), + m_DeleteAction(nullptr), m_RenameAction(nullptr), m_OpenAction(nullptr), + m_Directory(directory), m_Origin(nullptr) +{ + ui->setupUi(this); + this->setWindowTitle(modInfo->name()); + this->setWindowModality(Qt::WindowModal); - QString metaFileName = m_RootPath.mid(0).append("/meta.ini"); - m_Settings = new QSettings(metaFileName, QSettings::IniFormat); + m_RootPath = modInfo->absolutePath(); - QLineEdit* modIDEdit = findChild("modIDEdit"); - ui->modIDEdit->setValidator(new QIntValidator(modIDEdit)); - ui->modIDEdit->setText(QString("%1").arg(modInfo->getNexusID())); + QString metaFileName = m_RootPath.mid(0).append("/meta.ini"); + m_Settings = new QSettings(metaFileName, QSettings::IniFormat); - ui->notesEdit->setText(modInfo->notes()); + QLineEdit *modIDEdit = findChild("modIDEdit"); + ui->modIDEdit->setValidator(new QIntValidator(modIDEdit)); + ui->modIDEdit->setText(QString("%1").arg(modInfo->getNexusID())); - ui->descriptionView->setPage(new DescriptionPage); + ui->notesEdit->setText(modInfo->notes()); - connect(&m_ThumbnailMapper, SIGNAL(mapped(const QString&)), this, SIGNAL(thumbnailClickedSignal(const QString&))); - connect(this, SIGNAL(thumbnailClickedSignal(const QString&)), this, SLOT(thumbnailClicked(const QString&))); - connect(m_ModInfo.data(), SIGNAL(modDetailsUpdated(bool)), this, SLOT(modDetailsUpdated(bool))); - connect(ui->descriptionView->page(), SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl))); - // TODO: No easy way to delegate links - // ui->descriptionView->page()->acceptNavigationRequest(QWebEnginePage::DelegateAllLinks); + ui->descriptionView->setPage(new DescriptionPage); - if (directory->originExists(ToWString(modInfo->name()))) { - m_Origin = &directory->getOriginByName(ToWString(modInfo->name())); - if (m_Origin->isDisabled()) { - m_Origin = nullptr; - } + connect(&m_ThumbnailMapper, SIGNAL(mapped(const QString&)), this, SIGNAL(thumbnailClickedSignal(const QString&))); + connect(this, SIGNAL(thumbnailClickedSignal(const QString&)), this, SLOT(thumbnailClicked(const QString&))); + connect(m_ModInfo.data(), SIGNAL(modDetailsUpdated(bool)), this, SLOT(modDetailsUpdated(bool))); + connect(ui->descriptionView->page(), SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl))); + //TODO: No easy way to delegate links + //ui->descriptionView->page()->acceptNavigationRequest(QWebEnginePage::DelegateAllLinks); + + if (directory->originExists(ToWString(modInfo->name()))) { + m_Origin = &directory->getOriginByName(ToWString(modInfo->name())); + if (m_Origin->isDisabled()) { + m_Origin = nullptr; } + } - refreshLists(); + refreshLists(); + + if (unmanaged) { + ui->tabWidget->setTabEnabled(TAB_INIFILES, false); + ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); + ui->tabWidget->setTabEnabled(TAB_NEXUS, false); + ui->tabWidget->setTabEnabled(TAB_FILETREE, false); + ui->tabWidget->setTabEnabled(TAB_NOTES, false); + ui->tabWidget->setTabEnabled(TAB_ESPS, false); + ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); + ui->tabWidget->setTabEnabled(TAB_IMAGES, false); + } else { + initFiletree(modInfo); + addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0); + refreshPrimaryCategoriesBox(); + ui->tabWidget->setTabEnabled(TAB_TEXTFILES, ui->textFileList->count() != 0); + ui->tabWidget->setTabEnabled(TAB_IMAGES, ui->thumbnailArea->count() != 0); + ui->tabWidget->setTabEnabled(TAB_ESPS, (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); + } + initINITweaks(); - if (unmanaged) { - ui->tabWidget->setTabEnabled(TAB_INIFILES, false); - ui->tabWidget->setTabEnabled(TAB_CATEGORIES, false); - ui->tabWidget->setTabEnabled(TAB_NEXUS, false); - ui->tabWidget->setTabEnabled(TAB_FILETREE, false); - ui->tabWidget->setTabEnabled(TAB_NOTES, false); - ui->tabWidget->setTabEnabled(TAB_ESPS, false); - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, false); - ui->tabWidget->setTabEnabled(TAB_IMAGES, false); - } else { - initFiletree(modInfo); - addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), - 0); - refreshPrimaryCategoriesBox(); - ui->tabWidget->setTabEnabled(TAB_TEXTFILES, ui->textFileList->count() != 0); - ui->tabWidget->setTabEnabled(TAB_IMAGES, ui->thumbnailArea->count() != 0); - ui->tabWidget->setTabEnabled(TAB_ESPS, - (ui->inactiveESPList->count() != 0) || (ui->activeESPList->count() != 0)); - } - initINITweaks(); + ui->tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != nullptr); - ui->tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != nullptr); + if (ui->tabWidget->currentIndex() == TAB_NEXUS) { + activateNexusTab(); + } + + ui->endorseBtn->setEnabled((m_ModInfo->endorsedState() == ModInfo::ENDORSED_FALSE) || + (m_ModInfo->endorsedState() == ModInfo::ENDORSED_NEVER)); - if (ui->tabWidget->currentIndex() == TAB_NEXUS) { - activateNexusTab(); + // activate first enabled tab + for (int i = 0; i < ui->tabWidget->count(); ++i) { + if (ui->tabWidget->isTabEnabled(i)) { + ui->tabWidget->setCurrentIndex(i); + break; } + } +} - ui->endorseBtn->setEnabled((m_ModInfo->endorsedState() == ModInfo::ENDORSED_FALSE) || - (m_ModInfo->endorsedState() == ModInfo::ENDORSED_NEVER)); - // activate first enabled tab - for (int i = 0; i < ui->tabWidget->count(); ++i) { - if (ui->tabWidget->isTabEnabled(i)) { - ui->tabWidget->setCurrentIndex(i); - break; - } - } +ModInfoDialog::~ModInfoDialog() +{ + m_ModInfo->setNotes(ui->notesEdit->toPlainText()); + saveCategories(ui->categoriesTree->invisibleRootItem()); + saveIniTweaks(); // ini tweaks are written to the ini file directly. This is the only information not managed by ModInfo + delete ui->descriptionView->page(); + delete ui->descriptionView; + delete ui; + delete m_Settings; } -ModInfoDialog::~ModInfoDialog() { - m_ModInfo->setNotes(ui->notesEdit->toPlainText()); - saveCategories(ui->categoriesTree->invisibleRootItem()); - saveIniTweaks(); // ini tweaks are written to the ini file directly. This is the only information not managed by - // ModInfo - delete ui->descriptionView->page(); - delete ui->descriptionView; - delete ui; - delete m_Settings; -} - -void ModInfoDialog::initINITweaks() { - int numTweaks = m_Settings->beginReadArray("INI Tweaks"); - for (int i = 0; i < numTweaks; ++i) { - m_Settings->setArrayIndex(i); - QList items = - ui->iniTweaksList->findItems(m_Settings->value("name").toString(), Qt::MatchFixedString); - if (items.size() != 0) { - items.at(0)->setCheckState(Qt::Checked); - } - } - m_Settings->endArray(); -} - -void ModInfoDialog::initFiletree(ModInfo::Ptr modInfo) { - ui->fileTree = findChild("fileTree"); - - m_FileSystemModel = new QFileSystemModel(this); - m_FileSystemModel->setReadOnly(false); - m_FileSystemModel->setRootPath(m_RootPath); - ui->fileTree->setModel(m_FileSystemModel); - ui->fileTree->setRootIndex(m_FileSystemModel->index(m_RootPath)); - ui->fileTree->setColumnWidth(0, 300); - - m_DeleteAction = new QAction(tr("&Delete"), ui->fileTree); - m_RenameAction = new QAction(tr("&Rename"), ui->fileTree); - m_HideAction = new QAction(tr("&Hide"), ui->fileTree); - m_UnhideAction = new QAction(tr("&Unhide"), ui->fileTree); - m_OpenAction = new QAction(tr("&Open"), ui->fileTree); - m_NewFolderAction = new QAction(tr("&New Folder"), ui->fileTree); - 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())); - QObject::connect(m_HideAction, SIGNAL(triggered()), this, SLOT(hideTriggered())); - connect(m_UnhideAction, SIGNAL(triggered()), this, SLOT(unhideTriggered())); -} - -int ModInfoDialog::tabIndex(const QString& tabId) { - for (int i = 0; i < ui->tabWidget->count(); ++i) { - if (ui->tabWidget->widget(i)->objectName() == tabId) { - return i; - } + +void ModInfoDialog::initINITweaks() +{ + int numTweaks = m_Settings->beginReadArray("INI Tweaks"); + for (int i = 0; i < numTweaks; ++i) { + m_Settings->setArrayIndex(i); + QList items = ui->iniTweaksList->findItems(m_Settings->value("name").toString(), Qt::MatchFixedString); + if (items.size() != 0) { + items.at(0)->setCheckState(Qt::Checked); } - return -1; + } + m_Settings->endArray(); } -void ModInfoDialog::restoreTabState(const QByteArray& state) { - QDataStream stream(state); - int count = 0; - stream >> count; +void ModInfoDialog::initFiletree(ModInfo::Ptr modInfo) +{ + ui->fileTree = findChild("fileTree"); + + m_FileSystemModel = new QFileSystemModel(this); + m_FileSystemModel->setReadOnly(false); + m_FileSystemModel->setRootPath(m_RootPath); + ui->fileTree->setModel(m_FileSystemModel); + ui->fileTree->setRootIndex(m_FileSystemModel->index(m_RootPath)); + ui->fileTree->setColumnWidth(0, 300); + + m_DeleteAction = new QAction(tr("&Delete"), ui->fileTree); + m_RenameAction = new QAction(tr("&Rename"), ui->fileTree); + m_HideAction = new QAction(tr("&Hide"), ui->fileTree); + m_UnhideAction = new QAction(tr("&Unhide"), ui->fileTree); + m_OpenAction = new QAction(tr("&Open"), ui->fileTree); + m_NewFolderAction = new QAction(tr("&New Folder"), ui->fileTree); + 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())); + QObject::connect(m_HideAction, SIGNAL(triggered()), this, SLOT(hideTriggered())); + connect(m_UnhideAction, SIGNAL(triggered()), this, SLOT(unhideTriggered())); +} - QStringList tabIds; - // first, only determine the new mapping - for (int newPos = 0; newPos < count; ++newPos) { - QString tabId; - stream >> tabId; - tabIds.append(tabId); - int oldPos = tabIndex(tabId); - if (oldPos != -1) { - m_RealTabPos[newPos] = oldPos; - } else { - m_RealTabPos[newPos] = newPos; - } - } - // then actually move the tabs - QTabBar* tabBar = ui->tabWidget->findChild("qt_tabwidget_tabbar"); // magic name = bad - ui->tabWidget->blockSignals(true); - for (int newPos = 0; newPos < count; ++newPos) { - QString tabId = tabIds.at(newPos); - int oldPos = tabIndex(tabId); - tabBar->moveTab(oldPos, newPos); +int ModInfoDialog::tabIndex(const QString &tabId) +{ + for (int i = 0; i < ui->tabWidget->count(); ++i) { + if (ui->tabWidget->widget(i)->objectName() == tabId) { + return i; } - ui->tabWidget->blockSignals(false); + } + return -1; } -QByteArray ModInfoDialog::saveTabState() const { - QByteArray result; - QDataStream stream(&result, QIODevice::WriteOnly); - stream << ui->tabWidget->count(); - for (int i = 0; i < ui->tabWidget->count(); ++i) { - stream << ui->tabWidget->widget(i)->objectName(); + +void ModInfoDialog::restoreTabState(const QByteArray &state) +{ + QDataStream stream(state); + int count = 0; + stream >> count; + + QStringList tabIds; + + // first, only determine the new mapping + for (int newPos = 0; newPos < count; ++newPos) { + QString tabId; + stream >> tabId; + tabIds.append(tabId); + int oldPos = tabIndex(tabId); + if (oldPos != -1) { + m_RealTabPos[newPos] = oldPos; + } else { + m_RealTabPos[newPos] = newPos; } + } + // then actually move the tabs + QTabBar *tabBar = ui->tabWidget->findChild("qt_tabwidget_tabbar"); // magic name = bad + ui->tabWidget->blockSignals(true); + for (int newPos = 0; newPos < count; ++newPos) { + QString tabId = tabIds.at(newPos); + int oldPos = tabIndex(tabId); + tabBar->moveTab(oldPos, newPos); + } + ui->tabWidget->blockSignals(false); +} + - return result; -} - -void ModInfoDialog::refreshLists() { - int numNonConflicting = 0; - int numOverwrite = 0; - int numOverwritten = 0; - - ui->overwriteTree->clear(); - ui->overwrittenTree->clear(); - - if (m_Origin != nullptr) { - std::vector files = m_Origin->getFiles(); - for (auto iter = files.begin(); iter != files.end(); ++iter) { - QString relativeName = QDir::fromNativeSeparators(ToQString((*iter)->getRelativePath())); - QString fileName = relativeName.mid(0).prepend(m_RootPath); - bool archive; - if ((*iter)->getOrigin(archive) == m_Origin->getID()) { - std::vector> alternatives = (*iter)->getAlternatives(); - if (!alternatives.empty()) { - std::wostringstream altString; - for (std::vector>::iterator altIter = alternatives.begin(); - altIter != alternatives.end(); ++altIter) { - if (altIter != alternatives.begin()) { - altString << ", "; - } - altString << m_Directory->getOriginByID(altIter->first).getName(); - } - QStringList fields(relativeName.prepend("...")); - fields.append(ToQString(altString.str())); - QTreeWidgetItem* item = new QTreeWidgetItem(fields); - item->setData(0, Qt::UserRole, fileName); - item->setData(1, Qt::UserRole, - ToQString(m_Directory->getOriginByID(alternatives.begin()->first).getName())); - item->setData(1, Qt::UserRole + 1, alternatives.begin()->first); - item->setData(1, Qt::UserRole + 2, archive); - ui->overwriteTree->addTopLevelItem(item); - ++numOverwrite; - } else { // otherwise don't display the file - ++numNonConflicting; - } - } else { - FilesOrigin& realOrigin = m_Directory->getOriginByID((*iter)->getOrigin(archive)); - QStringList fields(relativeName); - fields.append(ToQString(realOrigin.getName())); - QTreeWidgetItem* item = new QTreeWidgetItem(fields); - item->setData(1, Qt::UserRole, ToQString(realOrigin.getName())); - ui->overwrittenTree->addTopLevelItem(item); - ++numOverwritten; +QByteArray ModInfoDialog::saveTabState() const +{ + QByteArray result; + QDataStream stream(&result, QIODevice::WriteOnly); + stream << ui->tabWidget->count(); + for (int i = 0; i < ui->tabWidget->count(); ++i) { + stream << ui->tabWidget->widget(i)->objectName(); + } + + return result; +} + + +void ModInfoDialog::refreshLists() +{ + int numNonConflicting = 0; + int numOverwrite = 0; + int numOverwritten = 0; + + ui->overwriteTree->clear(); + ui->overwrittenTree->clear(); + + if (m_Origin != nullptr) { + std::vector files = m_Origin->getFiles(); + for (auto iter = files.begin(); iter != files.end(); ++iter) { + QString relativeName = QDir::fromNativeSeparators(ToQString((*iter)->getRelativePath())); + QString fileName = relativeName.mid(0).prepend(m_RootPath); + bool archive; + if ((*iter)->getOrigin(archive) == m_Origin->getID()) { + std::vector> alternatives = (*iter)->getAlternatives(); + if (!alternatives.empty()) { + std::wostringstream altString; + for (std::vector>::iterator altIter = alternatives.begin(); + altIter != alternatives.end(); ++altIter) { + if (altIter != alternatives.begin()) { + altString << ", "; } + altString << m_Directory->getOriginByID(altIter->first).getName(); + } + QStringList fields(relativeName.prepend("...")); + fields.append(ToQString(altString.str())); + QTreeWidgetItem *item = new QTreeWidgetItem(fields); + item->setData(0, Qt::UserRole, fileName); + item->setData(1, Qt::UserRole, ToQString(m_Directory->getOriginByID(alternatives.begin()->first).getName())); + item->setData(1, Qt::UserRole + 1, alternatives.begin()->first); + item->setData(1, Qt::UserRole + 2, archive); + ui->overwriteTree->addTopLevelItem(item); + ++numOverwrite; + } else {// otherwise don't display the file + ++numNonConflicting; } + } else { + FilesOrigin &realOrigin = m_Directory->getOriginByID((*iter)->getOrigin(archive)); + QStringList fields(relativeName); + fields.append(ToQString(realOrigin.getName())); + QTreeWidgetItem *item = new QTreeWidgetItem(fields); + item->setData(1, Qt::UserRole, ToQString(realOrigin.getName())); + ui->overwrittenTree->addTopLevelItem(item); + ++numOverwritten; + } } + } - if (m_RootPath.length() > 0) { - QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); - while (dirIterator.hasNext()) { - QString fileName = dirIterator.next(); - - if (fileName.endsWith(".txt", Qt::CaseInsensitive)) { - ui->textFileList->addItem(fileName.mid(m_RootPath.length() + 1)); - } else if ((fileName.endsWith(".ini", Qt::CaseInsensitive) || - fileName.endsWith(".cfg", Qt::CaseInsensitive)) && - !fileName.endsWith("meta.ini")) { - QString namePart = fileName.mid(m_RootPath.length() + 1); - if (namePart.startsWith("INI Tweaks", Qt::CaseInsensitive)) { - QListWidgetItem* newItem = new QListWidgetItem(namePart.mid(11), ui->iniTweaksList); - newItem->setData(Qt::UserRole, namePart); - newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); - newItem->setCheckState(Qt::Unchecked); - ui->iniTweaksList->addItem(newItem); - } else { - ui->iniFileList->addItem(namePart); - } - } else if (fileName.endsWith(".esp", Qt::CaseInsensitive) || - fileName.endsWith(".esm", Qt::CaseInsensitive) || - fileName.endsWith(".esl", Qt::CaseInsensitive)) { - QString relativePath = fileName.mid(m_RootPath.length() + 1); - if (relativePath.contains('/')) { - QFileInfo fileInfo(fileName); - QListWidgetItem* newItem = new QListWidgetItem(fileInfo.fileName()); - newItem->setData(Qt::UserRole, relativePath); - ui->inactiveESPList->addItem(newItem); - } else { - ui->activeESPList->addItem(relativePath); - } - } else if ((fileName.endsWith(".png", Qt::CaseInsensitive)) || - (fileName.endsWith(".jpg", Qt::CaseInsensitive))) { - QImage image = QImage(fileName); - if (!image.isNull()) { - if (static_cast(image.width()) / static_cast(image.height()) > 1.34) { - image = image.scaledToWidth(128); - } else { - image = image.scaledToHeight(96); - } - - QPushButton* thumbnailButton = new QPushButton(QPixmap::fromImage(image), ""); - thumbnailButton->setIconSize(QSize(image.width(), image.height())); - connect(thumbnailButton, SIGNAL(clicked()), &m_ThumbnailMapper, SLOT(map())); - m_ThumbnailMapper.setMapping(thumbnailButton, fileName); - ui->thumbnailArea->addWidget(thumbnailButton); - } - } + if (m_RootPath.length() > 0) { + QDirIterator dirIterator(m_RootPath, QDir::Files, QDirIterator::Subdirectories); + while (dirIterator.hasNext()) { + QString fileName = dirIterator.next(); + + if (fileName.endsWith(".txt", Qt::CaseInsensitive)) { + ui->textFileList->addItem(fileName.mid(m_RootPath.length() + 1)); + } else if ((fileName.endsWith(".ini", Qt::CaseInsensitive) || fileName.endsWith(".cfg", Qt::CaseInsensitive)) && + !fileName.endsWith("meta.ini")) { + QString namePart = fileName.mid(m_RootPath.length() + 1); + if (namePart.startsWith("INI Tweaks", Qt::CaseInsensitive)) { + QListWidgetItem *newItem = new QListWidgetItem(namePart.mid(11), ui->iniTweaksList); + newItem->setData(Qt::UserRole, namePart); + newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); + newItem->setCheckState(Qt::Unchecked); + ui->iniTweaksList->addItem(newItem); + } else { + ui->iniFileList->addItem(namePart); + } + } else if (fileName.endsWith(".esp", Qt::CaseInsensitive) || + fileName.endsWith(".esm", Qt::CaseInsensitive) || + fileName.endsWith(".esl", Qt::CaseInsensitive)) { + QString relativePath = fileName.mid(m_RootPath.length() + 1); + if (relativePath.contains('/')) { + QFileInfo fileInfo(fileName); + QListWidgetItem *newItem = new QListWidgetItem(fileInfo.fileName()); + newItem->setData(Qt::UserRole, relativePath); + ui->inactiveESPList->addItem(newItem); + } else { + ui->activeESPList->addItem(relativePath); + } + } else if ((fileName.endsWith(".png", Qt::CaseInsensitive)) || + (fileName.endsWith(".jpg", Qt::CaseInsensitive))) { + QImage image = QImage(fileName); + if (!image.isNull()) { + if (static_cast(image.width()) / static_cast(image.height()) > 1.34) { + image = image.scaledToWidth(128); + } else { + image = image.scaledToHeight(96); + } + + QPushButton *thumbnailButton = new QPushButton(QPixmap::fromImage(image), ""); + thumbnailButton->setIconSize(QSize(image.width(), image.height())); + connect(thumbnailButton, SIGNAL(clicked()), &m_ThumbnailMapper, SLOT(map())); + m_ThumbnailMapper.setMapping(thumbnailButton, fileName); + ui->thumbnailArea->addWidget(thumbnailButton); } + } } + } - ui->overwriteCount->display(numOverwrite); - ui->overwrittenCount->display(numOverwritten); - ui->noConflictCount->display(numNonConflicting); + ui->overwriteCount->display(numOverwrite); + ui->overwrittenCount->display(numOverwritten); + ui->noConflictCount->display(numNonConflicting); } -void ModInfoDialog::addCategories(const CategoryFactory& factory, const std::set& enabledCategories, - QTreeWidgetItem* root, int rootLevel) { - 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))); - newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); - newItem->setCheckState(0, enabledCategories.find(categoryID) != enabledCategories.end() ? Qt::Checked - : Qt::Unchecked); - newItem->setData(0, Qt::UserRole, categoryID); - if (factory.hasChildren(i)) { - addCategories(factory, enabledCategories, newItem, categoryID); - } - root->addChild(newItem); - } + +void ModInfoDialog::addCategories(const CategoryFactory &factory, const std::set &enabledCategories, QTreeWidgetItem *root, int rootLevel) +{ + 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))); + newItem->setFlags(newItem->flags() | Qt::ItemIsUserCheckable); + newItem->setCheckState(0, enabledCategories.find(categoryID) + != enabledCategories.end() + ? Qt::Checked + : Qt::Unchecked); + newItem->setData(0, Qt::UserRole, categoryID); + if (factory.hasChildren(i)) { + addCategories(factory, enabledCategories, newItem, categoryID); + } + root->addChild(newItem); + } } -void ModInfoDialog::saveCategories(QTreeWidgetItem* currentNode) { - for (int i = 0; i < currentNode->childCount(); ++i) { - QTreeWidgetItem* childNode = currentNode->child(i); - m_ModInfo->setCategory(childNode->data(0, Qt::UserRole).toInt(), childNode->checkState(0)); - saveCategories(childNode); - } + +void ModInfoDialog::saveCategories(QTreeWidgetItem *currentNode) +{ + for (int i = 0; i < currentNode->childCount(); ++i) { + QTreeWidgetItem *childNode = currentNode->child(i); + m_ModInfo->setCategory(childNode->data(0, Qt::UserRole).toInt(), childNode->checkState(0)); + saveCategories(childNode); + } } -void ModInfoDialog::on_closeButton_clicked() { - if (allowNavigateFromTXT() && allowNavigateFromINI()) { - this->close(); - } + +void ModInfoDialog::on_closeButton_clicked() +{ + if (allowNavigateFromTXT() && allowNavigateFromINI()) { + this->close(); + } } -QString ModInfoDialog::getModVersion() const { return m_Settings->value("version", "").toString(); } -const int ModInfoDialog::getModID() const { return m_Settings->value("modid", 0).toInt(); } -void ModInfoDialog::openTab(int tab) { - QTabWidget* tabWidget = findChild("tabWidget"); - if (tabWidget->isTabEnabled(tab)) { - tabWidget->setCurrentIndex(tab); - } +QString ModInfoDialog::getModVersion() const +{ + return m_Settings->value("version", "").toString(); } -void ModInfoDialog::thumbnailClicked(const QString& fileName) { - QLabel* imageLabel = findChild("imageLabel"); - imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); - QImage image(fileName); - if (static_cast(image.width()) / static_cast(image.height()) > 1.34) { - image = image.scaledToWidth(imageLabel->geometry().width()); - } else { - image = image.scaledToHeight(imageLabel->geometry().height()); - } - imageLabel->setPixmap(QPixmap::fromImage(image)); -} - -bool ModInfoDialog::allowNavigateFromTXT() { - if (ui->saveTXTButton->isEnabled()) { - int res = QMessageBox::question( - this, tr("Save changes?"), - tr("Save changes to \"%1\"?").arg(ui->textFileView->property("currentFile").toString()), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); - if (res == QMessageBox::Cancel) { - return false; - } else if (res == QMessageBox::Yes) { - saveCurrentTextFile(); - } - } - return true; + +const int ModInfoDialog::getModID() const +{ + return m_Settings->value("modid", 0).toInt(); } -bool ModInfoDialog::allowNavigateFromINI() { - if (ui->saveButton->isEnabled()) { - int res = QMessageBox::question( - this, tr("Save changes?"), - tr("Save changes to \"%1\"?").arg(ui->iniFileView->property("currentFile").toString()), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); - if (res == QMessageBox::Cancel) { - return false; - } else if (res == QMessageBox::Yes) { - saveCurrentIniFile(); - } - } - return true; +void ModInfoDialog::openTab(int tab) +{ + QTabWidget *tabWidget = findChild("tabWidget"); + if (tabWidget->isTabEnabled(tab)) { + tabWidget->setCurrentIndex(tab); + } } -void ModInfoDialog::on_textFileList_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous) { - QString fullPath = m_RootPath + "/" + current->text(); +void ModInfoDialog::thumbnailClicked(const QString &fileName) +{ + QLabel *imageLabel = findChild("imageLabel"); + imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored); + QImage image(fileName); + if (static_cast(image.width()) / static_cast(image.height()) > 1.34) { + image = image.scaledToWidth(imageLabel->geometry().width()); + } else { + image = image.scaledToHeight(imageLabel->geometry().height()); + } + imageLabel->setPixmap(QPixmap::fromImage(image)); +} - QVariant currentFile = ui->textFileView->property("currentFile"); - if (currentFile.isValid() && (currentFile.toString() == fullPath)) { - // the new file is the same as the currently displayed file. May be the result of a cancelation - return; +bool ModInfoDialog::allowNavigateFromTXT() +{ + if (ui->saveTXTButton->isEnabled()) { + int res = QMessageBox::question(this, tr("Save changes?"), tr("Save changes to \"%1\"?").arg(ui->textFileView->property("currentFile").toString()), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (res == QMessageBox::Cancel) { + return false; + } else if (res == QMessageBox::Yes) { + saveCurrentTextFile(); } + } + return true; +} - if (allowNavigateFromTXT()) { - openTextFile(fullPath); - } else { - ui->textFileList->setCurrentItem(previous, QItemSelectionModel::Current); + +bool ModInfoDialog::allowNavigateFromINI() +{ + if (ui->saveButton->isEnabled()) { + int res = QMessageBox::question(this, tr("Save changes?"), tr("Save changes to \"%1\"?").arg(ui->iniFileView->property("currentFile").toString()), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); + if (res == QMessageBox::Cancel) { + return false; + } else if (res == QMessageBox::Yes) { + saveCurrentIniFile(); } + } + return true; } -void ModInfoDialog::openTextFile(const QString& fileName) { - QString encoding; - ui->textFileView->setText(MOBase::readFileText(fileName, &encoding)); - ui->textFileView->setProperty("currentFile", fileName); - ui->textFileView->setProperty("encoding", encoding); - ui->saveTXTButton->setEnabled(false); + +void ModInfoDialog::on_textFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) +{ + QString fullPath = m_RootPath + "/" + current->text(); + + QVariant currentFile = ui->textFileView->property("currentFile"); + if (currentFile.isValid() && (currentFile.toString() == fullPath)) { + // the new file is the same as the currently displayed file. May be the result of a cancelation + return; + } + + if (allowNavigateFromTXT()) { + openTextFile(fullPath); + } else { + ui->textFileList->setCurrentItem(previous, QItemSelectionModel::Current); + } } -void ModInfoDialog::openIniFile(const QString& fileName) { - QFile iniFile(fileName); - iniFile.open(QIODevice::ReadOnly); - QByteArray buffer = iniFile.readAll(); - QTextCodec* codec = QTextCodec::codecForUtfText(buffer, QTextCodec::codecForName("utf-8")); - QTextEdit* iniFileView = findChild("iniFileView"); - iniFileView->setText(codec->toUnicode(buffer)); - iniFileView->setProperty("currentFile", fileName); - iniFileView->setProperty("encoding", codec->name()); - iniFile.close(); +void ModInfoDialog::openTextFile(const QString &fileName) +{ + QString encoding; + ui->textFileView->setText(MOBase::readFileText(fileName, &encoding)); + ui->textFileView->setProperty("currentFile", fileName); + ui->textFileView->setProperty("encoding", encoding); + ui->saveTXTButton->setEnabled(false); +} - ui->saveButton->setEnabled(false); + +void ModInfoDialog::openIniFile(const QString &fileName) +{ + QFile iniFile(fileName); + iniFile.open(QIODevice::ReadOnly); + QByteArray buffer = iniFile.readAll(); + + QTextCodec *codec = QTextCodec::codecForUtfText(buffer, QTextCodec::codecForName("utf-8")); + QTextEdit *iniFileView = findChild("iniFileView"); + iniFileView->setText(codec->toUnicode(buffer)); + iniFileView->setProperty("currentFile", fileName); + iniFileView->setProperty("encoding", codec->name()); + iniFile.close(); + + ui->saveButton->setEnabled(false); } -void ModInfoDialog::saveIniTweaks() { - m_Settings->beginWriteArray("INI Tweaks"); - int countEnabled = 0; - for (int i = 0; i < ui->iniTweaksList->count(); ++i) { - if (ui->iniTweaksList->item(i)->checkState() == Qt::Checked) { - m_Settings->setArrayIndex(countEnabled++); - m_Settings->setValue("name", ui->iniTweaksList->item(i)->text()); - } +void ModInfoDialog::saveIniTweaks() +{ + m_Settings->beginWriteArray("INI Tweaks"); + + int countEnabled = 0; + for (int i = 0; i < ui->iniTweaksList->count(); ++i) { + if (ui->iniTweaksList->item(i)->checkState() == Qt::Checked) { + m_Settings->setArrayIndex(countEnabled++); + m_Settings->setValue("name", ui->iniTweaksList->item(i)->text()); } - m_Settings->endArray(); + } + m_Settings->endArray(); } -void ModInfoDialog::on_iniFileList_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous) { - QString fullPath = m_RootPath + "/" + current->text(); - QVariant currentFile = ui->iniFileView->property("currentFile"); - if (currentFile.isValid() && (currentFile.toString() == fullPath)) { - // the new file is the same as the currently displayed file. May be the result of a cancelation - return; - } +void ModInfoDialog::on_iniFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) +{ + QString fullPath = m_RootPath + "/" + current->text(); - if (allowNavigateFromINI()) { - openIniFile(fullPath); - } else { - ui->iniFileList->setCurrentItem(previous, QItemSelectionModel::Current); - } + QVariant currentFile = ui->iniFileView->property("currentFile"); + if (currentFile.isValid() && (currentFile.toString() == fullPath)) { + // the new file is the same as the currently displayed file. May be the result of a cancelation + return; + } + + if (allowNavigateFromINI()) { + openIniFile(fullPath); + } else { + ui->iniFileList->setCurrentItem(previous, QItemSelectionModel::Current); + } } -void ModInfoDialog::on_iniTweaksList_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous) { - QString fullPath = m_RootPath + "/" + current->data(Qt::UserRole).toString(); - QVariant currentFile = ui->iniFileView->property("currentFile"); - if (currentFile.isValid() && (currentFile.toString() == fullPath)) { - // the new file is the same as the currently displayed file. May be the result of a cancelation - return; - } +void ModInfoDialog::on_iniTweaksList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) +{ + QString fullPath = m_RootPath + "/" + current->data(Qt::UserRole).toString(); + + QVariant currentFile = ui->iniFileView->property("currentFile"); + if (currentFile.isValid() && (currentFile.toString() == fullPath)) { + // the new file is the same as the currently displayed file. May be the result of a cancelation + return; + } + + if (allowNavigateFromINI()) { + openIniFile(fullPath); + } else { + ui->iniFileList->setCurrentItem(previous, QItemSelectionModel::Current); + } - if (allowNavigateFromINI()) { - openIniFile(fullPath); - } else { - ui->iniFileList->setCurrentItem(previous, QItemSelectionModel::Current); - } } -void ModInfoDialog::on_saveButton_clicked() { saveCurrentIniFile(); } -void ModInfoDialog::on_saveTXTButton_clicked() { saveCurrentTextFile(); } +void ModInfoDialog::on_saveButton_clicked() +{ + saveCurrentIniFile(); +} -void ModInfoDialog::saveCurrentTextFile() { - QVariant fileNameVar = ui->textFileView->property("currentFile"); - QVariant encodingVar = ui->textFileView->property("encoding"); - if (fileNameVar.isValid() && encodingVar.isValid()) { - QString fileName = fileNameVar.toString(); - QFile txtFile(fileName); - txtFile.open(QIODevice::WriteOnly); - txtFile.resize(0); - QTextCodec* codec = QTextCodec::codecForName(encodingVar.toString().toUtf8()); - QString data = ui->textFileView->toPlainText().replace("\n", "\r\n"); - txtFile.write(codec->fromUnicode(data)); - } else { - reportError("no file selected"); - } - ui->saveTXTButton->setEnabled(false); -} - -void ModInfoDialog::saveCurrentIniFile() { - QVariant fileNameVar = ui->iniFileView->property("currentFile"); - QVariant encodingVar = ui->iniFileView->property("encoding"); - if (fileNameVar.isValid() && !fileNameVar.toString().isEmpty()) { - QString fileName = fileNameVar.toString(); - QDir().mkpath(QFileInfo(fileName).absolutePath()); - QFile txtFile(fileName); - txtFile.open(QIODevice::WriteOnly); - txtFile.resize(0); - QTextCodec* codec = QTextCodec::codecForName(encodingVar.toString().toUtf8()); - QString data = ui->iniFileView->toPlainText().replace("\n", "\r\n"); - txtFile.write(codec->fromUnicode(data)); - } else { - reportError("no file selected"); - } - ui->saveButton->setEnabled(false); + +void ModInfoDialog::on_saveTXTButton_clicked() +{ + saveCurrentTextFile(); } -void ModInfoDialog::on_iniFileView_textChanged() { - QPushButton* saveButton = findChild("saveButton"); - saveButton->setEnabled(true); + +void ModInfoDialog::saveCurrentTextFile() +{ + QVariant fileNameVar = ui->textFileView->property("currentFile"); + QVariant encodingVar = ui->textFileView->property("encoding"); + if (fileNameVar.isValid() && encodingVar.isValid()) { + QString fileName = fileNameVar.toString(); + QFile txtFile(fileName); + txtFile.open(QIODevice::WriteOnly); + txtFile.resize(0); + QTextCodec *codec = QTextCodec::codecForName(encodingVar.toString().toUtf8()); + QString data = ui->textFileView->toPlainText().replace("\n", "\r\n"); + txtFile.write(codec->fromUnicode(data)); + } else { + reportError("no file selected"); + } + ui->saveTXTButton->setEnabled(false); } -void ModInfoDialog::on_textFileView_textChanged() { ui->saveTXTButton->setEnabled(true); } -void ModInfoDialog::on_activateESP_clicked() { - QListWidget* activeESPList = findChild("activeESPList"); - QListWidget* inactiveESPList = findChild("inactiveESPList"); +void ModInfoDialog::saveCurrentIniFile() +{ + QVariant fileNameVar = ui->iniFileView->property("currentFile"); + QVariant encodingVar = ui->iniFileView->property("encoding"); + if (fileNameVar.isValid() && !fileNameVar.toString().isEmpty()) { + QString fileName = fileNameVar.toString(); + QDir().mkpath(QFileInfo(fileName).absolutePath()); + QFile txtFile(fileName); + txtFile.open(QIODevice::WriteOnly); + txtFile.resize(0); + QTextCodec *codec = QTextCodec::codecForName(encodingVar.toString().toUtf8()); + QString data = ui->iniFileView->toPlainText().replace("\n", "\r\n"); + txtFile.write(codec->fromUnicode(data)); + } else { + reportError("no file selected"); + } + ui->saveButton->setEnabled(false); +} - int selectedRow = inactiveESPList->currentRow(); - if (selectedRow < 0) { - return; - } - QListWidgetItem* selectedItem = inactiveESPList->takeItem(selectedRow); - - QDir root(m_RootPath); - bool renamed = false; - - while (root.exists(selectedItem->text())) { - bool okClicked = false; - QString newName = - QInputDialog::getText(this, tr("File Exists"), tr("A file with that name exists, please enter a new one"), - QLineEdit::Normal, selectedItem->text(), &okClicked); - if (!okClicked) { - inactiveESPList->insertItem(selectedRow, selectedItem); - return; - } else if (newName.size() > 0) { - selectedItem->setText(newName); - renamed = true; - } +void ModInfoDialog::on_iniFileView_textChanged() +{ + QPushButton* saveButton = findChild("saveButton"); + saveButton->setEnabled(true); +} + + +void ModInfoDialog::on_textFileView_textChanged() +{ + ui->saveTXTButton->setEnabled(true); +} + + +void ModInfoDialog::on_activateESP_clicked() +{ + QListWidget *activeESPList = findChild("activeESPList"); + QListWidget *inactiveESPList = findChild("inactiveESPList"); + + int selectedRow = inactiveESPList->currentRow(); + if (selectedRow < 0) { + return; + } + + QListWidgetItem *selectedItem = inactiveESPList->takeItem(selectedRow); + + QDir root(m_RootPath); + bool renamed = false; + + while (root.exists(selectedItem->text())) { + bool okClicked = false; + QString newName = QInputDialog::getText(this, tr("File Exists"), tr("A file with that name exists, please enter a new one"), QLineEdit::Normal, selectedItem->text(), &okClicked); + if (!okClicked) { + inactiveESPList->insertItem(selectedRow, selectedItem); + return; + } else if (newName.size() > 0) { + selectedItem->setText(newName); + renamed = true; } + } - if (root.rename(selectedItem->data(Qt::UserRole).toString(), selectedItem->text())) { - activeESPList->addItem(selectedItem); - if (renamed) { - selectedItem->setData(Qt::UserRole, QVariant()); - } - } else { - inactiveESPList->insertItem(selectedRow, selectedItem); - reportError(tr("failed to move file")); + if (root.rename(selectedItem->data(Qt::UserRole).toString(), selectedItem->text())) { + activeESPList->addItem(selectedItem); + if (renamed) { + selectedItem->setData(Qt::UserRole, QVariant()); } + } else { + inactiveESPList->insertItem(selectedRow, selectedItem); + reportError(tr("failed to move file")); + } } -void ModInfoDialog::on_deactivateESP_clicked() { - QListWidget* activeESPList = findChild("activeESPList"); - QListWidget* inactiveESPList = findChild("inactiveESPList"); - int selectedRow = activeESPList->currentRow(); - if (selectedRow < 0) { - return; - } +void ModInfoDialog::on_deactivateESP_clicked() +{ + QListWidget *activeESPList = findChild("activeESPList"); + QListWidget *inactiveESPList = findChild("inactiveESPList"); - QDir root(m_RootPath); + int selectedRow = activeESPList->currentRow(); + if (selectedRow < 0) { + return; + } - QListWidgetItem* selectedItem = activeESPList->takeItem(selectedRow); + QDir root(m_RootPath); - // if we moved the file from optional to active in this session, we move the file back to - // where it came from. Otherwise, it is moved to the new folder "optional" - if (selectedItem->data(Qt::UserRole).isNull()) { - selectedItem->setData(Qt::UserRole, QString("optional/") + selectedItem->text()); - if (!root.exists("optional")) { - if (!root.mkdir("optional")) { - reportError(tr("failed to create directory \"optional\"")); - activeESPList->insertItem(selectedRow, selectedItem); - return; - } - } - } + QListWidgetItem *selectedItem = activeESPList->takeItem(selectedRow); - if (root.rename(selectedItem->text(), selectedItem->data(Qt::UserRole).toString())) { - inactiveESPList->addItem(selectedItem); - } else { + // if we moved the file from optional to active in this session, we move the file back to + // where it came from. Otherwise, it is moved to the new folder "optional" + if (selectedItem->data(Qt::UserRole).isNull()) { + selectedItem->setData(Qt::UserRole, QString("optional/") + selectedItem->text()); + if (!root.exists("optional")) { + if (!root.mkdir("optional")) { + reportError(tr("failed to create directory \"optional\"")); activeESPList->insertItem(selectedRow, selectedItem); + return; + } } + } + + if (root.rename(selectedItem->text(), selectedItem->data(Qt::UserRole).toString())) { + inactiveESPList->addItem(selectedItem); + } else { + activeESPList->insertItem(selectedRow, selectedItem); + } } -void ModInfoDialog::on_visitNexusLabel_linkActivated(const QString& link) { - this->close(); - emit nexusLinkActivated(link); +void ModInfoDialog::on_visitNexusLabel_linkActivated(const QString &link) +{ + this->close(); + emit nexusLinkActivated(link); } -void ModInfoDialog::linkClicked(const QUrl& url) { - // Ideally we'd ask the mod for the game and the web service then pass the game - // and URL to the web service - if (NexusInterface::instance()->isURLGameRelated(url)) { - this->close(); - emit nexusLinkActivated(url.toString()); - } else { - ::ShellExecuteW(nullptr, L"open", ToWString(url.toString()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); - } +void ModInfoDialog::linkClicked(const QUrl &url) +{ + //Ideally we'd ask the mod for the game and the web service then pass the game + //and URL to the web service + if (NexusInterface::instance()->isURLGameRelated(url)) { + this->close(); + emit nexusLinkActivated(url.toString()); + } else { + ::ShellExecuteW(nullptr, L"open", ToWString(url.toString()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); + } } -void ModInfoDialog::refreshNexusData(int modID) { - if ((!m_RequestStarted) && (modID > 0)) { - m_RequestStarted = true; - m_ModInfo->updateNXMInfo(); +void ModInfoDialog::refreshNexusData(int modID) +{ + if ((!m_RequestStarted) && (modID > 0)) { + m_RequestStarted = true; + + m_ModInfo->updateNXMInfo(); - MessageDialog::showMessage(tr("Info requested, please wait"), this); - } + MessageDialog::showMessage(tr("Info requested, please wait"), this); + } } + /*void ModInfoDialog::nxmDescriptionAvailable(int, QVariant, QVariant resultData, int requestID) { std::set::iterator idIter = m_RequestIDs.find(requestID); @@ -678,8 +747,7 @@ void ModInfoDialog::refreshNexusData(int modID) { // QString descriptionAsHTML = BBCode::convertToHTML(result["description"].toString()); ui->descriptionView->setHtml(descriptionAsHTML); } else { - ui->descriptionView->setHtml(result["summary"].toString().append(QString("\r\n") + tr("(description incomplete, -please visit nexus)"))); + ui->descriptionView->setHtml(result["summary"].toString().append(QString("\r\n") + tr("(description incomplete, please visit nexus)"))); } QLineEdit *versionEdit = findChild("versionEdit"); @@ -703,454 +771,506 @@ please visit nexus)"))); } }*/ -QString ModInfoDialog::getFileCategory(int categoryID) { - switch (categoryID) { - case 1: - return tr("Main"); - case 2: - return tr("Update"); - case 3: - return tr("Optional"); - case 4: - return tr("Old"); - case 5: - return tr("Misc"); - default: - return tr("Unknown"); - } + +QString ModInfoDialog::getFileCategory(int categoryID) +{ + switch (categoryID) { + case 1: return tr("Main"); + case 2: return tr("Update"); + case 3: return tr("Optional"); + case 4: return tr("Old"); + case 5: return tr("Misc"); + default: return tr("Unknown"); + } +} + + +void ModInfoDialog::updateVersionColor() +{ +// QPalette versionColor; + if (m_ModInfo->getVersion() != m_ModInfo->getNewestVersion()) { + ui->versionEdit->setStyleSheet("color: red"); +// versionColor.setColor(QPalette::Text, Qt::red); + ui->versionEdit->setToolTip(tr("Current Version: %1").arg(m_ModInfo->getNewestVersion().canonicalString())); + } else { + ui->versionEdit->setStyleSheet("color: green"); +// versionColor.setColor(QPalette::Text, Qt::green); + ui->versionEdit->setToolTip(tr("No update available")); + } +// ui->versionEdit->setPalette(versionColor); } -void ModInfoDialog::updateVersionColor() { - // QPalette versionColor; - if (m_ModInfo->getVersion() != m_ModInfo->getNewestVersion()) { - ui->versionEdit->setStyleSheet("color: red"); - // versionColor.setColor(QPalette::Text, Qt::red); - ui->versionEdit->setToolTip(tr("Current Version: %1").arg(m_ModInfo->getNewestVersion().canonicalString())); + +void ModInfoDialog::modDetailsUpdated(bool success) +{ + if (success) { + QString nexusDescription = m_ModInfo->getNexusDescription(); + if (!nexusDescription.isEmpty()) { + /* QString input = + "[size=20]sizetest[/size]\r\n" + "[COLOR=yellow]colortest[/COLOR]\r\n" + "[center]centertest[/center]\r\n" + "[quote]quotetest 1[/quote]\r\n" + "[quote=bla]quotetest 2[/quote]\r\n" + "[url]www.skyrimnexus.com[/url]\r\n" + "[url=www.skyrimnexus.com]urltest 2[/url]\r\n" + "[ol]\r\n" + "[li]item 2[/li]" + "[*]item 1\r\n" + "[/ol]\r\n" + "[img]http://www.bbcode.org/images/bbcode_logo.png[/img]\r\n" + "[table][tr][th]headertest1[/th]" + "[th]headertest2[/th][/tr]" + "[tr][td]rowtest11[/td][td]rowtest12[/td][/tr]" + "[tr][td]rowtest21[/td][td]rowtest22[/td][/tr][/table]" + "[email=\"sherb@gmx.net\"]mail me[/email]"; + ui->descriptionView->setHtml(BBCode::convertToHTML(input));*/ + + QString descriptionAsHTML = + QString("" + "" + "%1" + "").arg(BBCode::convertToHTML(nexusDescription)); + + ui->descriptionView->page()->setHtml(descriptionAsHTML); + + // QString descriptionAsHTML = BBCode::convertToHTML(result["description"].toString()); + // ui->descriptionView->setHtml(descriptionAsHTML); } else { - ui->versionEdit->setStyleSheet("color: green"); - // versionColor.setColor(QPalette::Text, Qt::green); - ui->versionEdit->setToolTip(tr("No update available")); + // ui->descriptionView->setHtml(result["summary"].toString().append(QString("\r\n") + tr("(description incomplete, please visit nexus)"))); + ui->descriptionView->page()->setHtml(tr("(description incomplete, please visit nexus)")); } - // ui->versionEdit->setPalette(versionColor); -} - -void ModInfoDialog::modDetailsUpdated(bool success) { - if (success) { - QString nexusDescription = m_ModInfo->getNexusDescription(); - if (!nexusDescription.isEmpty()) { - /* QString input = - "[size=20]sizetest[/size]\r\n" - "[COLOR=yellow]colortest[/COLOR]\r\n" - "[center]centertest[/center]\r\n" - "[quote]quotetest 1[/quote]\r\n" - "[quote=bla]quotetest 2[/quote]\r\n" - "[url]www.skyrimnexus.com[/url]\r\n" - "[url=www.skyrimnexus.com]urltest 2[/url]\r\n" - "[ol]\r\n" - "[li]item 2[/li]" - "[*]item 1\r\n" - "[/ol]\r\n" - "[img]http://www.bbcode.org/images/bbcode_logo.png[/img]\r\n" - "[table][tr][th]headertest1[/th]" - "[th]headertest2[/th][/tr]" - "[tr][td]rowtest11[/td][td]rowtest12[/td][/tr]" - "[tr][td]rowtest21[/td][td]rowtest22[/td][/tr][/table]" - "[email=\"sherb@gmx.net\"]mail me[/email]"; - ui->descriptionView->setHtml(BBCode::convertToHTML(input));*/ - - QString descriptionAsHTML = - QString("" - "" - "%1" - "") - .arg(BBCode::convertToHTML(nexusDescription)); - - ui->descriptionView->page()->setHtml(descriptionAsHTML); - - // QString descriptionAsHTML = BBCode::convertToHTML(result["description"].toString()); - // ui->descriptionView->setHtml(descriptionAsHTML); - } else { - // ui->descriptionView->setHtml(result["summary"].toString().append(QString("\r\n") + tr("(description - // incomplete, please visit nexus)"))); - ui->descriptionView->page()->setHtml(tr("(description incomplete, please visit nexus)")); - } - updateVersionColor(); - } + updateVersionColor(); + } } -void ModInfoDialog::activateNexusTab() { - QLineEdit* modIDEdit = findChild("modIDEdit"); - int modID = modIDEdit->text().toInt(); - if (modID != 0) { - QString nexusLink = NexusInterface::instance()->getModURL(modID); - QLabel* visitNexusLabel = findChild("visitNexusLabel"); - visitNexusLabel->setText(tr("Visit on Nexus").arg(nexusLink)); - visitNexusLabel->setToolTip(nexusLink); - - if (m_ModInfo->getNexusDescription().isEmpty() || - QDateTime::currentDateTime() > m_ModInfo->getLastNexusQuery().addDays(1)) { - refreshNexusData(modID); - } else { - this->modDetailsUpdated(true); - } + +void ModInfoDialog::activateNexusTab() +{ + QLineEdit *modIDEdit = findChild("modIDEdit"); + int modID = modIDEdit->text().toInt(); + if (modID != 0) { + QString nexusLink = NexusInterface::instance()->getModURL(modID); + QLabel *visitNexusLabel = findChild("visitNexusLabel"); + visitNexusLabel->setText(tr("Visit on Nexus").arg(nexusLink)); + visitNexusLabel->setToolTip(nexusLink); + + if (m_ModInfo->getNexusDescription().isEmpty() || + QDateTime::currentDateTime() > m_ModInfo->getLastNexusQuery().addDays(1)) { + refreshNexusData(modID); + } else { + this->modDetailsUpdated(true); } - QLineEdit* versionEdit = findChild("versionEdit"); - QString currentVersion = m_Settings->value("version", "0.0").toString(); - versionEdit->setText(currentVersion); + } + QLineEdit *versionEdit = findChild("versionEdit"); + QString currentVersion = m_Settings->value("version", "0.0").toString(); + versionEdit->setText(currentVersion); } -void ModInfoDialog::on_tabWidget_currentChanged(int index) { - if (m_RealTabPos[index] == TAB_NEXUS) { - activateNexusTab(); - } + +void ModInfoDialog::on_tabWidget_currentChanged(int index) +{ + if (m_RealTabPos[index] == TAB_NEXUS) { + activateNexusTab(); + } } -void ModInfoDialog::on_modIDEdit_editingFinished() { - int oldID = m_Settings->value("modid", 0).toInt(); - int modID = ui->modIDEdit->text().toInt(); - if (oldID != modID) { - m_ModInfo->setNexusID(modID); - ui->descriptionView->page()->setHtml(""); - if (modID != 0) { - m_RequestStarted = false; - refreshNexusData(modID); - } +void ModInfoDialog::on_modIDEdit_editingFinished() +{ + int oldID = m_Settings->value("modid", 0).toInt(); + int modID = ui->modIDEdit->text().toInt(); + if (oldID != modID){ + m_ModInfo->setNexusID(modID); + + ui->descriptionView->page()->setHtml(""); + if (modID != 0) { + m_RequestStarted = false; + refreshNexusData(modID); } + } } -void ModInfoDialog::on_versionEdit_editingFinished() { - VersionInfo version(ui->versionEdit->text()); - m_ModInfo->setVersion(version); - updateVersionColor(); +void ModInfoDialog::on_versionEdit_editingFinished() +{ + VersionInfo version(ui->versionEdit->text()); + m_ModInfo->setVersion(version); + updateVersionColor(); } -bool ModInfoDialog::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)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); - return false; - } - } else { - if (!m_FileSystemModel->remove(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); - return false; - } - } - } - if (!m_FileSystemModel->remove(index)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); + +bool ModInfoDialog::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)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); + return false; + } + } else { + if (!m_FileSystemModel->remove(childIndex)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); return false; + } } - return true; + } + if (!m_FileSystemModel->remove(index)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); + return false; + } + return true; } -void ModInfoDialog::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 ModInfoDialog::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 ModInfoDialog::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 sure you want to delete \"%1\"?").arg(fileName), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } else { - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } + +void ModInfoDialog::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 sure you want to delete \"%1\"?").arg(fileName), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; } + } else { + if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } - foreach (QModelIndex index, m_FileSelection) { deleteFile(index); } + foreach(QModelIndex index, m_FileSelection) { + deleteFile(index); + } } -void ModInfoDialog::renameTriggered() { - QModelIndex selection = m_FileSelection.at(0); - QModelIndex index = selection.sibling(selection.row(), 0); - if (!index.isValid() || m_FileSystemModel->isReadOnly()) { - return; - } - ui->fileTree->edit(index); +void ModInfoDialog::renameTriggered() +{ + QModelIndex selection = m_FileSelection.at(0); + QModelIndex index = selection.sibling(selection.row(), 0); + if (!index.isValid() || m_FileSystemModel->isReadOnly()) { + return; + } + + ui->fileTree->edit(index); } -void ModInfoDialog::hideTriggered() { - for (QModelIndexList::const_iterator iter = m_FileSelection.constBegin(); iter != m_FileSelection.constEnd(); - ++iter) { - QString path = m_FileSystemModel->filePath(*iter); - if (!path.endsWith(ModInfo::s_HiddenExt)) { - hideFile(path); - } + +void ModInfoDialog::hideTriggered() +{ + for (QModelIndexList::const_iterator iter = m_FileSelection.constBegin(); + iter != m_FileSelection.constEnd(); ++iter) { + QString path = m_FileSystemModel->filePath(*iter); + if (!path.endsWith(ModInfo::s_HiddenExt)) { + hideFile(path); } + } } -void ModInfoDialog::unhideTriggered() { - for (QModelIndexList::const_iterator iter = m_FileSelection.constBegin(); iter != m_FileSelection.constEnd(); - ++iter) { - QString path = m_FileSystemModel->filePath(*iter); - if (path.endsWith(ModInfo::s_HiddenExt)) { - unhideFile(path); - } + +void ModInfoDialog::unhideTriggered() +{ + for (QModelIndexList::const_iterator iter = m_FileSelection.constBegin(); + iter != m_FileSelection.constEnd(); ++iter) { + QString path = m_FileSystemModel->filePath(*iter); + if (path.endsWith(ModInfo::s_HiddenExt)) { + unhideFile(path); } + } } -void ModInfoDialog::openFile(const QModelIndex& index) { - QString fileName = m_FileSystemModel->filePath(index); - HINSTANCE res = ::ShellExecuteW(nullptr, L"open", ToWString(fileName).c_str(), nullptr, nullptr, SW_SHOW); - if ((int)res <= 32) { - qCritical("failed to invoke %s: %d", fileName.toUtf8().constData(), res); - } +void ModInfoDialog::openFile(const QModelIndex &index) +{ + QString fileName = m_FileSystemModel->filePath(index); + + HINSTANCE res = ::ShellExecuteW(nullptr, L"open", ToWString(fileName).c_str(), nullptr, nullptr, SW_SHOW); + if ((int)res <= 32) { + qCritical("failed to invoke %s: %d", fileName.toUtf8().constData(), res); + } } -void ModInfoDialog::openTriggered() { - foreach (QModelIndex idx, m_FileSelection) { openFile(idx); } + +void ModInfoDialog::openTriggered() +{ + foreach(QModelIndex idx, m_FileSelection) { + openFile(idx); + } } -void ModInfoDialog::createDirectoryTriggered() { - QModelIndex selection = m_FileSelection.at(0); +void ModInfoDialog::createDirectoryTriggered() +{ + QModelIndex selection = m_FileSelection.at(0); - QModelIndex index = m_FileSystemModel->isDir(selection) ? selection : selection.parent(); - index = index.sibling(index.row(), 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("/"); + 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 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; - } + QModelIndex newIndex = m_FileSystemModel->mkdir(index, name); + if (!newIndex.isValid()) { + reportError(tr("Failed to create \"%1\"").arg(name)); + return; + } - ui->fileTree->setCurrentIndex(newIndex); - ui->fileTree->edit(newIndex); + ui->fileTree->setCurrentIndex(newIndex); + ui->fileTree->edit(newIndex); } -void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint& pos) { - QItemSelectionModel* selectionModel = ui->fileTree->selectionModel(); - m_FileSelection = selectionModel->selectedRows(0); - // m_FileSelection = ui->fileTree->indexAt(pos); - QMenu menu(ui->fileTree); +void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos) +{ + QItemSelectionModel *selectionModel = ui->fileTree->selectionModel(); + m_FileSelection = selectionModel->selectedRows(0); - menu.addAction(m_NewFolderAction); +// m_FileSelection = ui->fileTree->indexAt(pos); + QMenu menu(ui->fileTree); - bool hasFiles = false; + menu.addAction(m_NewFolderAction); - foreach (QModelIndex idx, m_FileSelection) { - if (m_FileSystemModel->fileInfo(idx).isFile()) { - hasFiles = true; - break; - } + 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); - if (m_FileSystemModel->fileName(m_FileSelection.at(0)).endsWith(ModInfo::s_HiddenExt)) { - menu.addAction(m_UnhideAction); - } else { - menu.addAction(m_HideAction); - } + if (selectionModel->hasSelection()) { + if (hasFiles) { + menu.addAction(m_OpenAction); + } + menu.addAction(m_RenameAction); + menu.addAction(m_DeleteAction); + if (m_FileSystemModel->fileName(m_FileSelection.at(0)).endsWith(ModInfo::s_HiddenExt)) { + menu.addAction(m_UnhideAction); } else { - m_FileSelection.clear(); - m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); + menu.addAction(m_HideAction); } - menu.exec(ui->fileTree->mapToGlobal(pos)); + } else { + m_FileSelection.clear(); + m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); + } + menu.exec(ui->fileTree->mapToGlobal(pos)); } -void ModInfoDialog::on_categoriesTree_itemChanged(QTreeWidgetItem* item, int) { - QTreeWidgetItem* parent = item->parent(); - while ((parent != nullptr) && ((parent->flags() & Qt::ItemIsUserCheckable) != 0) && - (parent->checkState(0) == Qt::Unchecked)) { - parent->setCheckState(0, Qt::Checked); - parent = parent->parent(); - } - refreshPrimaryCategoriesBox(); + +void ModInfoDialog::on_categoriesTree_itemChanged(QTreeWidgetItem *item, int) +{ + QTreeWidgetItem *parent = item->parent(); + while ((parent != nullptr) && ((parent->flags() & Qt::ItemIsUserCheckable) != 0) && (parent->checkState(0) == Qt::Unchecked)) { + parent->setCheckState(0, Qt::Checked); + parent = parent->parent(); + } + refreshPrimaryCategoriesBox(); } -void ModInfoDialog::addCheckedCategories(QTreeWidgetItem* tree) { - for (int i = 0; i < tree->childCount(); ++i) { - QTreeWidgetItem* child = tree->child(i); - if (child->checkState(0) == Qt::Checked) { - ui->primaryCategoryBox->addItem(child->text(0), child->data(0, Qt::UserRole)); - addCheckedCategories(child); - } + +void ModInfoDialog::addCheckedCategories(QTreeWidgetItem *tree) +{ + for (int i = 0; i < tree->childCount(); ++i) { + QTreeWidgetItem *child = tree->child(i); + if (child->checkState(0) == Qt::Checked) { + ui->primaryCategoryBox->addItem(child->text(0), child->data(0, Qt::UserRole)); + addCheckedCategories(child); } + } } -void ModInfoDialog::refreshPrimaryCategoriesBox() { - ui->primaryCategoryBox->clear(); - int primaryCategory = m_ModInfo->getPrimaryCategory(); - addCheckedCategories(ui->categoriesTree->invisibleRootItem()); - for (int i = 0; i < ui->primaryCategoryBox->count(); ++i) { - if (ui->primaryCategoryBox->itemData(i).toInt() == primaryCategory) { - ui->primaryCategoryBox->setCurrentIndex(i); - break; - } + +void ModInfoDialog::refreshPrimaryCategoriesBox() +{ + ui->primaryCategoryBox->clear(); + int primaryCategory = m_ModInfo->getPrimaryCategory(); + addCheckedCategories(ui->categoriesTree->invisibleRootItem()); + for (int i = 0; i < ui->primaryCategoryBox->count(); ++i) { + if (ui->primaryCategoryBox->itemData(i).toInt() == primaryCategory) { + ui->primaryCategoryBox->setCurrentIndex(i); + break; } + } } -void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) { - if (index != -1) { - m_ModInfo->setPrimaryCategory(ui->primaryCategoryBox->itemData(index).toInt()); - } + +void ModInfoDialog::on_primaryCategoryBox_currentIndexChanged(int index) +{ + if (index != -1) { + m_ModInfo->setPrimaryCategory(ui->primaryCategoryBox->itemData(index).toInt()); + } } -void ModInfoDialog::on_overwriteTree_itemDoubleClicked(QTreeWidgetItem* item, int) { - this->close(); - emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); + +void ModInfoDialog::on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int) +{ + this->close(); + emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); } -bool ModInfoDialog::hideFile(const QString& oldName) { - QString newName = oldName + ModInfo::s_HiddenExt; - if (QFileInfo(newName).exists()) { - if (QMessageBox::question(this, tr("Replace file?"), - tr("There already is a hidden version of this file. Replace it?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - if (!QFile(newName).remove()) { - QMessageBox::critical( - this, tr("File operation failed"), - tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); - return false; - } - } else { - return false; - } - } +bool ModInfoDialog::hideFile(const QString &oldName) +{ + QString newName = oldName + ModInfo::s_HiddenExt; - if (QFile::rename(oldName, newName)) { - return true; - } else { - reportError(tr("failed to rename %1 to %2").arg(oldName).arg(QDir::toNativeSeparators(newName))); + if (QFileInfo(newName).exists()) { + if (QMessageBox::question(this, tr("Replace file?"), tr("There already is a hidden version of this file. Replace it?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (!QFile(newName).remove()) { + QMessageBox::critical(this, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); return false; + } + } else { + return false; } + } + + if (QFile::rename(oldName, newName)) { + return true; + } else { + reportError(tr("failed to rename %1 to %2").arg(oldName).arg(QDir::toNativeSeparators(newName))); + return false; + } } -bool ModInfoDialog::unhideFile(const QString& oldName) { - QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); - if (QFileInfo(newName).exists()) { - if (QMessageBox::question(this, tr("Replace file?"), - tr("There already is a visible version of this file. Replace it?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - if (!QFile(newName).remove()) { - QMessageBox::critical( - this, tr("File operation failed"), - tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); - return false; - } - } else { - return false; - } - } - if (QFile::rename(oldName, newName)) { - return true; - } else { - reportError(tr("failed to rename %1 to %2") - .arg(QDir::toNativeSeparators(oldName)) - .arg(QDir::toNativeSeparators(newName))); + +bool ModInfoDialog::unhideFile(const QString &oldName) +{ + QString newName = oldName.left(oldName.length() - ModInfo::s_HiddenExt.length()); + if (QFileInfo(newName).exists()) { + if (QMessageBox::question(this, tr("Replace file?"), tr("There already is a visible version of this file. Replace it?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + if (!QFile(newName).remove()) { + QMessageBox::critical(this, tr("File operation failed"), tr("Failed to remove \"%1\". Maybe you lack the required file permissions?").arg(newName)); return false; + } + } else { + return false; } + } + if (QFile::rename(oldName, newName)) { + return true; + } else { + reportError(tr("failed to rename %1 to %2").arg(QDir::toNativeSeparators(oldName)).arg(QDir::toNativeSeparators(newName))); + return false; + } } -void ModInfoDialog::hideConflictFile() { - if (hideFile(m_ConflictsContextItem->data(0, Qt::UserRole).toString())) { - emit originModified(m_Origin->getID()); - refreshLists(); - } + +void ModInfoDialog::hideConflictFile() +{ + if (hideFile(m_ConflictsContextItem->data(0, Qt::UserRole).toString())) { + emit originModified(m_Origin->getID()); + refreshLists(); + } } -void ModInfoDialog::unhideConflictFile() { - if (unhideFile(m_ConflictsContextItem->data(0, Qt::UserRole).toString())) { - emit originModified(m_Origin->getID()); - refreshLists(); - } + +void ModInfoDialog::unhideConflictFile() +{ + if (unhideFile(m_ConflictsContextItem->data(0, Qt::UserRole).toString())) { + emit originModified(m_Origin->getID()); + refreshLists(); + } } -void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint& pos) { - m_ConflictsContextItem = ui->overwriteTree->itemAt(pos.x(), pos.y()); - if (m_ConflictsContextItem != nullptr) { - // offer to hide/unhide file, but not for files from archives - if (!m_ConflictsContextItem->data(1, Qt::UserRole + 2).toBool()) { - QMenu menu; - if (m_ConflictsContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) { - menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFile())); - } else { - menu.addAction(tr("Hide"), this, SLOT(hideConflictFile())); - } - menu.exec(ui->overwriteTree->mapToGlobal(pos)); - } +void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos) +{ + m_ConflictsContextItem = ui->overwriteTree->itemAt(pos.x(), pos.y()); + + if (m_ConflictsContextItem != nullptr) { + // offer to hide/unhide file, but not for files from archives + if (!m_ConflictsContextItem->data(1, Qt::UserRole + 2).toBool()) { + QMenu menu; + if (m_ConflictsContextItem->text(0).endsWith(ModInfo::s_HiddenExt)) { + menu.addAction(tr("Un-Hide"), this, SLOT(unhideConflictFile())); + } else { + menu.addAction(tr("Hide"), this, SLOT(hideConflictFile())); + } + menu.exec(ui->overwriteTree->mapToGlobal(pos)); } + } } -void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem* item, int) { - emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); - this->accept(); + +void ModInfoDialog::on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int) +{ + emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS); + this->accept(); } -void ModInfoDialog::on_refreshButton_clicked() { - m_ModInfo->updateNXMInfo(); +void ModInfoDialog::on_refreshButton_clicked() +{ + m_ModInfo->updateNXMInfo(); - MessageDialog::showMessage(tr("Info requested, please wait"), this); + MessageDialog::showMessage(tr("Info requested, please wait"), this); } -void ModInfoDialog::on_endorseBtn_clicked() { emit endorseMod(m_ModInfo); } +void ModInfoDialog::on_endorseBtn_clicked() +{ + emit endorseMod(m_ModInfo); +} -void ModInfoDialog::on_nextButton_clicked() { - emit modOpenNext(); - this->accept(); +void ModInfoDialog::on_nextButton_clicked() +{ + emit modOpenNext(); + this->accept(); } -void ModInfoDialog::on_prevButton_clicked() { - emit modOpenPrev(); - this->accept(); +void ModInfoDialog::on_prevButton_clicked() +{ + emit modOpenPrev(); + this->accept(); } -void ModInfoDialog::createTweak() { - QString name = QInputDialog::getText(this, tr("Name"), tr("Please enter a name")); - if (name.isNull()) { - return; - } else if (!fixDirectoryName(name)) { - QMessageBox::critical(this, tr("Error"), tr("Invalid name. Must be a valid file name")); - return; - } else if (ui->iniTweaksList->findItems(name, Qt::MatchFixedString).count() != 0) { - QMessageBox::critical(this, tr("Error"), tr("A tweak by that name exists")); - return; - } - QListWidgetItem* newTweak = new QListWidgetItem(name + ".ini"); - newTweak->setData(Qt::UserRole, "INI Tweaks/" + name + ".ini"); - newTweak->setFlags(newTweak->flags() | Qt::ItemIsUserCheckable); - newTweak->setCheckState(Qt::Unchecked); - ui->iniTweaksList->addItem(newTweak); +void ModInfoDialog::createTweak() +{ + QString name = QInputDialog::getText(this, tr("Name"), tr("Please enter a name")); + if (name.isNull()) { + return; + } else if (!fixDirectoryName(name)) { + QMessageBox::critical(this, tr("Error"), tr("Invalid name. Must be a valid file name")); + return; + } else if (ui->iniTweaksList->findItems(name, Qt::MatchFixedString).count() != 0) { + QMessageBox::critical(this, tr("Error"), tr("A tweak by that name exists")); + return; + } + + QListWidgetItem *newTweak = new QListWidgetItem(name + ".ini"); + newTweak->setData(Qt::UserRole, "INI Tweaks/" + name + ".ini"); + newTweak->setFlags(newTweak->flags() | Qt::ItemIsUserCheckable); + newTweak->setCheckState(Qt::Unchecked); + ui->iniTweaksList->addItem(newTweak); } -void ModInfoDialog::on_iniTweaksList_customContextMenuRequested(const QPoint& pos) { - QMenu menu; - menu.addAction(tr("Create Tweak"), this, SLOT(createTweak())); - menu.exec(ui->iniTweaksList->mapToGlobal(pos)); +void ModInfoDialog::on_iniTweaksList_customContextMenuRequested(const QPoint &pos) +{ + QMenu menu; + menu.addAction(tr("Create Tweak"), this, SLOT(createTweak())); + menu.exec(ui->iniTweaksList->mapToGlobal(pos)); } diff --git a/src/modinfodialog.h b/src/modinfodialog.h index 514ef331..312ff99e 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -20,24 +20,26 @@ along with Mod Organizer. If not, see . #ifndef MODINFODIALOG_H #define MODINFODIALOG_H + #include "modinfo.h" #include "tutorabledialog.h" -#include #include -#include -#include +#include +#include #include #include -#include -#include -#include +#include +#include +#include #include -#include +#include #include +#include + namespace Ui { -class ModInfoDialog; + class ModInfoDialog; } class QFileSystemModel; @@ -48,184 +50,187 @@ class CategoryFactory; * this is a larger dialog used to visualise information abount the mod. * @todo this would probably a good place for a plugin-system **/ -class ModInfoDialog : public MOBase::TutorableDialog { +class ModInfoDialog : public MOBase::TutorableDialog +{ Q_OBJECT public: - enum ETabs { - TAB_TEXTFILES, - TAB_INIFILES, - TAB_IMAGES, - TAB_ESPS, - TAB_CONFLICTS, - TAB_CATEGORIES, - TAB_NEXUS, - TAB_NOTES, - TAB_FILETREE - }; + + enum ETabs { + TAB_TEXTFILES, + TAB_INIFILES, + TAB_IMAGES, + TAB_ESPS, + TAB_CONFLICTS, + TAB_CATEGORIES, + TAB_NEXUS, + TAB_NOTES, + TAB_FILETREE + }; public: - /** - * @brief constructor - * - * @param modInfo info structure about the mod to display - * @param parent parend widget - **/ - explicit ModInfoDialog(ModInfo::Ptr modInfo, const MOShared::DirectoryEntry* directory, bool unmanaged, - QWidget* parent = 0); - ~ModInfoDialog(); - - /** - * @brief retrieve the (user-modified) version of the mod - * - * @return the (user-modified) version of the mod - **/ - QString getModVersion() const; - - /** - * @brief retrieve the (user-modified) mod id - * - * @return the (user-modified) id of the mod - **/ - const int getModID() const; - - /** - * @brief open the specified tab in the dialog if it's enabled - * - * @param tab the tab to activate - **/ - void openTab(int tab); - - void restoreTabState(const QByteArray& state); - - QByteArray saveTabState() const; + + /** + * @brief constructor + * + * @param modInfo info structure about the mod to display + * @param parent parend widget + **/ + explicit ModInfoDialog(ModInfo::Ptr modInfo, const MOShared::DirectoryEntry *directory, bool unmanaged, QWidget *parent = 0); + ~ModInfoDialog(); + + /** + * @brief retrieve the (user-modified) version of the mod + * + * @return the (user-modified) version of the mod + **/ + QString getModVersion() const; + + /** + * @brief retrieve the (user-modified) mod id + * + * @return the (user-modified) id of the mod + **/ + const int getModID() const; + + /** + * @brief open the specified tab in the dialog if it's enabled + * + * @param tab the tab to activate + **/ + void openTab(int tab); + + void restoreTabState(const QByteArray &state); + + QByteArray saveTabState() const; signals: - void thumbnailClickedSignal(const QString& filename); - void nexusLinkActivated(const QString& link); - void downloadRequest(const QString& link); - void modOpen(const QString& modName, int tab); - void modOpenNext(); - void modOpenPrev(); - void originModified(int originID); - void endorseMod(ModInfo::Ptr nexusID); + void thumbnailClickedSignal(const QString &filename); + void nexusLinkActivated(const QString &link); + void downloadRequest(const QString &link); + void modOpen(const QString &modName, int tab); + void modOpenNext(); + void modOpenPrev(); + void originModified(int originID); + void endorseMod(ModInfo::Ptr nexusID); public slots: - void modDetailsUpdated(bool success); + void modDetailsUpdated(bool success); private: - void initFiletree(ModInfo::Ptr modInfo); - void initINITweaks(); - - void refreshLists(); - - void addCategories(const CategoryFactory& factory, const std::set& enabledCategories, QTreeWidgetItem* root, - int rootLevel); - - void updateVersionColor(); - - void refreshNexusData(int modID); - void activateNexusTab(); - QString getFileCategory(int categoryID); - bool recursiveDelete(const QModelIndex& index); - void deleteFile(const QModelIndex& index); - void openFile(const QModelIndex& index); - void saveIniTweaks(); - void saveCategories(QTreeWidgetItem* currentNode); - void saveCurrentTextFile(); - void saveCurrentIniFile(); - void openTextFile(const QString& fileName); - void openIniFile(const QString& fileName); - bool allowNavigateFromTXT(); - bool allowNavigateFromINI(); - bool hideFile(const QString& oldName); - bool unhideFile(const QString& oldName); - void addCheckedCategories(QTreeWidgetItem* tree); - void refreshPrimaryCategoriesBox(); - - int tabIndex(const QString& tabId); -private slots: - - void hideConflictFile(); - void unhideConflictFile(); - - void thumbnailClicked(const QString& fileName); - void linkClicked(const QUrl& url); + void initFiletree(ModInfo::Ptr modInfo); + void initINITweaks(); - void deleteTriggered(); - void renameTriggered(); - void openTriggered(); - void createDirectoryTriggered(); - void hideTriggered(); - void unhideTriggered(); + void refreshLists(); - void on_closeButton_clicked(); - void on_saveButton_clicked(); - void on_activateESP_clicked(); - void on_deactivateESP_clicked(); - void on_saveTXTButton_clicked(); - void on_visitNexusLabel_linkActivated(const QString& link); - void on_modIDEdit_editingFinished(); - void on_versionEdit_editingFinished(); - void on_iniFileView_textChanged(); - void on_textFileView_textChanged(); - void on_tabWidget_currentChanged(int index); - void on_primaryCategoryBox_currentIndexChanged(int index); - void on_categoriesTree_itemChanged(QTreeWidgetItem* item, int column); - void on_textFileList_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous); - void on_iniFileList_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous); - void on_iniTweaksList_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous); - void on_overwriteTree_itemDoubleClicked(QTreeWidgetItem* item, int column); - void on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem* item, int column); - void on_overwriteTree_customContextMenuRequested(const QPoint& pos); - void on_fileTree_customContextMenuRequested(const QPoint& pos); + void addCategories(const CategoryFactory &factory, const std::set &enabledCategories, QTreeWidgetItem *root, int rootLevel); - void on_refreshButton_clicked(); + void updateVersionColor(); - void on_endorseBtn_clicked(); + void refreshNexusData(int modID); + void activateNexusTab(); + QString getFileCategory(int categoryID); + bool recursiveDelete(const QModelIndex &index); + void deleteFile(const QModelIndex &index); + void openFile(const QModelIndex &index); + void saveIniTweaks(); + void saveCategories(QTreeWidgetItem *currentNode); + void saveCurrentTextFile(); + void saveCurrentIniFile(); + void openTextFile(const QString &fileName); + void openIniFile(const QString &fileName); + bool allowNavigateFromTXT(); + bool allowNavigateFromINI(); + bool hideFile(const QString &oldName); + bool unhideFile(const QString &oldName); + void addCheckedCategories(QTreeWidgetItem *tree); + void refreshPrimaryCategoriesBox(); - void on_nextButton_clicked(); + int tabIndex(const QString &tabId); - void on_prevButton_clicked(); +private slots: - void on_iniTweaksList_customContextMenuRequested(const QPoint& pos); + void hideConflictFile(); + void unhideConflictFile(); + + void thumbnailClicked(const QString &fileName); + void linkClicked(const QUrl &url); + + void deleteTriggered(); + void renameTriggered(); + void openTriggered(); + void createDirectoryTriggered(); + void hideTriggered(); + void unhideTriggered(); + + void on_closeButton_clicked(); + void on_saveButton_clicked(); + void on_activateESP_clicked(); + void on_deactivateESP_clicked(); + void on_saveTXTButton_clicked(); + void on_visitNexusLabel_linkActivated(const QString &link); + void on_modIDEdit_editingFinished(); + void on_versionEdit_editingFinished(); + void on_iniFileView_textChanged(); + void on_textFileView_textChanged(); + void on_tabWidget_currentChanged(int index); + void on_primaryCategoryBox_currentIndexChanged(int index); + void on_categoriesTree_itemChanged(QTreeWidgetItem *item, int column); + void on_textFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + void on_iniFileList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + void on_iniTweaksList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); + void on_overwriteTree_itemDoubleClicked(QTreeWidgetItem *item, int column); + void on_overwrittenTree_itemDoubleClicked(QTreeWidgetItem *item, int column); + void on_overwriteTree_customContextMenuRequested(const QPoint &pos); + void on_fileTree_customContextMenuRequested(const QPoint &pos); + + void on_refreshButton_clicked(); + + void on_endorseBtn_clicked(); + + void on_nextButton_clicked(); + + void on_prevButton_clicked(); + + void on_iniTweaksList_customContextMenuRequested(const QPoint &pos); + + void createTweak(); +private: - void createTweak(); + Ui::ModInfoDialog *ui; -private: - Ui::ModInfoDialog* ui; + ModInfo::Ptr m_ModInfo; + int m_OriginID; - ModInfo::Ptr m_ModInfo; - int m_OriginID; + QSignalMapper m_ThumbnailMapper; + QString m_RootPath; - QSignalMapper m_ThumbnailMapper; - QString m_RootPath; + QFileSystemModel *m_FileSystemModel; + QTreeView *m_FileTree; + QModelIndexList m_FileSelection; - QFileSystemModel* m_FileSystemModel; - QTreeView* m_FileTree; - QModelIndexList m_FileSelection; + QSettings *m_Settings; - QSettings* m_Settings; + std::set m_RequestIDs; + bool m_RequestStarted; - std::set m_RequestIDs; - bool m_RequestStarted; + QAction *m_DeleteAction; + QAction *m_RenameAction; + QAction *m_OpenAction; + QAction *m_NewFolderAction; + QAction *m_HideAction; + QAction *m_UnhideAction; - QAction* m_DeleteAction; - QAction* m_RenameAction; - QAction* m_OpenAction; - QAction* m_NewFolderAction; - QAction* m_HideAction; - QAction* m_UnhideAction; + QTreeWidgetItem *m_ConflictsContextItem; - QTreeWidgetItem* m_ConflictsContextItem; + const MOShared::DirectoryEntry *m_Directory; + MOShared::FilesOrigin *m_Origin; - const MOShared::DirectoryEntry* m_Directory; - MOShared::FilesOrigin* m_Origin; + std::map m_RealTabPos; - std::map m_RealTabPos; }; #endif // MODINFODIALOG_H diff --git a/src/modinfoforeign.cpp b/src/modinfoforeign.cpp index 187ae576..0bde2c30 100644 --- a/src/modinfoforeign.cpp +++ b/src/modinfoforeign.cpp @@ -8,47 +8,61 @@ using namespace MOBase; using namespace MOShared; -QString ModInfoForeign::name() const { return m_Name; } +QString ModInfoForeign::name() const +{ + return m_Name; +} -QDateTime ModInfoForeign::creationTime() const { return m_CreationTime; } +QDateTime ModInfoForeign::creationTime() const +{ + return m_CreationTime; +} -QString ModInfoForeign::absolutePath() const { - // I ought to store this, it's used elsewhere - IPluginGame const* game = qApp->property("managed_game").value(); - return game->dataDirectory().absolutePath(); +QString ModInfoForeign::absolutePath() const +{ + //I ought to store this, it's used elsewhere + IPluginGame const *game = qApp->property("managed_game").value(); + return game->dataDirectory().absolutePath(); } -std::vector ModInfoForeign::getFlags() const { - std::vector result = ModInfoWithConflictInfo::getFlags(); - result.push_back(FLAG_FOREIGN); +std::vector ModInfoForeign::getFlags() const +{ + std::vector result = ModInfoWithConflictInfo::getFlags(); + result.push_back(FLAG_FOREIGN); - if (m_PluginSelected) { - result.push_back(ModInfo::FLAG_PLUGIN_SELECTED); - } + if (m_PluginSelected) { + result.push_back(ModInfo::FLAG_PLUGIN_SELECTED); + } - return result; + return result; } -int ModInfoForeign::getHighlight() const { - return m_PluginSelected ? ModInfo::HIGHLIGHT_PLUGIN : ModInfo::HIGHLIGHT_NONE; +int ModInfoForeign::getHighlight() const +{ + return m_PluginSelected ? ModInfo::HIGHLIGHT_PLUGIN : ModInfo::HIGHLIGHT_NONE; } -QString ModInfoForeign::getDescription() const { - return tr("This pseudo mod represents content managed outside MO. It isn't modified by MO."); +QString ModInfoForeign::getDescription() const +{ + 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, DirectoryEntry** directoryStructure) - : ModInfoWithConflictInfo(directoryStructure), m_ReferenceFile(referenceFile), m_Archives(archives) { - m_CreationTime = QFileInfo(referenceFile).created(); - switch (modType) { - case ModInfo::EModType::MOD_DLC: - m_Name = "DLC: " + modName; - break; - case ModInfo::EModType::MOD_CC: - m_Name = "Creation Club: " + modName; - break; - default: - m_Name = "Unmanaged: " + modName; - } +ModInfoForeign::ModInfoForeign(const QString &modName, + const QString &referenceFile, + const QStringList &archives, + ModInfo::EModType modType, + DirectoryEntry **directoryStructure) + : ModInfoWithConflictInfo(directoryStructure), + m_ReferenceFile(referenceFile), m_Archives(archives) { + m_CreationTime = QFileInfo(referenceFile).created(); + switch (modType) { + case ModInfo::EModType::MOD_DLC: + m_Name = "DLC: " + modName; + break; + case ModInfo::EModType::MOD_CC: + m_Name = "Creation Club: " + modName; + break; + default: + m_Name = "Unmanaged: " + modName; + } } diff --git a/src/modinfoforeign.h b/src/modinfoforeign.h index c7d50365..d60064f0 100644 --- a/src/modinfoforeign.h +++ b/src/modinfoforeign.h @@ -3,61 +3,65 @@ #include "modinfowithconflictinfo.h" -class ModInfoForeign : public ModInfoWithConflictInfo { +class ModInfoForeign : public ModInfoWithConflictInfo +{ - Q_OBJECT + Q_OBJECT - friend class ModInfo; + friend class ModInfo; public: - virtual bool updateAvailable() const { return false; } - virtual bool updateIgnored() const { return false; } - virtual bool downgradeAvailable() const { return false; } - virtual bool updateNXMInfo() { return false; } - virtual void setCategory(int, bool) {} - virtual bool setName(const QString&) { return false; } - virtual void setNotes(const QString&) {} - virtual void setNexusID(int) {} - virtual void setNewestVersion(const MOBase::VersionInfo&) {} - virtual void ignoreUpdate(bool) {} - virtual void setNexusDescription(const QString&) {} - virtual void setInstallationFile(const QString&) {} - virtual void addNexusCategory(int) {} - virtual void setIsEndorsed(bool) {} - virtual void setNeverEndorse() {} - virtual bool remove() { return false; } - virtual void endorse(bool) {} - virtual bool isEmpty() const { return false; } - virtual QString name() const; - virtual QString internalName() const { return name(); } - virtual QString notes() const { return ""; } - virtual QDateTime creationTime() const; - virtual QString absolutePath() const; - virtual MOBase::VersionInfo getNewestVersion() const { return QString(); } - virtual QString getInstallationFile() const { return ""; } - virtual int getNexusID() const { return -1; } - virtual std::vector getIniTweaks() const { return std::vector(); } - virtual std::vector getFlags() const; - virtual int getHighlight() const; - virtual QString getDescription() const; - virtual QDateTime getLastNexusQuery() const { return QDateTime(); } - virtual QString getNexusDescription() const { return QString(); } - virtual int getFixedPriority() const { return INT_MIN; } - virtual QStringList archives() const { return m_Archives; } - virtual QStringList stealFiles() const { return m_Archives + QStringList(m_ReferenceFile); } - virtual bool alwaysEnabled() const { return true; } - virtual void addInstalledFile(int, int) {} -protected: - ModInfoForeign(const QString& modName, const QString& referenceFile, const QStringList& archives, - ModInfo::EModType modType, MOShared::DirectoryEntry** directoryStructure); + virtual bool updateAvailable() const { return false; } + virtual bool updateIgnored() const { return false; } + virtual bool downgradeAvailable() const { return false; } + virtual bool updateNXMInfo() { return false; } + virtual void setCategory(int, bool) {} + virtual bool setName(const QString&) { return false; } + virtual void setNotes(const QString&) {} + virtual void setNexusID(int) {} + virtual void setNewestVersion(const MOBase::VersionInfo&) {} + virtual void ignoreUpdate(bool) {} + virtual void setNexusDescription(const QString&) {} + virtual void setInstallationFile(const QString&) {} + virtual void addNexusCategory(int) {} + virtual void setIsEndorsed(bool) {} + virtual void setNeverEndorse() {} + virtual bool remove() { return false; } + virtual void endorse(bool) {} + virtual bool isEmpty() const { return false; } + virtual QString name() const; + virtual QString internalName() const { return name(); } + virtual QString notes() const { return ""; } + virtual QDateTime creationTime() const; + virtual QString absolutePath() const; + virtual MOBase::VersionInfo getNewestVersion() const { return QString(); } + virtual QString getInstallationFile() const { return ""; } + virtual int getNexusID() const { return -1; } + virtual std::vector getIniTweaks() const { return std::vector(); } + virtual std::vector getFlags() const; + virtual int getHighlight() const; + virtual QString getDescription() const; + virtual QDateTime getLastNexusQuery() const { return QDateTime(); } + virtual QString getNexusDescription() const { return QString(); } + virtual int getFixedPriority() const { return INT_MIN; } + virtual QStringList archives() const { return m_Archives; } + virtual QStringList stealFiles() const { return m_Archives + QStringList(m_ReferenceFile); } + virtual bool alwaysEnabled() const { return true; } + virtual void addInstalledFile(int, int) {} +protected: + ModInfoForeign(const QString &modName, const QString &referenceFile, + const QStringList &archives, ModInfo::EModType modType, + MOShared::DirectoryEntry **directoryStructure); private: - QString m_Name; - QString m_ReferenceFile; - QStringList m_Archives; - QDateTime m_CreationTime; - int m_Priority; + + QString m_Name; + QString m_ReferenceFile; + QStringList m_Archives; + QDateTime m_CreationTime; + int m_Priority; + }; #endif // MODINFOFOREIGN_H diff --git a/src/modinfooverwrite.cpp b/src/modinfooverwrite.cpp index c02211be..360212c0 100644 --- a/src/modinfooverwrite.cpp +++ b/src/modinfooverwrite.cpp @@ -6,46 +6,55 @@ #include #include -ModInfoOverwrite::ModInfoOverwrite() { testValid(); } - -bool ModInfoOverwrite::isEmpty() const { - QDirIterator iter(absolutePath(), QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - if (!iter.hasNext()) - return true; - iter.next(); - if ((iter.fileName() == "meta.ini") && !iter.hasNext()) - return true; - return false; +ModInfoOverwrite::ModInfoOverwrite() +{ + testValid(); } -QString ModInfoOverwrite::absolutePath() const { return Settings::instance().getOverwriteDirectory(); } +bool ModInfoOverwrite::isEmpty() const +{ + QDirIterator iter(absolutePath(), QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + if (!iter.hasNext()) return true; + iter.next(); + if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; + return false; +} + +QString ModInfoOverwrite::absolutePath() const +{ + return Settings::instance().getOverwriteDirectory(); +} -std::vector ModInfoOverwrite::getFlags() const { - std::vector result; - result.push_back(FLAG_OVERWRITE); - if (m_PluginSelected) - result.push_back(FLAG_PLUGIN_SELECTED); - return result; +std::vector ModInfoOverwrite::getFlags() const +{ + std::vector result; + result.push_back(FLAG_OVERWRITE); + if (m_PluginSelected) + result.push_back(FLAG_PLUGIN_SELECTED); + return result; } -int ModInfoOverwrite::getHighlight() const { - 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()) - highlight |= HIGHLIGHT_PLUGIN; - return highlight; +int ModInfoOverwrite::getHighlight() const +{ + 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()) + highlight |= HIGHLIGHT_PLUGIN; + return highlight; } -QString ModInfoOverwrite::getDescription() const { - return tr("This pseudo mod contains files from the virtual data tree that got " - "modified (i.e. by the construction kit)"); +QString ModInfoOverwrite::getDescription() const +{ + return tr("This pseudo mod contains files from the virtual data tree that got " + "modified (i.e. by the construction kit)"); } -QStringList ModInfoOverwrite::archives() const { - QStringList result; - QDir dir(this->absolutePath()); - for (const QString& archive : dir.entryList(QStringList({"*.bsa", "*.ba2"}))) { - result.append(this->absolutePath() + "/" + archive); - } - return result; +QStringList ModInfoOverwrite::archives() const +{ + QStringList result; + QDir dir(this->absolutePath()); + 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 276e5b6e..b6cdfb43 100644 --- a/src/modinfooverwrite.h +++ b/src/modinfooverwrite.h @@ -5,51 +5,55 @@ #include -class ModInfoOverwrite : public ModInfo { +class ModInfoOverwrite : public ModInfo +{ - Q_OBJECT + Q_OBJECT - friend class ModInfo; + friend class ModInfo; public: - virtual bool updateAvailable() const { return false; } - virtual bool updateIgnored() const { return false; } - virtual bool downgradeAvailable() const { return false; } - virtual bool updateNXMInfo() { return false; } - virtual void setCategory(int, bool) {} - virtual bool setName(const QString&) { return false; } - virtual void setNotes(const QString&) {} - virtual void setNexusID(int) {} - virtual void setNewestVersion(const MOBase::VersionInfo&) {} - virtual void ignoreUpdate(bool) {} - virtual void setNexusDescription(const QString&) {} - virtual void setInstallationFile(const QString&) {} - virtual void addNexusCategory(int) {} - virtual void setIsEndorsed(bool) {} - virtual void setNeverEndorse() {} - virtual bool remove() { return false; } - virtual void endorse(bool) {} - virtual bool alwaysEnabled() const { return true; } - virtual bool isEmpty() const; - virtual QString name() const { return "Overwrite"; } - virtual QString notes() const { return ""; } - virtual QDateTime creationTime() const { return QDateTime(); } - virtual QString absolutePath() const; - virtual MOBase::VersionInfo getNewestVersion() const { return QString(); } - virtual QString getInstallationFile() const { return ""; } - virtual int getFixedPriority() const { return INT_MAX; } - virtual int getNexusID() const { return -1; } - virtual std::vector getIniTweaks() const { return std::vector(); } - virtual std::vector getFlags() const; - virtual int getHighlight() const; - virtual QString getDescription() const; - virtual QDateTime getLastNexusQuery() const { return QDateTime(); } - virtual QString getNexusDescription() const { return QString(); } - virtual QStringList archives() const; - virtual void addInstalledFile(int, int) {} + + virtual bool updateAvailable() const { return false; } + virtual bool updateIgnored() const { return false; } + virtual bool downgradeAvailable() const { return false; } + virtual bool updateNXMInfo() { return false; } + virtual void setCategory(int, bool) {} + virtual bool setName(const QString&) { return false; } + virtual void setNotes(const QString&) {} + virtual void setNexusID(int) {} + virtual void setNewestVersion(const MOBase::VersionInfo&) {} + virtual void ignoreUpdate(bool) {} + virtual void setNexusDescription(const QString&) {} + virtual void setInstallationFile(const QString&) {} + virtual void addNexusCategory(int) {} + virtual void setIsEndorsed(bool) {} + virtual void setNeverEndorse() {} + virtual bool remove() { return false; } + virtual void endorse(bool) {} + virtual bool alwaysEnabled() const { return true; } + virtual bool isEmpty() const; + virtual QString name() const { return "Overwrite"; } + virtual QString notes() const { return ""; } + virtual QDateTime creationTime() const { return QDateTime(); } + virtual QString absolutePath() const; + virtual MOBase::VersionInfo getNewestVersion() const { return QString(); } + virtual QString getInstallationFile() const { return ""; } + virtual int getFixedPriority() const { return INT_MAX; } + virtual int getNexusID() const { return -1; } + virtual std::vector getIniTweaks() const { return std::vector(); } + virtual std::vector getFlags() const; + virtual int getHighlight() const; + virtual QString getDescription() const; + virtual QDateTime getLastNexusQuery() const { return QDateTime(); } + virtual QString getNexusDescription() const { return QString(); } + virtual QStringList archives() const; + virtual void addInstalledFile(int, int) {} private: - ModInfoOverwrite(); + + ModInfoOverwrite(); + }; #endif // MODINFOOVERWRITE_H diff --git a/src/modinforegular.cpp b/src/modinforegular.cpp index b3b0c2a6..5486bb2c 100644 --- a/src/modinforegular.cpp +++ b/src/modinforegular.cpp @@ -16,500 +16,572 @@ 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) { + //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, DirectoryEntry** directoryStructure) - : ModInfoWithConflictInfo(directoryStructure), m_Name(path.dirName()), m_Path(path.absolutePath()), m_Repository(), - m_MetaInfoChanged(false), m_EndorsedState(ENDORSED_UNKNOWN), m_NexusBridge() { - testValid(); - m_CreationTime = QFileInfo(path.absolutePath()).created(); - // read out the meta-file for information - readMeta(); - - connect(&m_NexusBridge, SIGNAL(descriptionAvailable(int, QVariant, QVariant)), this, - SLOT(nxmDescriptionAvailable(int, QVariant, QVariant))); - connect(&m_NexusBridge, SIGNAL(endorsementToggled(int, QVariant, QVariant)), this, - SLOT(nxmEndorsementToggled(int, QVariant, QVariant))); - connect(&m_NexusBridge, SIGNAL(requestFailed(int, int, QVariant, QString)), this, - SLOT(nxmRequestFailed(int, int, QVariant, QString))); -} - -ModInfoRegular::~ModInfoRegular() { - try { - saveMeta(); - } catch (const std::exception& e) { - qCritical("failed to save meta information for \"%s\": %s", m_Name.toUtf8().constData(), e.what()); + } +} + +ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStructure) + : ModInfoWithConflictInfo(directoryStructure) + , m_Name(path.dirName()) + , m_Path(path.absolutePath()) + , m_Repository() + , m_MetaInfoChanged(false) + , m_EndorsedState(ENDORSED_UNKNOWN) + , m_NexusBridge() +{ + testValid(); + m_CreationTime = QFileInfo(path.absolutePath()).created(); + // read out the meta-file for information + readMeta(); + + connect(&m_NexusBridge, SIGNAL(descriptionAvailable(int,QVariant,QVariant)) + , this, SLOT(nxmDescriptionAvailable(int,QVariant,QVariant))); + connect(&m_NexusBridge, SIGNAL(endorsementToggled(int,QVariant,QVariant)) + , this, SLOT(nxmEndorsementToggled(int,QVariant,QVariant))); + connect(&m_NexusBridge, SIGNAL(requestFailed(int,int,QVariant,QString)) + , this, SLOT(nxmRequestFailed(int,int,QVariant,QString))); +} + + +ModInfoRegular::~ModInfoRegular() +{ + try { + saveMeta(); + } catch (const std::exception &e) { + qCritical("failed to save meta information for \"%s\": %s", + m_Name.toUtf8().constData(), e.what()); + } +} + +bool ModInfoRegular::isEmpty() const +{ + QDirIterator iter(m_Path, QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); + if (!iter.hasNext()) return true; + iter.next(); + if ((iter.fileName() == "meta.ini") && !iter.hasNext()) return true; + return false; +} + + +void ModInfoRegular::readMeta() +{ + QSettings metaFile(m_Path + "/meta.ini", QSettings::IniFormat); + m_Notes = metaFile.value("notes", "").toString(); + m_NexusID = metaFile.value("modid", -1).toInt(); + m_Version.parse(metaFile.value("version", "").toString()); + m_NewestVersion = metaFile.value("newestVersion", "").toString(); + m_IgnoredVersion = metaFile.value("ignoredVersion", "").toString(); + m_InstallationFile = metaFile.value("installationFile", "").toString(); + m_NexusDescription = metaFile.value("nexusDescription", "").toString(); + m_Repository = metaFile.value("repository", "Nexus").toString(); + m_URL = metaFile.value("url", "").toString(); + m_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); + if (metaFile.contains("endorsed")) { + if (metaFile.value("endorsed").canConvert()) { + switch (metaFile.value("endorsed").toInt()) { + case ENDORSED_FALSE: m_EndorsedState = ENDORSED_FALSE; break; + case ENDORSED_TRUE: m_EndorsedState = ENDORSED_TRUE; break; + case ENDORSED_NEVER: m_EndorsedState = ENDORSED_NEVER; break; + default: m_EndorsedState = ENDORSED_UNKNOWN; break; + } + } else { + m_EndorsedState = metaFile.value("endorsed", false).toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; + } + } + + QString categoriesString = metaFile.value("category", "").toString(); + + QStringList categories = categoriesString.split(',', QString::SkipEmptyParts); + 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))) { + m_Categories.insert(categoryID); + if (iter == categories.begin()) { + m_PrimaryCategory = categoryID; + } } + } + + 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())); + } + metaFile.endArray(); + + m_MetaInfoChanged = false; +} + +void ModInfoRegular::saveMeta() +{ + // only write meta data if the mod directory exists + if (m_MetaInfoChanged && QFile::exists(absolutePath())) { + QSettings metaFile(absolutePath().append("/meta.ini"), QSettings::IniFormat); + if (metaFile.status() == QSettings::NoError) { + std::set temp = m_Categories; + temp.erase(m_PrimaryCategory); + metaFile.setValue("category", QString("%1").arg(m_PrimaryCategory) + "," + SetJoin(temp, ",")); + metaFile.setValue("newestVersion", m_NewestVersion.canonicalString()); + metaFile.setValue("ignoredVersion", m_IgnoredVersion.canonicalString()); + metaFile.setValue("version", m_Version.canonicalString()); + metaFile.setValue("installationFile", m_InstallationFile); + metaFile.setValue("repository", m_Repository); + metaFile.setValue("modid", m_NexusID); + metaFile.setValue("notes", m_Notes); + metaFile.setValue("nexusDescription", m_NexusDescription); + metaFile.setValue("url", m_URL); + metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); + if (m_EndorsedState != ENDORSED_UNKNOWN) { + metaFile.setValue("endorsed", m_EndorsedState); + } + + metaFile.beginWriteArray("installedFiles"); + int idx = 0; + for (auto iter = m_InstalledFileIDs.begin(); iter != m_InstalledFileIDs.end(); ++iter) { + metaFile.setArrayIndex(idx++); + metaFile.setValue("modid", iter->first); + metaFile.setValue("fileid", iter->second); + } + metaFile.endArray(); + + metaFile.sync(); // sync needs to be called to ensure the file is created + + if (metaFile.status() == QSettings::NoError) { + m_MetaInfoChanged = false; + } else { + reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); + } + } else { + reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); + } + } } -bool ModInfoRegular::isEmpty() const { - QDirIterator iter(m_Path, QDir::NoDotAndDotDot | QDir::Files | QDir::Dirs); - if (!iter.hasNext()) - return true; - iter.next(); - if ((iter.fileName() == "meta.ini") && !iter.hasNext()) - return true; + +bool ModInfoRegular::updateAvailable() const +{ + if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { return false; + } + return m_NewestVersion.isValid() && (m_Version < m_NewestVersion); } -void ModInfoRegular::readMeta() { - QSettings metaFile(m_Path + "/meta.ini", QSettings::IniFormat); - m_Notes = metaFile.value("notes", "").toString(); - m_NexusID = metaFile.value("modid", -1).toInt(); - m_Version.parse(metaFile.value("version", "").toString()); - m_NewestVersion = metaFile.value("newestVersion", "").toString(); - m_IgnoredVersion = metaFile.value("ignoredVersion", "").toString(); - m_InstallationFile = metaFile.value("installationFile", "").toString(); - m_NexusDescription = metaFile.value("nexusDescription", "").toString(); - m_Repository = metaFile.value("repository", "Nexus").toString(); - m_URL = metaFile.value("url", "").toString(); - m_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); - if (metaFile.contains("endorsed")) { - if (metaFile.value("endorsed").canConvert()) { - switch (metaFile.value("endorsed").toInt()) { - case ENDORSED_FALSE: - m_EndorsedState = ENDORSED_FALSE; - break; - case ENDORSED_TRUE: - m_EndorsedState = ENDORSED_TRUE; - break; - case ENDORSED_NEVER: - m_EndorsedState = ENDORSED_NEVER; - break; - default: - m_EndorsedState = ENDORSED_UNKNOWN; - break; - } - } else { - m_EndorsedState = metaFile.value("endorsed", false).toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; - } - } - - QString categoriesString = metaFile.value("category", "").toString(); - - QStringList categories = categoriesString.split(',', QString::SkipEmptyParts); - 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))) { - m_Categories.insert(categoryID); - if (iter == categories.begin()) { - m_PrimaryCategory = categoryID; - } - } - } - 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())); - } - metaFile.endArray(); - - m_MetaInfoChanged = false; -} - -void ModInfoRegular::saveMeta() { - // only write meta data if the mod directory exists - if (m_MetaInfoChanged && QFile::exists(absolutePath())) { - QSettings metaFile(absolutePath().append("/meta.ini"), QSettings::IniFormat); - if (metaFile.status() == QSettings::NoError) { - std::set temp = m_Categories; - temp.erase(m_PrimaryCategory); - metaFile.setValue("category", QString("%1").arg(m_PrimaryCategory) + "," + SetJoin(temp, ",")); - metaFile.setValue("newestVersion", m_NewestVersion.canonicalString()); - metaFile.setValue("ignoredVersion", m_IgnoredVersion.canonicalString()); - metaFile.setValue("version", m_Version.canonicalString()); - metaFile.setValue("installationFile", m_InstallationFile); - metaFile.setValue("repository", m_Repository); - metaFile.setValue("modid", m_NexusID); - metaFile.setValue("notes", m_Notes); - metaFile.setValue("nexusDescription", m_NexusDescription); - metaFile.setValue("url", m_URL); - metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); - if (m_EndorsedState != ENDORSED_UNKNOWN) { - metaFile.setValue("endorsed", m_EndorsedState); - } - - metaFile.beginWriteArray("installedFiles"); - int idx = 0; - for (auto iter = m_InstalledFileIDs.begin(); iter != m_InstalledFileIDs.end(); ++iter) { - metaFile.setArrayIndex(idx++); - metaFile.setValue("modid", iter->first); - metaFile.setValue("fileid", iter->second); - } - metaFile.endArray(); - - metaFile.sync(); // sync needs to be called to ensure the file is created - - if (metaFile.status() == QSettings::NoError) { - m_MetaInfoChanged = false; - } else { - reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); - } - } else { - reportError(tr("failed to write %1/meta.ini: error %2").arg(absolutePath()).arg(metaFile.status())); - } - } +bool ModInfoRegular::downgradeAvailable() const +{ + if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { + return false; + } + return m_NewestVersion.isValid() && (m_NewestVersion < m_Version); } -bool ModInfoRegular::updateAvailable() const { - if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { - return false; - } - return m_NewestVersion.isValid() && (m_Version < m_NewestVersion); + +void ModInfoRegular::nxmDescriptionAvailable(int, QVariant, QVariant resultData) +{ + QVariantMap result = resultData.toMap(); + setNewestVersion(VersionInfo(result["version"].toString())); + setNexusDescription(result["description"].toString()); + + if ((m_EndorsedState != ENDORSED_NEVER) && (result.contains("voted_by_user"))) { + setEndorsedState(result["voted_by_user"].toBool() ? ENDORSED_TRUE : ENDORSED_FALSE); + } + m_LastNexusQuery = QDateTime::currentDateTime(); + //m_MetaInfoChanged = true; + saveMeta(); + emit modDetailsUpdated(true); } -bool ModInfoRegular::downgradeAvailable() const { - if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { - return false; - } - return m_NewestVersion.isValid() && (m_NewestVersion < m_Version); + +void ModInfoRegular::nxmEndorsementToggled(int, QVariant, QVariant resultData) +{ + m_EndorsedState = resultData.toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; + m_MetaInfoChanged = true; + saveMeta(); + emit modDetailsUpdated(true); } -void ModInfoRegular::nxmDescriptionAvailable(int, QVariant, QVariant resultData) { - QVariantMap result = resultData.toMap(); - setNewestVersion(VersionInfo(result["version"].toString())); - setNexusDescription(result["description"].toString()); - if ((m_EndorsedState != ENDORSED_NEVER) && (result.contains("voted_by_user"))) { - setEndorsedState(result["voted_by_user"].toBool() ? ENDORSED_TRUE : ENDORSED_FALSE); - } - m_LastNexusQuery = QDateTime::currentDateTime(); - // m_MetaInfoChanged = true; - saveMeta(); - emit modDetailsUpdated(true); +void ModInfoRegular::nxmRequestFailed(int, int, QVariant userData, 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."; + } + if (QApplication::activeWindow() != nullptr) { + MessageDialog::showMessage(fullMessage, QApplication::activeWindow()); + } + emit modDetailsUpdated(false); } -void ModInfoRegular::nxmEndorsementToggled(int, QVariant, QVariant resultData) { - m_EndorsedState = resultData.toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; - m_MetaInfoChanged = true; - saveMeta(); - emit modDetailsUpdated(true); + +bool ModInfoRegular::updateNXMInfo() +{ + if (m_NexusID > 0) { + m_NexusBridge.requestDescription(m_NexusID, QVariant()); + return true; + } + return false; } -void ModInfoRegular::nxmRequestFailed(int, int, QVariant userData, 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."; + +void ModInfoRegular::setCategory(int categoryID, bool active) +{ + m_MetaInfoChanged = true; + + if (active) { + m_Categories.insert(categoryID); + if (m_PrimaryCategory == -1) { + m_PrimaryCategory = categoryID; + } + } else { + std::set::iterator iter = m_Categories.find(categoryID); + if (iter != m_Categories.end()) { + m_Categories.erase(iter); } - if (QApplication::activeWindow() != nullptr) { - MessageDialog::showMessage(fullMessage, QApplication::activeWindow()); + if (categoryID == m_PrimaryCategory) { + if (m_Categories.size() == 0) { + m_PrimaryCategory = -1; + } else { + m_PrimaryCategory = *(m_Categories.begin()); + } } - emit modDetailsUpdated(false); + } } -bool ModInfoRegular::updateNXMInfo() { - if (m_NexusID > 0) { - m_NexusBridge.requestDescription(m_NexusID, QVariant()); - return true; - } + +bool ModInfoRegular::setName(const QString &name) +{ + if (name.contains('/') || name.contains('\\')) { return false; -} + } -void ModInfoRegular::setCategory(int categoryID, bool active) { - m_MetaInfoChanged = true; + 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 (active) { - m_Categories.insert(categoryID); - if (m_PrimaryCategory == -1) { - m_PrimaryCategory = categoryID; - } - } else { - std::set::iterator iter = m_Categories.find(categoryID); - if (iter != m_Categories.end()) { - m_Categories.erase(iter); - } - if (categoryID == m_PrimaryCategory) { - if (m_Categories.size() == 0) { - m_PrimaryCategory = -1; - } else { - m_PrimaryCategory = *(m_Categories.begin()); - } - } + if (m_Name.compare(name, Qt::CaseInsensitive) == 0) { + QString tempName = name; + tempName.append("_temp"); + while (modDir.exists(tempName)) { + tempName.append("_"); } -} - -bool ModInfoRegular::setName(const QString& name) { - if (name.contains('/') || name.contains('\\')) { - return false; + if (!modDir.rename(m_Name, tempName)) { + return false; } - - 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) { - QString tempName = name; - tempName.append("_temp"); - while (modDir.exists(tempName)) { - tempName.append("_"); - } - if (!modDir.rename(m_Name, tempName)) { - return false; - } - if (!modDir.rename(tempName, name)) { - qCritical("rename to final name failed after successful rename to intermediate name"); - modDir.rename(tempName, m_Name); - return false; - } - } else { - if (!shellRename(modDir.absoluteFilePath(m_Name), modDir.absoluteFilePath(name))) { - qCritical("failed to rename mod %s (errorcode %d)", qPrintable(name), ::GetLastError()); - return false; - } + if (!modDir.rename(tempName, name)) { + qCritical("rename to final name failed after successful rename to intermediate name"); + modDir.rename(tempName, m_Name); + return false; } + } else { + if (!shellRename(modDir.absoluteFilePath(m_Name), modDir.absoluteFilePath(name))) { + qCritical("failed to rename mod %s (errorcode %d)", + qPrintable(name), ::GetLastError()); + return false; + } + } - std::map::iterator nameIter = s_ModsByName.find(m_Name); - if (nameIter != s_ModsByName.end()) { - unsigned int index = nameIter->second; - s_ModsByName.erase(nameIter); + std::map::iterator nameIter = s_ModsByName.find(m_Name); + if (nameIter != s_ModsByName.end()) { + unsigned int index = nameIter->second; + s_ModsByName.erase(nameIter); - m_Name = name; - m_Path = newPath; + m_Name = name; + m_Path = newPath; - s_ModsByName[m_Name] = index; + s_ModsByName[m_Name] = index; - std::sort(s_Collection.begin(), s_Collection.end(), ByName); - updateIndices(); - } else { // otherwise mod isn't registered yet? - m_Name = name; - m_Path = newPath; - } + std::sort(s_Collection.begin(), s_Collection.end(), ByName); + updateIndices(); + } else { // otherwise mod isn't registered yet? + m_Name = name; + m_Path = newPath; + } - return true; + return true; } -void ModInfoRegular::setNotes(const QString& notes) { - m_Notes = notes; - m_MetaInfoChanged = true; +void ModInfoRegular::setNotes(const QString ¬es) +{ + m_Notes = notes; + m_MetaInfoChanged = true; +} + +void ModInfoRegular::setNexusID(int modID) +{ + m_NexusID = modID; + m_MetaInfoChanged = true; +} + +void ModInfoRegular::setVersion(const VersionInfo &version) +{ + m_Version = version; + m_MetaInfoChanged = true; } -void ModInfoRegular::setNexusID(int modID) { - m_NexusID = modID; +void ModInfoRegular::setNewestVersion(const VersionInfo &version) +{ + if (version != m_NewestVersion) { + m_NewestVersion = version; m_MetaInfoChanged = true; + } } -void ModInfoRegular::setVersion(const VersionInfo& version) { - m_Version = version; +void ModInfoRegular::setNexusDescription(const QString &description) +{ + if (qHash(description) != qHash(m_NexusDescription)) { + m_NexusDescription = description; m_MetaInfoChanged = true; + } } -void ModInfoRegular::setNewestVersion(const VersionInfo& version) { - if (version != m_NewestVersion) { - m_NewestVersion = version; - m_MetaInfoChanged = true; - } +void ModInfoRegular::setEndorsedState(EEndorsedState endorsedState) +{ + if (endorsedState != m_EndorsedState) { + m_EndorsedState = endorsedState; + m_MetaInfoChanged = true; + } } -void ModInfoRegular::setNexusDescription(const QString& description) { - if (qHash(description) != qHash(m_NexusDescription)) { - m_NexusDescription = description; - m_MetaInfoChanged = true; - } +void ModInfoRegular::setInstallationFile(const QString &fileName) +{ + m_InstallationFile = fileName; + m_MetaInfoChanged = true; } -void ModInfoRegular::setEndorsedState(EEndorsedState endorsedState) { - if (endorsedState != m_EndorsedState) { - m_EndorsedState = endorsedState; - m_MetaInfoChanged = true; - } +void ModInfoRegular::addNexusCategory(int categoryID) +{ + m_Categories.insert(CategoryFactory::instance().resolveNexusID(categoryID)); } -void ModInfoRegular::setInstallationFile(const QString& fileName) { - m_InstallationFile = fileName; +void ModInfoRegular::setIsEndorsed(bool endorsed) +{ + if (m_EndorsedState != ENDORSED_NEVER) { + m_EndorsedState = endorsed ? ENDORSED_TRUE : ENDORSED_FALSE; m_MetaInfoChanged = true; + } } -void ModInfoRegular::addNexusCategory(int categoryID) { - m_Categories.insert(CategoryFactory::instance().resolveNexusID(categoryID)); + +void ModInfoRegular::setNeverEndorse() +{ + m_EndorsedState = ENDORSED_NEVER; + m_MetaInfoChanged = true; } -void ModInfoRegular::setIsEndorsed(bool endorsed) { - if (m_EndorsedState != ENDORSED_NEVER) { - m_EndorsedState = endorsed ? ENDORSED_TRUE : ENDORSED_FALSE; - m_MetaInfoChanged = true; - } + +bool ModInfoRegular::remove() +{ + m_MetaInfoChanged = false; + return shellDelete(QStringList(absolutePath()), true); } -void ModInfoRegular::setNeverEndorse() { - m_EndorsedState = ENDORSED_NEVER; - m_MetaInfoChanged = true; +void ModInfoRegular::endorse(bool doEndorse) +{ + if (doEndorse != (m_EndorsedState == ENDORSED_TRUE)) { + m_NexusBridge.requestToggleEndorsement(getNexusID(), doEndorse, QVariant(1)); + } } -bool ModInfoRegular::remove() { - m_MetaInfoChanged = false; - return shellDelete(QStringList(absolutePath()), true); + +QString ModInfoRegular::absolutePath() const +{ + return m_Path; } -void ModInfoRegular::endorse(bool doEndorse) { - if (doEndorse != (m_EndorsedState == ENDORSED_TRUE)) { - m_NexusBridge.requestToggleEndorsement(getNexusID(), doEndorse, QVariant(1)); - } +void ModInfoRegular::ignoreUpdate(bool ignore) +{ + if (ignore) { + m_IgnoredVersion = m_NewestVersion; + } else { + m_IgnoredVersion.clear(); + } + m_MetaInfoChanged = true; } -QString ModInfoRegular::absolutePath() const { return m_Path; } -void ModInfoRegular::ignoreUpdate(bool ignore) { - if (ignore) { - m_IgnoredVersion = m_NewestVersion; - } else { - m_IgnoredVersion.clear(); - } - m_MetaInfoChanged = true; +std::vector ModInfoRegular::getFlags() const +{ + std::vector result = ModInfoWithConflictInfo::getFlags(); + if ((m_NexusID > 0) && (endorsedState() == ENDORSED_FALSE)) { + result.push_back(ModInfo::FLAG_NOTENDORSED); + } + if (!isValid()) { + result.push_back(ModInfo::FLAG_INVALID); + } + if (m_Notes.length() != 0) { + result.push_back(ModInfo::FLAG_NOTES); + } + if (m_PluginSelected) { + result.push_back(ModInfo::FLAG_PLUGIN_SELECTED); + } + return result; } -std::vector ModInfoRegular::getFlags() const { - std::vector result = ModInfoWithConflictInfo::getFlags(); - if ((m_NexusID > 0) && (endorsedState() == ENDORSED_FALSE)) { - result.push_back(ModInfo::FLAG_NOTENDORSED); - } - if (!isValid()) { - result.push_back(ModInfo::FLAG_INVALID); + +std::vector ModInfoRegular::getContents() const +{ + QTime now = QTime::currentTime(); + if (m_LastContentCheck.isNull() || (m_LastContentCheck.secsTo(now) > 60)) { + m_CachedContent.clear(); + QDir dir(absolutePath()); + if (dir.entryList(QStringList() << "*.esp" << "*.esm" << "*.esl").size() > 0) { + m_CachedContent.push_back(CONTENT_PLUGIN); } - if (m_Notes.length() != 0) { - result.push_back(ModInfo::FLAG_NOTES); + if (dir.entryList(QStringList() << "*.bsa" << "*.ba2").size() > 0) { + m_CachedContent.push_back(CONTENT_BSA); } - if (m_PluginSelected) { - result.push_back(ModInfo::FLAG_PLUGIN_SELECTED); + + ScriptExtender *extender = qApp->property("managed_game") + .value() + ->feature(); + + if (extender != nullptr) { + QString sePluginPath = extender->PluginPath(); + if (dir.exists(sePluginPath)) + m_CachedContent.push_back(CONTENT_SKSE); } - return result; -} - -std::vector ModInfoRegular::getContents() const { - QTime now = QTime::currentTime(); - if (m_LastContentCheck.isNull() || (m_LastContentCheck.secsTo(now) > 60)) { - m_CachedContent.clear(); - QDir dir(absolutePath()); - if (dir.entryList(QStringList() << "*.esp" - << "*.esm" - << "*.esl") - .size() > 0) { - m_CachedContent.push_back(CONTENT_PLUGIN); - } - if (dir.entryList(QStringList() << "*.bsa" - << "*.ba2") - .size() > 0) { - m_CachedContent.push_back(CONTENT_BSA); - } - - ScriptExtender* extender = qApp->property("managed_game").value()->feature(); - - if (extender != nullptr) { - QString sePluginPath = extender->PluginPath(); - if (dir.exists(sePluginPath)) - m_CachedContent.push_back(CONTENT_SKSE); - } - if (dir.exists("textures")) - m_CachedContent.push_back(CONTENT_TEXTURE); - if (dir.exists("meshes")) - m_CachedContent.push_back(CONTENT_MESH); - if (dir.exists("interface") || dir.exists("menus")) - m_CachedContent.push_back(CONTENT_INTERFACE); - if (dir.exists("music") || dir.exists("sound")) - m_CachedContent.push_back(CONTENT_SOUND); - if (dir.exists("scripts")) - m_CachedContent.push_back(CONTENT_SCRIPT); - if (dir.exists("SkyProc Patchers")) - m_CachedContent.push_back(CONTENT_SKYPROC); - if (dir.exists("MCM")) - m_CachedContent.push_back(CONTENT_MCM); - - m_LastContentCheck = QTime::currentTime(); + if (dir.exists("textures")) + m_CachedContent.push_back(CONTENT_TEXTURE); + if (dir.exists("meshes")) + m_CachedContent.push_back(CONTENT_MESH); + if (dir.exists("interface") || dir.exists("menus")) + m_CachedContent.push_back(CONTENT_INTERFACE); + if (dir.exists("music") || dir.exists("sound")) + m_CachedContent.push_back(CONTENT_SOUND); + if (dir.exists("scripts")) + m_CachedContent.push_back(CONTENT_SCRIPT); + if (dir.exists("SkyProc Patchers")) + m_CachedContent.push_back(CONTENT_SKYPROC); + if (dir.exists("MCM")) + m_CachedContent.push_back(CONTENT_MCM); + + m_LastContentCheck = QTime::currentTime(); + } + + return m_CachedContent; + +} + + +int ModInfoRegular::getHighlight() const +{ + if (!isValid()) + return HIGHLIGHT_INVALID; + auto flags = getFlags(); + 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()) { + return tr("%1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory").arg(name()); + } else { + const std::set &categories = getCategories(); + std::wostringstream categoryString; + categoryString << ToWString(tr("Categories:
    ")); + 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))) << ""; } - return m_CachedContent; + return ToQString(categoryString.str()); + } } -int ModInfoRegular::getHighlight() const { - if (!isValid()) - return HIGHLIGHT_INVALID; - auto flags = getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_PLUGIN_SELECTED) != flags.end()) - return HIGHLIGHT_PLUGIN; - return HIGHLIGHT_NONE; +QString ModInfoRegular::notes() const +{ + return m_Notes; } -QString ModInfoRegular::getDescription() const { - if (!isValid()) { - return tr("%1 contains no esp/esm/esl and no asset (textures, meshes, interface, ...) directory").arg(name()); - } else { - const std::set& categories = getCategories(); - std::wostringstream categoryString; - categoryString << ToWString(tr("Categories:
    ")); - 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))) - << ""; - } - - return ToQString(categoryString.str()); - } +QDateTime ModInfoRegular::creationTime() const +{ + return m_CreationTime; } -QString ModInfoRegular::notes() const { return m_Notes; } - -QDateTime ModInfoRegular::creationTime() const { return m_CreationTime; } +QString ModInfoRegular::getNexusDescription() const +{ + return m_NexusDescription; +} -QString ModInfoRegular::getNexusDescription() const { return m_NexusDescription; } +QString ModInfoRegular::repository() const +{ + return m_Repository; +} -QString ModInfoRegular::repository() const { return m_Repository; } +ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const +{ + return m_EndorsedState; +} -ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const { return m_EndorsedState; } +QDateTime ModInfoRegular::getLastNexusQuery() const +{ + return m_LastNexusQuery; +} -QDateTime ModInfoRegular::getLastNexusQuery() const { return m_LastNexusQuery; } +void ModInfoRegular::setURL(QString const &url) +{ + m_URL = url; + m_MetaInfoChanged = true; +} -void ModInfoRegular::setURL(QString const& url) { - m_URL = url; - m_MetaInfoChanged = true; +QString ModInfoRegular::getURL() const +{ + return m_URL; } -QString ModInfoRegular::getURL() const { return m_URL; } -QStringList ModInfoRegular::archives() const { - QStringList result; - QDir dir(this->absolutePath()); - for (const QString& archive : dir.entryList(QStringList({"*.bsa", "*.ba2"}))) { - result.append(this->absolutePath() + "/" + archive); - } - return result; + +QStringList ModInfoRegular::archives() const +{ + QStringList result; + QDir dir(this->absolutePath()); + for (const QString &archive : dir.entryList(QStringList({ "*.bsa", "*.ba2" }))) { + result.append(this->absolutePath() + "/" + archive); + } + return result; } -void ModInfoRegular::addInstalledFile(int modId, int fileId) { - m_InstalledFileIDs.insert(std::make_pair(modId, fileId)); - m_MetaInfoChanged = true; +void ModInfoRegular::addInstalledFile(int modId, int fileId) +{ + m_InstalledFileIDs.insert(std::make_pair(modId, fileId)); + m_MetaInfoChanged = true; } -std::vector ModInfoRegular::getIniTweaks() const { - QString metaFileName = absolutePath().append("/meta.ini"); - QSettings metaFile(metaFileName, QSettings::IniFormat); +std::vector ModInfoRegular::getIniTweaks() const +{ + QString metaFileName = absolutePath().append("/meta.ini"); + QSettings metaFile(metaFileName, QSettings::IniFormat); - std::vector result; + std::vector result; - int numTweaks = metaFile.beginReadArray("INI Tweaks"); + int numTweaks = metaFile.beginReadArray("INI Tweaks"); - if (numTweaks != 0) { - qDebug("%d active ini tweaks in %s", numTweaks, QDir::toNativeSeparators(metaFileName).toUtf8().constData()); - } + if (numTweaks != 0) { + qDebug("%d active ini tweaks in %s", + numTweaks, QDir::toNativeSeparators(metaFileName).toUtf8().constData()); + } - for (int i = 0; i < numTweaks; ++i) { - metaFile.setArrayIndex(i); - QString filename = absolutePath().append("/INI Tweaks/").append(metaFile.value("name").toString()); - result.push_back(filename); - } - metaFile.endArray(); - return result; + for (int i = 0; i < numTweaks; ++i) { + metaFile.setArrayIndex(i); + QString filename = absolutePath().append("/INI Tweaks/").append(metaFile.value("name").toString()); + result.push_back(filename); + } + metaFile.endArray(); + return result; } diff --git a/src/modinforegular.h b/src/modinforegular.h index 616e2fbe..a94d0363 100644 --- a/src/modinforegular.h +++ b/src/modinforegular.h @@ -12,334 +12,338 @@ * to manage the mod collection * **/ -class ModInfoRegular : public ModInfoWithConflictInfo { +class ModInfoRegular : public ModInfoWithConflictInfo +{ - Q_OBJECT + Q_OBJECT - friend class ModInfo; + friend class ModInfo; public: - ~ModInfoRegular(); - - virtual bool isRegular() const { return true; } - - virtual bool isEmpty() const; - - /** - * @brief test if there is a newer version of the mod - * - * test if there is a newer version of the mod. This does NOT cause - * information to be retrieved from the nexus, it will only test version information already - * available locally. Use checkAllForUpdate() to update this version information - * - * @return true if there is a newer version - **/ - bool updateAvailable() const; - - /** - * @return true if the current update is being ignored - */ - virtual bool updateIgnored() const { return m_IgnoredVersion == m_NewestVersion; } - - /** - * @brief test if there is a newer version of the mod - * - * test if there is a newer version of the mod. This does NOT cause - * information to be retrieved from the nexus, it will only test version information already - * available locally. Use checkAllForUpdate() to update this version information - * - * @return true if there is a newer version - **/ - bool downgradeAvailable() const; - - /** - * @brief request an update of nexus description for this mod. - * - * 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 - **/ - bool updateNXMInfo(); - - /** - * @brief assign or unassign the specified category - * - * Every mod can have an arbitrary number of categories assigned to it - * - * @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 - **/ - void setCategory(int categoryID, bool active); - - /** - * @brief set the name of this mod - * - * set the name of this mod. This will also update the name of the - * directory that contains this mod - * - * @param name new name of the mod - * @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); - - /** - * @brief change the notes (manually set information) for this mod - * @param notes new notes - */ - void setNotes(const QString& notes); - - /** - * @brief set/change the nexus mod id of this mod - * - * @param modID the nexus mod id - **/ - void setNexusID(int modID); - - /** - * @brief set the version of this mod - * - * this can be used to overwrite the version of a mod without actually - * updating the mod - * - * @param version the new version to use - **/ - void setVersion(const MOBase::VersionInfo& version); - - /** - * @brief set the newest version of this mod on the nexus - * - * this can be used to overwrite the version of a mod without actually - * 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 - **/ - void setNewestVersion(const MOBase::VersionInfo& version); - - /** - * @brief changes/updates the nexus description text - * @param description the current description text - */ - virtual void setNexusDescription(const QString& description); - - virtual void setInstallationFile(const QString& fileName); - - /** - * @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 - */ - virtual void addNexusCategory(int categoryID); - - /** - * @brief sets the new primary category of the mod - * @param categoryID the category to set - */ - virtual void setPrimaryCategory(int categoryID) { - m_PrimaryCategory = categoryID; - m_MetaInfoChanged = true; - } - - /** - * @brief sets the download repository - * @param repository - */ - virtual void setRepository(const QString& repository) { m_Repository = repository; } - - /** - * update the endorsement state for the mod. This only changes the - * buffered state, it does not sync with Nexus - * @param endorsed the new endorsement state - */ - virtual void setIsEndorsed(bool 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(); - - /** - * @brief delete the mod from the disc. This does not update the global ModInfo structure or indices - * @return true if the mod was successfully removed - **/ - bool remove(); - - /** - * @brief endorse or un-endorse the mod - * @param doEndorse if true, the mod is endorsed, if false, it's un-endorsed. - * @note if doEndorse doesn't differ from the current value, nothing happens. - */ - virtual void endorse(bool doEndorse); - - /** - * @brief getter for the mod name - * - * @return the mod name - **/ - QString name() const { return m_Name; } - - /** - * @brief getter for the mod path - * - * @return the (absolute) path to the mod - **/ - QString absolutePath() const; - - /** - * @brief getter for the newest version number of this mod - * - * @return newest version of the mod - **/ - MOBase::VersionInfo getNewestVersion() const { return m_NewestVersion; } - - /** - * @brief ignore the newest version for updates - */ - void ignoreUpdate(bool ignore); - - /** - * @brief getter for the installation file - * - * @return file used to install this mod from - */ - virtual QString getInstallationFile() const { return m_InstallationFile; } - /** - * @brief getter for the nexus mod id - * - * @return the nexus mod id. may be 0 if the mod id isn't known or doesn't exist - **/ - int getNexusID() const { return m_NexusID; } - - /** - * @return the fixed priority of mods of this type or INT_MIN if the priority of mods - * needs to be user-modifiable - */ - virtual int getFixedPriority() const { return INT_MIN; } - - /** - * @return true if the mod can be updated - */ - virtual bool canBeUpdated() const { return m_NexusID > 0; } - - /** - * @return true if the mod can be enabled/disabled - */ - virtual bool canBeEnabled() const { return true; } - - /** - * @return a list of flags for this mod - */ - virtual std::vector getFlags() const; - - virtual std::vector getContents() const; - - /** - * @return an indicator if and how this mod should be highlighted by the UI - */ - virtual int getHighlight() const; - - /** - * @return list of names of ini tweaks - **/ - std::vector getIniTweaks() const; - - /** - * @return a description about the mod, to be displayed in the ui - */ - virtual QString getDescription() const; - - /** - * @return manually set notes for this mod - */ - virtual QString notes() const; - - /** - * @return time this mod was created (file time of the directory) - */ - virtual QDateTime creationTime() const; - - /** - * @return nexus description of the mod (html) - */ - QString getNexusDescription() const; - - /** - * @return repository from which the file was downloaded - */ - virtual QString repository() const; - - /** - * @return true if the file has been endorsed on nexus - */ - virtual EEndorsedState endorsedState() const; - - /** - * @return last time nexus was queried for infos on this mod - */ - virtual QDateTime getLastNexusQuery() const; - - virtual QStringList archives() const; - - virtual void addInstalledFile(int modId, int fileId); - - /** - * @brief stores meta information back to disk - */ - virtual void saveMeta(); - - void readMeta(); - - /** - * @brief set the URL for a mod - */ - virtual void setURL(QString const&); - - /** - * @returns the URL for a mod - */ - virtual QString getURL() const; + + ~ModInfoRegular(); + + virtual bool isRegular() const { return true; } + + virtual bool isEmpty() const; + + /** + * @brief test if there is a newer version of the mod + * + * test if there is a newer version of the mod. This does NOT cause + * information to be retrieved from the nexus, it will only test version information already + * available locally. Use checkAllForUpdate() to update this version information + * + * @return true if there is a newer version + **/ + bool updateAvailable() const; + + /** + * @return true if the current update is being ignored + */ + virtual bool updateIgnored() const { return m_IgnoredVersion == m_NewestVersion; } + + /** + * @brief test if there is a newer version of the mod + * + * test if there is a newer version of the mod. This does NOT cause + * information to be retrieved from the nexus, it will only test version information already + * available locally. Use checkAllForUpdate() to update this version information + * + * @return true if there is a newer version + **/ + bool downgradeAvailable() const; + + /** + * @brief request an update of nexus description for this mod. + * + * 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 + **/ + bool updateNXMInfo(); + + /** + * @brief assign or unassign the specified category + * + * Every mod can have an arbitrary number of categories assigned to it + * + * @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 + **/ + void setCategory(int categoryID, bool active); + + /** + * @brief set the name of this mod + * + * set the name of this mod. This will also update the name of the + * directory that contains this mod + * + * @param name new name of the mod + * @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); + + /** + * @brief change the notes (manually set information) for this mod + * @param notes new notes + */ + void setNotes(const QString ¬es); + + /** + * @brief set/change the nexus mod id of this mod + * + * @param modID the nexus mod id + **/ + void setNexusID(int modID); + + /** + * @brief set the version of this mod + * + * this can be used to overwrite the version of a mod without actually + * updating the mod + * + * @param version the new version to use + **/ + void setVersion(const MOBase::VersionInfo &version); + + /** + * @brief set the newest version of this mod on the nexus + * + * this can be used to overwrite the version of a mod without actually + * 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 + **/ + void setNewestVersion(const MOBase::VersionInfo &version); + + /** + * @brief changes/updates the nexus description text + * @param description the current description text + */ + virtual void setNexusDescription(const QString &description); + + virtual void setInstallationFile(const QString &fileName); + + /** + * @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 + */ + virtual void addNexusCategory(int categoryID); + + /** + * @brief sets the new primary category of the mod + * @param categoryID the category to set + */ + virtual void setPrimaryCategory(int categoryID) { m_PrimaryCategory = categoryID; m_MetaInfoChanged = true; } + + /** + * @brief sets the download repository + * @param repository + */ + virtual void setRepository(const QString &repository) { m_Repository = repository; } + + /** + * update the endorsement state for the mod. This only changes the + * buffered state, it does not sync with Nexus + * @param endorsed the new endorsement state + */ + virtual void setIsEndorsed(bool 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(); + + /** + * @brief delete the mod from the disc. This does not update the global ModInfo structure or indices + * @return true if the mod was successfully removed + **/ + bool remove(); + + /** + * @brief endorse or un-endorse the mod + * @param doEndorse if true, the mod is endorsed, if false, it's un-endorsed. + * @note if doEndorse doesn't differ from the current value, nothing happens. + */ + virtual void endorse(bool doEndorse); + + /** + * @brief getter for the mod name + * + * @return the mod name + **/ + QString name() const { return m_Name; } + + /** + * @brief getter for the mod path + * + * @return the (absolute) path to the mod + **/ + QString absolutePath() const; + + /** + * @brief getter for the newest version number of this mod + * + * @return newest version of the mod + **/ + MOBase::VersionInfo getNewestVersion() const { return m_NewestVersion; } + + /** + * @brief ignore the newest version for updates + */ + void ignoreUpdate(bool ignore); + + /** + * @brief getter for the installation file + * + * @return file used to install this mod from + */ + virtual QString getInstallationFile() const { return m_InstallationFile; } + /** + * @brief getter for the nexus mod id + * + * @return the nexus mod id. may be 0 if the mod id isn't known or doesn't exist + **/ + int getNexusID() const { return m_NexusID; } + + /** + * @return the fixed priority of mods of this type or INT_MIN if the priority of mods + * needs to be user-modifiable + */ + virtual int getFixedPriority() const { return INT_MIN; } + + /** + * @return true if the mod can be updated + */ + virtual bool canBeUpdated() const { return m_NexusID > 0; } + + /** + * @return true if the mod can be enabled/disabled + */ + virtual bool canBeEnabled() const { return true; } + + /** + * @return a list of flags for this mod + */ + virtual std::vector getFlags() const; + + virtual std::vector getContents() const; + + /** + * @return an indicator if and how this mod should be highlighted by the UI + */ + virtual int getHighlight() const; + + /** + * @return list of names of ini tweaks + **/ + std::vector getIniTweaks() const; + + /** + * @return a description about the mod, to be displayed in the ui + */ + virtual QString getDescription() const; + + /** + * @return manually set notes for this mod + */ + virtual QString notes() const; + + /** + * @return time this mod was created (file time of the directory) + */ + virtual QDateTime creationTime() const; + + /** + * @return nexus description of the mod (html) + */ + QString getNexusDescription() const; + + /** + * @return repository from which the file was downloaded + */ + virtual QString repository() const; + + /** + * @return true if the file has been endorsed on nexus + */ + virtual EEndorsedState endorsedState() const; + + /** + * @return last time nexus was queried for infos on this mod + */ + virtual QDateTime getLastNexusQuery() const; + + virtual QStringList archives() const; + + virtual void addInstalledFile(int modId, int fileId); + + /** + * @brief stores meta information back to disk + */ + virtual void saveMeta(); + + void readMeta(); + + /** + * @brief set the URL for a mod + */ + virtual void setURL(QString const &); + + /** + * @returns the URL for a mod + */ + virtual QString getURL() const; private: - void setEndorsedState(EEndorsedState endorsedState); + + void setEndorsedState(EEndorsedState endorsedState); private slots: - void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData); - void nxmEndorsementToggled(int, QVariant userData, QVariant resultData); - void nxmRequestFailed(int modID, int fileID, QVariant userData, const QString& errorMessage); + void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData); + void nxmEndorsementToggled(int, QVariant userData, QVariant resultData); + void nxmRequestFailed(int modID, int fileID, QVariant userData, const QString &errorMessage); protected: - ModInfoRegular(const QDir& path, MOShared::DirectoryEntry** directoryStructure); + + ModInfoRegular(const QDir &path, MOShared::DirectoryEntry **directoryStructure); private: - QString m_Name; - QString m_Path; - QString m_InstallationFile; - QString m_Notes; - QString m_NexusDescription; - QString m_Repository; - QString m_URL; - QDateTime m_CreationTime; - QDateTime m_LastNexusQuery; + QString m_Name; + QString m_Path; + QString m_InstallationFile; + QString m_Notes; + QString m_NexusDescription; + QString m_Repository; + QString m_URL; - int m_NexusID; - std::set> m_InstalledFileIDs; + QDateTime m_CreationTime; + QDateTime m_LastNexusQuery; - bool m_MetaInfoChanged; - MOBase::VersionInfo m_NewestVersion; - MOBase::VersionInfo m_IgnoredVersion; + int m_NexusID; + std::set> m_InstalledFileIDs; - EEndorsedState m_EndorsedState; + bool m_MetaInfoChanged; + MOBase::VersionInfo m_NewestVersion; + MOBase::VersionInfo m_IgnoredVersion; - NexusBridge m_NexusBridge; + EEndorsedState m_EndorsedState; + + NexusBridge m_NexusBridge; + + mutable std::vector m_CachedContent; + mutable QTime m_LastContentCheck; - mutable std::vector m_CachedContent; - mutable QTime m_LastContentCheck; }; + #endif // MODINFOREGULAR_H diff --git a/src/modinfowithconflictinfo.cpp b/src/modinfowithconflictinfo.cpp index 74a208e9..b8ece783 100644 --- a/src/modinfowithconflictinfo.cpp +++ b/src/modinfowithconflictinfo.cpp @@ -6,117 +6,125 @@ using namespace MOBase; using namespace MOShared; -ModInfoWithConflictInfo::ModInfoWithConflictInfo(DirectoryEntry** directoryStructure) - : m_DirectoryStructure(directoryStructure) {} +ModInfoWithConflictInfo::ModInfoWithConflictInfo(DirectoryEntry **directoryStructure) + : m_DirectoryStructure(directoryStructure) {} -void ModInfoWithConflictInfo::clearCaches() { m_LastConflictCheck = QTime(); } +void ModInfoWithConflictInfo::clearCaches() +{ + m_LastConflictCheck = QTime(); +} -std::vector ModInfoWithConflictInfo::getFlags() const { - std::vector result; - switch (isConflicted()) { +std::vector ModInfoWithConflictInfo::getFlags() const +{ + std::vector result; + switch (isConflicted()) { case CONFLICT_MIXED: { - result.push_back(ModInfo::FLAG_CONFLICT_MIXED); + result.push_back(ModInfo::FLAG_CONFLICT_MIXED); } break; case CONFLICT_OVERWRITE: { - result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITE); + result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITE); } break; case CONFLICT_OVERWRITTEN: { - result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITTEN); + result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITTEN); } break; case CONFLICT_REDUNDANT: { - result.push_back(ModInfo::FLAG_CONFLICT_REDUNDANT); + result.push_back(ModInfo::FLAG_CONFLICT_REDUNDANT); } break; - default: { /* NOP */ - } - } - return result; + default: { /* NOP */ } + } + return result; } -void ModInfoWithConflictInfo::doConflictCheck() const { - m_OverwriteList.clear(); - m_OverwrittenList.clear(); - - bool providesAnything = false; - - int dataID = 0; - if ((*m_DirectoryStructure)->originExists(L"data")) { - dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID(); - } - - std::wstring name = ToWString(this->name()); - m_CurrentConflictState = CONFLICT_NONE; +void ModInfoWithConflictInfo::doConflictCheck() const +{ + m_OverwriteList.clear(); + m_OverwrittenList.clear(); + + bool providesAnything = false; + + int dataID = 0; + if ((*m_DirectoryStructure)->originExists(L"data")) { + dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID(); + } + + std::wstring name = ToWString(this->name()); + + m_CurrentConflictState = CONFLICT_NONE; + + if ((*m_DirectoryStructure)->originExists(name)) { + FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); + std::vector files = origin.getFiles(); + // for all files in this origin + for (FileEntry::Ptr file : files) { + const std::vector> &alternatives = file->getAlternatives(); + if ((alternatives.size() == 0) || (alternatives.begin()->first == dataID)) { + // no alternatives -> no conflict + providesAnything = true; + } else { + if (file->getOrigin() != origin.getID()) { + FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(file->getOrigin()); + unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); + m_OverwrittenList.insert(altIndex); + } else { + providesAnything = true; + } - if ((*m_DirectoryStructure)->originExists(name)) { - FilesOrigin& origin = (*m_DirectoryStructure)->getOriginByName(name); - std::vector files = origin.getFiles(); - // for all files in this origin - for (FileEntry::Ptr file : files) { - const std::vector>& alternatives = file->getAlternatives(); - if ((alternatives.size() == 0) || (alternatives.begin()->first == dataID)) { - // no alternatives -> no conflict - providesAnything = true; + // for all non-providing alternative origins + for (auto altInfo : alternatives) { + if ((altInfo.first != dataID) && (altInfo.first != origin.getID())) { + FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(altInfo.first); + unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); + if (origin.getPriority() > altOrigin.getPriority()) { + m_OverwriteList.insert(altIndex); } else { - if (file->getOrigin() != origin.getID()) { - FilesOrigin& altOrigin = (*m_DirectoryStructure)->getOriginByID(file->getOrigin()); - unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); - m_OverwrittenList.insert(altIndex); - } else { - providesAnything = true; - } - - // for all non-providing alternative origins - for (auto altInfo : alternatives) { - if ((altInfo.first != dataID) && (altInfo.first != origin.getID())) { - FilesOrigin& altOrigin = (*m_DirectoryStructure)->getOriginByID(altInfo.first); - unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); - if (origin.getPriority() > altOrigin.getPriority()) { - m_OverwriteList.insert(altIndex); - } else { - m_OverwrittenList.insert(altIndex); - } - } - } + m_OverwrittenList.insert(altIndex); } + } } - m_LastConflictCheck = QTime::currentTime(); - - if (files.size() != 0) { - if (!providesAnything) - m_CurrentConflictState = CONFLICT_REDUNDANT; - else if (!m_OverwriteList.empty() && !m_OverwrittenList.empty()) - m_CurrentConflictState = CONFLICT_MIXED; - else if (!m_OverwriteList.empty()) - m_CurrentConflictState = CONFLICT_OVERWRITE; - else if (!m_OverwrittenList.empty()) - m_CurrentConflictState = CONFLICT_OVERWRITTEN; - } + } } + m_LastConflictCheck = QTime::currentTime(); + + if (files.size() != 0) { + if (!providesAnything) + m_CurrentConflictState = CONFLICT_REDUNDANT; + else if (!m_OverwriteList.empty() && !m_OverwrittenList.empty()) + m_CurrentConflictState = CONFLICT_MIXED; + else if (!m_OverwriteList.empty()) + m_CurrentConflictState = CONFLICT_OVERWRITE; + else if (!m_OverwrittenList.empty()) + m_CurrentConflictState = CONFLICT_OVERWRITTEN; + } + } } -ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() const { - // this is costy so cache the result - QTime now = QTime::currentTime(); - if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) { - doConflictCheck(); - } +ModInfoWithConflictInfo::EConflictType ModInfoWithConflictInfo::isConflicted() const +{ + // this is costy so cache the result + QTime now = QTime::currentTime(); + if (m_LastConflictCheck.isNull() || (m_LastConflictCheck.secsTo(now) > 10)) { + doConflictCheck(); + } - return m_CurrentConflictState; + return m_CurrentConflictState; } -bool ModInfoWithConflictInfo::isRedundant() const { - std::wstring name = ToWString(this->name()); - if ((*m_DirectoryStructure)->originExists(name)) { - FilesOrigin& origin = (*m_DirectoryStructure)->getOriginByName(name); - std::vector files = origin.getFiles(); - bool ignore = false; - for (auto iter = files.begin(); iter != files.end(); ++iter) { - if ((*iter)->getOrigin(ignore) == origin.getID()) { - return false; - } - } - return true; - } else { + +bool ModInfoWithConflictInfo::isRedundant() const +{ + std::wstring name = ToWString(this->name()); + if ((*m_DirectoryStructure)->originExists(name)) { + FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); + std::vector files = origin.getFiles(); + bool ignore = false; + for (auto iter = files.begin(); iter != files.end(); ++iter) { + if ((*iter)->getOrigin(ignore) == origin.getID()) { return false; + } } + return true; + } else { + return false; + } } diff --git a/src/modinfowithconflictinfo.h b/src/modinfowithconflictinfo.h index 05ee6ee1..be31f20f 100644 --- a/src/modinfowithconflictinfo.h +++ b/src/modinfowithconflictinfo.h @@ -5,46 +5,60 @@ #include -class ModInfoWithConflictInfo : public ModInfo { +class ModInfoWithConflictInfo : public ModInfo +{ public: - ModInfoWithConflictInfo(MOShared::DirectoryEntry** directoryStructure); - std::vector getFlags() const; + ModInfoWithConflictInfo(MOShared::DirectoryEntry **directoryStructure); - /** - * @brief clear all caches held for this mod - */ - virtual void clearCaches(); + std::vector getFlags() const; - virtual std::set getModOverwrite() { return m_OverwriteList; } + /** + * @brief clear all caches held for this mod + */ + virtual void clearCaches(); - virtual std::set getModOverwritten() { return m_OverwrittenList; } + virtual std::set getModOverwrite() { return m_OverwriteList; } - virtual void doConflictCheck() const; + virtual std::set getModOverwritten() { return m_OverwrittenList; } + + virtual void doConflictCheck() const; private: - enum EConflictType { CONFLICT_NONE, CONFLICT_OVERWRITE, CONFLICT_OVERWRITTEN, CONFLICT_MIXED, CONFLICT_REDUNDANT }; + + enum EConflictType { + CONFLICT_NONE, + CONFLICT_OVERWRITE, + CONFLICT_OVERWRITTEN, + CONFLICT_MIXED, + CONFLICT_REDUNDANT + }; private: - /** - * @return true if there is a conflict for files in this mod - */ - EConflictType isConflicted() const; - /** - * @return true if this mod is completely replaced by others - */ - bool isRedundant() const; + /** + * @return true if there is a conflict for files in this mod + */ + EConflictType isConflicted() const; + + /** + * @return true if this mod is completely replaced by others + */ + bool isRedundant() const; private: - MOShared::DirectoryEntry** m_DirectoryStructure; - mutable EConflictType m_CurrentConflictState; - mutable QTime m_LastConflictCheck; + MOShared::DirectoryEntry **m_DirectoryStructure; + + mutable EConflictType m_CurrentConflictState; + mutable QTime m_LastConflictCheck; + + mutable std::set m_OverwriteList; // indices of mods overritten by this mod + mutable std::set m_OverwrittenList; // indices of mods overwriting this mod - mutable std::set m_OverwriteList; // indices of mods overritten by this mod - mutable std::set m_OverwrittenList; // indices of mods overwriting this mod }; + + #endif // MODINFOWITHCONFLICTINFO_H diff --git a/src/modlist.cpp b/src/modlist.cpp index 20d1a9cb..4002a424 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -19,1138 +19,1173 @@ along with Mod Organizer. If not, see . #include "modlist.h" -#include "installationtester.h" #include "messagedialog.h" -#include "modlistsortproxy.h" +#include "installationtester.h" #include "qtgroupingproxy.h" -#include "settings.h" #include "viewmarkingscrollbar.h" +#include "modlistsortproxy.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 +#include #include +#include +#include +#include -#include #include #include +#include + using namespace MOBase; -ModList::ModList(QObject* parent) - : QAbstractItemModel(parent), m_Profile(nullptr), m_NexusInterface(nullptr), m_Modified(false), - m_FontMetrics(QFont()), m_DropOnItems(false) { - m_ContentIcons[ModInfo::CONTENT_PLUGIN] = - std::make_tuple(":/MO/gui/content/plugin", tr("Game Plugins (ESP/ESM/ESL)")); - m_ContentIcons[ModInfo::CONTENT_INTERFACE] = std::make_tuple(":/MO/gui/content/interface", tr("Interface")); - m_ContentIcons[ModInfo::CONTENT_MESH] = std::make_tuple(":/MO/gui/content/mesh", tr("Meshes")); - m_ContentIcons[ModInfo::CONTENT_BSA] = std::make_tuple(":/MO/gui/content/bsa", tr("Bethesda Archive")); - m_ContentIcons[ModInfo::CONTENT_SCRIPT] = std::make_tuple(":/MO/gui/content/script", tr("Scripts (Papyrus)")); - m_ContentIcons[ModInfo::CONTENT_SKSE] = std::make_tuple(":/MO/gui/content/skse", tr("Script Extender Plugin")); - m_ContentIcons[ModInfo::CONTENT_SKYPROC] = std::make_tuple(":/MO/gui/content/skyproc", tr("SkyProc Patcher")); - m_ContentIcons[ModInfo::CONTENT_SOUND] = std::make_tuple(":/MO/gui/content/sound", tr("Sound or Music")); - m_ContentIcons[ModInfo::CONTENT_TEXTURE] = std::make_tuple(":/MO/gui/content/texture", tr("Textures")); - m_ContentIcons[ModInfo::CONTENT_MCM] = std::make_tuple(":/MO/gui/content/menu", tr("MCM Configuration")); - - m_LastCheck.start(); + +ModList::ModList(QObject *parent) + : QAbstractItemModel(parent) + , m_Profile(nullptr) + , m_NexusInterface(nullptr) + , m_Modified(false) + , m_FontMetrics(QFont()) + , m_DropOnItems(false) +{ + m_ContentIcons[ModInfo::CONTENT_PLUGIN] = std::make_tuple(":/MO/gui/content/plugin", tr("Game Plugins (ESP/ESM/ESL)")); + m_ContentIcons[ModInfo::CONTENT_INTERFACE] = std::make_tuple(":/MO/gui/content/interface", tr("Interface")); + m_ContentIcons[ModInfo::CONTENT_MESH] = std::make_tuple(":/MO/gui/content/mesh", tr("Meshes")); + m_ContentIcons[ModInfo::CONTENT_BSA] = std::make_tuple(":/MO/gui/content/bsa", tr("Bethesda Archive")); + m_ContentIcons[ModInfo::CONTENT_SCRIPT] = std::make_tuple(":/MO/gui/content/script", tr("Scripts (Papyrus)")); + m_ContentIcons[ModInfo::CONTENT_SKSE] = std::make_tuple(":/MO/gui/content/skse", tr("Script Extender Plugin")); + m_ContentIcons[ModInfo::CONTENT_SKYPROC] = std::make_tuple(":/MO/gui/content/skyproc", tr("SkyProc Patcher")); + m_ContentIcons[ModInfo::CONTENT_SOUND] = std::make_tuple(":/MO/gui/content/sound", tr("Sound or Music")); + m_ContentIcons[ModInfo::CONTENT_TEXTURE] = std::make_tuple(":/MO/gui/content/texture", tr("Textures")); + m_ContentIcons[ModInfo::CONTENT_MCM] = std::make_tuple(":/MO/gui/content/menu", tr("MCM Configuration")); + + m_LastCheck.start(); } -ModList::~ModList() { - m_ModStateChanged.disconnect_all_slots(); - m_ModMoved.disconnect_all_slots(); +ModList::~ModList() +{ + m_ModStateChanged.disconnect_all_slots(); + m_ModMoved.disconnect_all_slots(); } -void ModList::setProfile(Profile* profile) { m_Profile = profile; } +void ModList::setProfile(Profile *profile) +{ + m_Profile = profile; +} -int ModList::rowCount(const QModelIndex& parent) const { - if (!parent.isValid()) { - return ModInfo::getNumMods(); - } else { - return 0; - } +int ModList::rowCount(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return ModInfo::getNumMods(); + } else { + return 0; + } } -bool ModList::hasChildren(const QModelIndex& parent) const { - if (!parent.isValid()) { - return ModInfo::getNumMods() > 0; - } else { - return false; - } +bool ModList::hasChildren(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return ModInfo::getNumMods() > 0; + } else { + return false; + } } -int ModList::columnCount(const QModelIndex&) const { return COL_LASTCOLUMN + 1; } -QVariant ModList::getOverwriteData(int column, int role) const { - switch (role) { +int ModList::columnCount(const QModelIndex &) const +{ + return COL_LASTCOLUMN + 1; +} + + +QVariant ModList::getOverwriteData(int column, int role) const +{ + switch (role) { case Qt::DisplayRole: { - if (column == 0) { - return "Overwrite"; - } else { - return QVariant(); - } + if (column == 0) { + return "Overwrite"; + } else { + return QVariant(); + } } break; case Qt::CheckStateRole: { - if (column == 0) { - return Qt::Checked; - } else { - return QVariant(); - } - } break; - case Qt::TextAlignmentRole: - return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - case Qt::UserRole: - return -1; - case Qt::ForegroundRole: - return QBrush(Qt::red); - case Qt::ToolTipRole: - return tr("This entry contains files that have been created inside the virtual data tree (i.e. by the " - "construction kit)"); - default: + if (column == 0) { + return Qt::Checked; + } else { return QVariant(); - } + } + } break; + case Qt::TextAlignmentRole: return QVariant(Qt::AlignCenter | Qt::AlignVCenter); + case Qt::UserRole: return -1; + case Qt::ForegroundRole: return QBrush(Qt::red); + case Qt::ToolTipRole: return tr("This entry contains files that have been created inside the virtual data tree (i.e. by the construction kit)"); + default: return QVariant(); + } } -QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const { - switch (flag) { - case ModInfo::FLAG_BACKUP: - return tr("Backup"); - case ModInfo::FLAG_INVALID: - return tr("No valid game data"); - case ModInfo::FLAG_NOTENDORSED: - return tr("Not endorsed yet"); - case ModInfo::FLAG_NOTES: - return QString("%1").arg(modInfo->notes().replace("\n", "
    ")); - case ModInfo::FLAG_CONFLICT_OVERWRITE: - return tr("Overwrites files"); - case ModInfo::FLAG_CONFLICT_OVERWRITTEN: - return tr("Overwritten files"); - case ModInfo::FLAG_CONFLICT_MIXED: - return tr("Overwrites & Overwritten"); - case ModInfo::FLAG_CONFLICT_REDUNDANT: - return tr("Redundant"); - default: - return ""; - } + +QString ModList::getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const +{ + switch (flag) { + case ModInfo::FLAG_BACKUP: return tr("Backup"); + case ModInfo::FLAG_INVALID: return tr("No valid game data"); + case ModInfo::FLAG_NOTENDORSED: return tr("Not endorsed yet"); + case ModInfo::FLAG_NOTES: return QString("%1").arg(modInfo->notes().replace("\n", "
    ")); + case ModInfo::FLAG_CONFLICT_OVERWRITE: return tr("Overwrites files"); + case ModInfo::FLAG_CONFLICT_OVERWRITTEN: return tr("Overwritten files"); + case ModInfo::FLAG_CONFLICT_MIXED: return tr("Overwrites & Overwritten"); + case ModInfo::FLAG_CONFLICT_REDUNDANT: return tr("Redundant"); + default: return ""; + } } -QVariantList ModList::contentsToIcons(const std::vector& contents) const { - QVariantList result; - std::set contentsSet(contents.begin(), contents.end()); - for (auto iter = m_ContentIcons.begin(); iter != m_ContentIcons.end(); ++iter) { - if (contentsSet.find(iter->first) != contentsSet.end()) { - result.append(std::get<0>(iter->second)); - } else { - result.append(QString()); - } + +QVariantList ModList::contentsToIcons(const std::vector &contents) const +{ + QVariantList result; + std::set contentsSet(contents.begin(), contents.end()); + for (auto iter = m_ContentIcons.begin(); iter != m_ContentIcons.end(); ++iter) { + if (contentsSet.find(iter->first) != contentsSet.end()) { + result.append(std::get<0>(iter->second)); + } else { + result.append(QString()); } - return result; + } + return result; } -QString ModList::contentsToToolTip(const std::vector& contents) const { - QString result(""); +QString ModList::contentsToToolTip(const std::vector &contents) const +{ + QString result("
    "); - std::set contentsSet(contents.begin(), contents.end()); - for (auto iter = m_ContentIcons.begin(); iter != m_ContentIcons.end(); ++iter) { - if (contentsSet.find(iter->first) != contentsSet.end()) { - result.append(QString("" - "") - .arg(std::get<0>(iter->second)) - .arg(std::get<1>(iter->second))); - } + std::set contentsSet(contents.begin(), contents.end()); + for (auto iter = m_ContentIcons.begin(); iter != m_ContentIcons.end(); ++iter) { + if (contentsSet.find(iter->first) != contentsSet.end()) { + result.append(QString("" + "") + .arg(std::get<0>(iter->second)).arg(std::get<1>(iter->second))); } - result.append("
    %2
    %2
    "); - return result; + } + result.append(""); + return result; } -QVariant ModList::data(const QModelIndex& modelIndex, int role) const { - if (m_Profile == nullptr) - return QVariant(); - if (!modelIndex.isValid()) - return QVariant(); - unsigned int modIndex = modelIndex.row(); - int column = modelIndex.column(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if ((role == Qt::DisplayRole) || (role == Qt::EditRole)) { - if ((column == COL_FLAGS) || (column == COL_CONTENT)) { - return QVariant(); - } else if (column == COL_NAME) { - return modInfo->name(); - } else if (column == COL_VERSION) { - VersionInfo verInfo = modInfo->getVersion(); - QString version = verInfo.displayString(); - if (role != Qt::EditRole) { - if (version.isEmpty() && modInfo->canBeUpdated()) { - version = "?"; - } - } - return version; - } else if (column == COL_PRIORITY) { - int priority = modInfo->getFixedPriority(); - if (priority != INT_MIN) { - return QVariant(); // hide priority for mods where it's fixed - } else { - return m_Profile->getModPriority(modIndex); - } - } else if (column == COL_MODID) { - int modID = modInfo->getNexusID(); - if (modID >= 0) { - return modID; - } else { - return QVariant(); - } - } else if (column == COL_CATEGORY) { - if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { - return tr("Non-MO"); - } else { - int category = modInfo->getPrimaryCategory(); - if (category != -1) { - CategoryFactory& categoryFactory = CategoryFactory::instance(); - if (categoryFactory.categoryExists(category)) { - try { - int categoryIdx = categoryFactory.getCategoryIndex(category); - return categoryFactory.getCategoryName(categoryIdx); - } catch (const std::exception& e) { - qCritical("failed to retrieve category name: %s", e.what()); - return QString(); - } - } else { - qWarning("category %d doesn't exist (may have been removed)", category); - modInfo->setCategory(category, false); - return QString(); - } - } else { - return QVariant(); - } - } - } else if (column == COL_INSTALLTIME) { - // display installation time for mods that can be updated - if (modInfo->creationTime().isValid()) { - return modInfo->creationTime(); - } else { - return QVariant(); - } - } else { - return tr("invalid"); - } - } else if ((role == Qt::CheckStateRole) && (column == 0)) { - if (modInfo->canBeEnabled()) { - return m_Profile->modEnabled(modIndex) ? Qt::Checked : Qt::Unchecked; - } else { - return QVariant(); +QVariant ModList::data(const QModelIndex &modelIndex, int role) const +{ + if (m_Profile == nullptr) return QVariant(); + if (!modelIndex.isValid()) return QVariant(); + unsigned int modIndex = modelIndex.row(); + int column = modelIndex.column(); + + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + if ((role == Qt::DisplayRole) || + (role == Qt::EditRole)) { + if ((column == COL_FLAGS) + || (column == COL_CONTENT)) { + return QVariant(); + } else if (column == COL_NAME) { + return modInfo->name(); + } else if (column == COL_VERSION) { + VersionInfo verInfo = modInfo->getVersion(); + QString version = verInfo.displayString(); + if (role != Qt::EditRole) { + if (version.isEmpty() && modInfo->canBeUpdated()) { + version = "?"; } - } else if (role == Qt::TextAlignmentRole) { - if (column == COL_NAME) { - if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_CENTER) { - return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - } else { - return QVariant(Qt::AlignLeft | Qt::AlignVCenter); - } - } else if (column == COL_VERSION) { - return QVariant(Qt::AlignRight | Qt::AlignVCenter); - } else { - return QVariant(Qt::AlignCenter | Qt::AlignVCenter); - } - } else if (role == Qt::UserRole) { - if (column == COL_CATEGORY) { - QVariantList categoryNames; - 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))); - } - if (categoryNames.count() != 0) { - return categoryNames; - } else { - return QVariant(); - } - } else if (column == COL_PRIORITY) { - int priority = modInfo->getFixedPriority(); - if (priority != INT_MIN) { - return priority; - } else { - return m_Profile->getModPriority(modIndex); + } + return version; + } else if (column == COL_PRIORITY) { + int priority = modInfo->getFixedPriority(); + if (priority != INT_MIN) { + return QVariant(); // hide priority for mods where it's fixed + } else { + return m_Profile->getModPriority(modIndex); + } + } else if (column == COL_MODID) { + int modID = modInfo->getNexusID(); + if (modID >= 0) { + return modID; + } else { + return QVariant(); + } + } else if (column == COL_CATEGORY) { + if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { + return tr("Non-MO"); + } else { + int category = modInfo->getPrimaryCategory(); + if (category != -1) { + CategoryFactory &categoryFactory = CategoryFactory::instance(); + if (categoryFactory.categoryExists(category)) { + try { + int categoryIdx = categoryFactory.getCategoryIndex(category); + return categoryFactory.getCategoryName(categoryIdx); + } catch (const std::exception &e) { + qCritical("failed to retrieve category name: %s", e.what()); + return QString(); } + } else { + qWarning("category %d doesn't exist (may have been removed)", category); + modInfo->setCategory(category, false); + return QString(); + } } else { - return modInfo->getNexusID(); - } - } else if (role == Qt::UserRole + 1) { - return modIndex; - } else if (role == Qt::UserRole + 2) { - 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 == Qt::UserRole + 3) { - return contentsToIcons(modInfo->getContents()); - } else if (role == Qt::FontRole) { - QFont result; - if (column == COL_NAME) { - if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_INVALID) { - result.setItalic(true); - } - } else if ((column == COL_CATEGORY) && (modInfo->hasFlag(ModInfo::FLAG_FOREIGN))) { - result.setItalic(true); - } else if (column == COL_VERSION) { - if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { - result.setWeight(QFont::Bold); - } - } - return result; - } else if (role == Qt::DecorationRole) { - if (column == COL_VERSION) { - if (modInfo->updateAvailable()) { - return QIcon(":/MO/gui/update_available"); - } else if (modInfo->downgradeAvailable()) { - return QIcon(":/MO/gui/warning"); - } else if (modInfo->getVersion().scheme() == VersionInfo::SCHEME_DATE) { - return QIcon(":/MO/gui/version_date"); - } + return QVariant(); } + } + } else if (column == COL_INSTALLTIME) { + // display installation time for mods that can be updated + if (modInfo->creationTime().isValid()) { + return modInfo->creationTime(); + } else { return QVariant(); - } 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) - return QBrush(Qt::darkGray); - else if (highlight & ModInfo::HIGHLIGHT_PLUGIN) - return QBrush(Qt::darkBlue); - } else if (column == COL_VERSION) { - if (!modInfo->getNewestVersion().isValid()) { - return QVariant(); - } else if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { - return QBrush(Qt::red); - } else { - return QBrush(Qt::darkGreen); - } - } + } + } else { + return tr("invalid"); + } + } else if ((role == Qt::CheckStateRole) && (column == 0)) { + if (modInfo->canBeEnabled()) { + return m_Profile->modEnabled(modIndex) ? Qt::Checked : Qt::Unchecked; + } else { + return QVariant(); + } + } else if (role == Qt::TextAlignmentRole) { + if (column == COL_NAME) { + if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_CENTER) { + return QVariant(Qt::AlignCenter | Qt::AlignVCenter); + } else { + return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + } + } else if (column == COL_VERSION) { + return QVariant(Qt::AlignRight | Qt::AlignVCenter); + } else { + return QVariant(Qt::AlignCenter | Qt::AlignVCenter); + } + } else if (role == Qt::UserRole) { + if (column == COL_CATEGORY) { + QVariantList categoryNames; + 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))); + } + if (categoryNames.count() != 0) { + return categoryNames; + } else { return QVariant(); - } else if ((role == Qt::BackgroundRole) || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { - if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) { - return QColor(0, 0, 255, 32); - } else if (m_Overwrite.find(modIndex) != m_Overwrite.end()) { - return QColor(0, 255, 0, 32); - } else if (m_Overwritten.find(modIndex) != m_Overwritten.end()) { - return QColor(255, 0, 0, 32); - } else { - return QVariant(); + } + } else if (column == COL_PRIORITY) { + int priority = modInfo->getFixedPriority(); + if (priority != INT_MIN) { + return priority; + } else { + return m_Profile->getModPriority(modIndex); + } + } else { + return modInfo->getNexusID(); + } + } else if (role == Qt::UserRole + 1) { + return modIndex; + } else if (role == Qt::UserRole + 2) { + 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 == Qt::UserRole + 3) { + return contentsToIcons(modInfo->getContents()); + } else if (role == Qt::FontRole) { + QFont result; + if (column == COL_NAME) { + if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_INVALID) { + result.setItalic(true); + } + } else if ((column == COL_CATEGORY) && (modInfo->hasFlag(ModInfo::FLAG_FOREIGN))) { + result.setItalic(true); + } else if (column == COL_VERSION) { + if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { + result.setWeight(QFont::Bold); + } + } + return result; + } else if (role == Qt::DecorationRole) { + if (column == COL_VERSION) { + if (modInfo->updateAvailable()) { + return QIcon(":/MO/gui/update_available"); + } else if (modInfo->downgradeAvailable()) { + return QIcon(":/MO/gui/warning"); + } else if (modInfo->getVersion().scheme() == VersionInfo::SCHEME_DATE) { + return QIcon(":/MO/gui/version_date"); + } + } + return QVariant(); + } 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) return QBrush(Qt::darkGray); + else if (highlight & ModInfo::HIGHLIGHT_PLUGIN) return QBrush(Qt::darkBlue); + } else if (column == COL_VERSION) { + if (!modInfo->getNewestVersion().isValid()) { + return QVariant(); + } else if (modInfo->updateAvailable() || modInfo->downgradeAvailable()) { + return QBrush(Qt::red); + } else { + return QBrush(Qt::darkGreen); + } + } + return QVariant(); + } else if ((role == Qt::BackgroundRole) + || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { + if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_PLUGIN) { + return QColor(0, 0, 255, 32); + } else if (m_Overwrite.find(modIndex) != m_Overwrite.end()) { + return QColor(0, 255, 0, 32); + } else if (m_Overwritten.find(modIndex) != m_Overwritten.end()) { + return QColor(255, 0, 0, 32); + } else { + return QVariant(); + } + } else if (role == Qt::ToolTipRole) { + if (column == COL_FLAGS) { + QString result; + + for (ModInfo::EFlag flag : modInfo->getFlags()) { + if (result.length() != 0) result += "
    "; + result += getFlagText(flag, modInfo); + } + + return result; + } else if (column == COL_CONTENT) { + return contentsToToolTip(modInfo->getContents()); + } else if (column == COL_NAME) { + try { + return modInfo->getDescription(); + } catch (const std::exception &e) { + qCritical("invalid mod description: %s", e.what()); + return QString(); + } + } else if (column == COL_VERSION) { + QString text = tr("installed version: \"%1\", newest version: \"%2\"").arg(modInfo->getVersion().displayString()).arg(modInfo->getNewestVersion().displayString()); + if (modInfo->downgradeAvailable()) { + text += "
    " + tr("The newest version on Nexus seems to be older than the one you have installed. This could either mean the version you have has been withdrawn " + "(i.e. due to a bug) or the author uses a non-standard versioning scheme and that newest version is actually newer. " + "Either way you may want to \"upgrade\"."); + } + return text; + } else if (column == COL_CATEGORY) { + const std::set &categories = modInfo->getCategories(); + std::wostringstream categoryString; + categoryString << ToWString(tr("Categories:
    ")); + CategoryFactory &categoryFactory = CategoryFactory::instance(); + for (std::set::const_iterator catIter = categories.begin(); + catIter != categories.end(); ++catIter) { + if (catIter != categories.begin()) { + categoryString << " , "; } - } else if (role == Qt::ToolTipRole) { - if (column == COL_FLAGS) { - QString result; - - for (ModInfo::EFlag flag : modInfo->getFlags()) { - if (result.length() != 0) - result += "
    "; - result += getFlagText(flag, modInfo); - } - - return result; - } else if (column == COL_CONTENT) { - return contentsToToolTip(modInfo->getContents()); - } else if (column == COL_NAME) { - try { - return modInfo->getDescription(); - } catch (const std::exception& e) { - qCritical("invalid mod description: %s", e.what()); - return QString(); - } - } else if (column == COL_VERSION) { - QString text = tr("installed version: \"%1\", newest version: \"%2\"") - .arg(modInfo->getVersion().displayString()) - .arg(modInfo->getNewestVersion().displayString()); - if (modInfo->downgradeAvailable()) { - text += "
    " + tr("The newest version on Nexus seems to be older than the one you have installed. " - "This could either mean the version you have has been withdrawn " - "(i.e. due to a bug) or the author uses a non-standard versioning scheme and that " - "newest version is actually newer. " - "Either way you may want to \"upgrade\"."); - } - return text; - } else if (column == COL_CATEGORY) { - const std::set& categories = modInfo->getCategories(); - std::wostringstream categoryString; - categoryString << ToWString(tr("Categories:
    ")); - 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) { - qCritical("failed to generate tooltip: %s", e.what()); - return QString(); - } - } - - return ToQString(categoryString.str()); - } else { - return QVariant(); + try { + categoryString << "" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << ""; + } catch (const std::exception &e) { + qCritical("failed to generate tooltip: %s", e.what()); + return QString(); } + } + + return ToQString(categoryString.str()); } else { - return QVariant(); + return QVariant(); } + } else { + return QVariant(); + } } -bool ModList::renameMod(int index, const QString& newName) { - QString nameFixed = newName; - if (!fixDirectoryName(nameFixed) || nameFixed.isEmpty()) { - MessageDialog::showMessage(tr("Invalid name"), nullptr); - return false; - } - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - QString oldName = modInfo->name(); - if (newName != 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 - // and update the modlists in all profiles - emit modRenamed(oldName, nameFixed); - } +bool ModList::renameMod(int index, const QString &newName) +{ + QString nameFixed = newName; + if (!fixDirectoryName(nameFixed) || nameFixed.isEmpty()) { + MessageDialog::showMessage(tr("Invalid name"), nullptr); + return false; + } - // invalidate the currently displayed state of this list - notifyChange(-1); - return true; + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + QString oldName = modInfo->name(); + if (newName != 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 + // and update the modlists in all profiles + emit modRenamed(oldName, nameFixed); + } + + // invalidate the currently displayed state of this list + notifyChange(-1); + return true; } -bool ModList::setData(const QModelIndex& index, const QVariant& value, int role) { - if (m_Profile == nullptr) - return false; +bool ModList::setData(const QModelIndex &index, const QVariant &value, int role) +{ + if (m_Profile == nullptr) return false; - if (static_cast(index.row()) >= ModInfo::getNumMods()) { - return false; - } + if (static_cast(index.row()) >= ModInfo::getNumMods()) { + return false; + } - int modID = index.row(); + int modID = index.row(); - ModInfo::Ptr info = ModInfo::getByIndex(modID); - IModList::ModStates oldState = state(modID); + ModInfo::Ptr info = ModInfo::getByIndex(modID); + IModList::ModStates oldState = state(modID); - bool result = false; + bool result = false; - emit aboutToChangeData(); + emit aboutToChangeData(); - if (role == Qt::CheckStateRole) { - bool enabled = value.toInt() == Qt::Checked; - if (m_Profile->modEnabled(modID) != enabled) { - m_Profile->setModEnabled(modID, enabled); - m_Modified = true; - m_LastCheck.restart(); - emit modlist_changed(index, role); + if (role == Qt::CheckStateRole) { + bool enabled = value.toInt() == Qt::Checked; + if (m_Profile->modEnabled(modID) != enabled) { + m_Profile->setModEnabled(modID, enabled); + m_Modified = true; + m_LastCheck.restart(); + emit modlist_changed(index, role); + } + result = true; + emit dataChanged(index, index); + } else if (role == Qt::EditRole) { + switch (index.column()) { + case COL_NAME: { + result = renameMod(modID, value.toString()); + } break; + case COL_PRIORITY: { + bool ok = false; + int newPriority = value.toInt(&ok); + if (ok) { + m_Profile->setModPriority(modID, newPriority); + + emit modorder_changed(); + result = true; + } else { + result = false; } - result = true; - emit dataChanged(index, index); - } else if (role == Qt::EditRole) { - switch (index.column()) { - case COL_NAME: { - result = renameMod(modID, value.toString()); - } break; - case COL_PRIORITY: { - bool ok = false; - int newPriority = value.toInt(&ok); - if (ok) { - m_Profile->setModPriority(modID, newPriority); - - emit modorder_changed(); - result = true; - } else { - result = false; - } - } break; - case COL_MODID: { - bool ok = false; - int newID = value.toInt(&ok); - if (ok) { - info->setNexusID(newID); - emit modlist_changed(index, role); - result = true; - } else { - result = false; - } - } break; - case COL_VERSION: { - VersionInfo::VersionScheme scheme = info->getVersion().scheme(); - VersionInfo version(value.toString(), scheme, true); - if (version.isValid()) { - info->setVersion(version); - result = true; - } else { - result = false; - } - } break; - default: { - qWarning("edit on column \"%s\" not supported", getColumnName(index.column()).toUtf8().constData()); - result = false; - } break; + } break; + case COL_MODID: { + bool ok = false; + int newID = value.toInt(&ok); + if (ok) { + info->setNexusID(newID); + emit modlist_changed(index, role); + result = true; + } else { + result = false; } - if (result) { - emit dataChanged(index, index); + } break; + case COL_VERSION: { + VersionInfo::VersionScheme scheme = info->getVersion().scheme(); + VersionInfo version(value.toString(), scheme, true); + if (version.isValid()) { + info->setVersion(version); + result = true; + } else { + result = false; } + } break; + default: { + qWarning("edit on column \"%s\" not supported", + getColumnName(index.column()).toUtf8().constData()); + result = false; + } break; } + if (result) { + emit dataChanged(index, index); + } + } - emit postDataChanged(); + emit postDataChanged(); - IModList::ModStates newState = state(modID); - if (oldState != newState) { - try { - m_ModStateChanged(info->name(), newState); - } catch (const std::exception& e) { - qCritical("failed to invoke state changed notification: %s", e.what()); - } catch (...) { - qCritical("failed to invoke state changed notification: unknown exception"); - } + IModList::ModStates newState = state(modID); + if (oldState != newState) { + try { + m_ModStateChanged(info->name(), newState); + } catch (const std::exception &e) { + qCritical("failed to invoke state changed notification: %s", e.what()); + } catch (...) { + qCritical("failed to invoke state changed notification: unknown exception"); } + } - return result; + return result; } -QVariant ModList::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); - } else if (role == Qt::TextAlignmentRole) { - return QVariant(Qt::AlignCenter); - } else if (role == Qt::SizeHintRole) { - QSize temp = m_FontMetrics.size(Qt::TextSingleLine, getColumnName(section)); - temp.rwidth() += 20; - temp.rheight() += 12; - return temp; - } + +QVariant ModList::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); + } else if (role == Qt::TextAlignmentRole) { + return QVariant(Qt::AlignCenter); + } else if (role == Qt::SizeHintRole) { + QSize temp = m_FontMetrics.size(Qt::TextSingleLine, getColumnName(section)); + temp.rwidth() += 20; + temp.rheight() += 12; + return temp; } - return QAbstractItemModel::headerData(section, orientation, role); + } + return QAbstractItemModel::headerData(section, orientation, role); } -Qt::ItemFlags ModList::flags(const QModelIndex& modelIndex) const { - Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex); - if (modelIndex.internalId() < 0) { - return Qt::ItemIsEnabled; + +Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const +{ + Qt::ItemFlags result = QAbstractItemModel::flags(modelIndex); + if (modelIndex.internalId() < 0) { + return Qt::ItemIsEnabled; + } + if (modelIndex.isValid()) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modelIndex.row()); + if (modInfo->getFixedPriority() == INT_MIN) { + result |= Qt::ItemIsDragEnabled; + result |= Qt::ItemIsUserCheckable; + if ((modelIndex.column() == COL_NAME) || + (modelIndex.column() == COL_PRIORITY) || + (modelIndex.column() == COL_VERSION) || + (modelIndex.column() == COL_MODID)) { + result |= Qt::ItemIsEditable; + } } - if (modelIndex.isValid()) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modelIndex.row()); - if (modInfo->getFixedPriority() == INT_MIN) { - result |= Qt::ItemIsDragEnabled; - result |= Qt::ItemIsUserCheckable; - if ((modelIndex.column() == COL_NAME) || (modelIndex.column() == COL_PRIORITY) || - (modelIndex.column() == COL_VERSION) || (modelIndex.column() == COL_MODID)) { - result |= Qt::ItemIsEditable; - } - } - std::vector flags = modInfo->getFlags(); - if (m_DropOnItems && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end())) { - result |= Qt::ItemIsDropEnabled; - } - } else { - if (!m_DropOnItems) - result |= Qt::ItemIsDropEnabled; + std::vector flags = modInfo->getFlags(); + if (m_DropOnItems + && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end())) { + result |= Qt::ItemIsDropEnabled; } - return result; + } else { + if (!m_DropOnItems) result |= Qt::ItemIsDropEnabled; + } + return result; } -QStringList ModList::mimeTypes() const { - QStringList result = QAbstractItemModel::mimeTypes(); - result.append("text/uri-list"); - return result; + +QStringList ModList::mimeTypes() const +{ + QStringList result = QAbstractItemModel::mimeTypes(); + result.append("text/uri-list"); + return result; } -QMimeData* ModList::mimeData(const QModelIndexList& indexes) const { - QMimeData* result = QAbstractItemModel::mimeData(indexes); - result->setData("text/plain", "mod"); - return result; +QMimeData *ModList::mimeData(const QModelIndexList &indexes) const +{ + QMimeData *result = QAbstractItemModel::mimeData(indexes); + result->setData("text/plain", "mod"); + return result; } -void ModList::changeModPriority(std::vector sourceIndices, int newPriority) { - if (m_Profile == nullptr) - return; - - emit layoutAboutToBeChanged(); - Profile* profile = m_Profile; - // sort rows to insert by their old priority (ascending) and insert them move them in that order - std::sort(sourceIndices.begin(), sourceIndices.end(), [profile](const int& LHS, const int& RHS) { - return profile->getModPriority(LHS) < profile->getModPriority(RHS); - }); - - // odd stuff: if any of the dragged sources has priority lower than the destination then the - // target idx is that of the row BELOW the dropped location, otherwise it's the one above. why? - for (std::vector::const_iterator iter = sourceIndices.begin(); iter != sourceIndices.end(); ++iter) { - if (profile->getModPriority(*iter) < newPriority) { - --newPriority; - break; - } - } - for (std::vector::const_iterator iter = sourceIndices.begin(); iter != sourceIndices.end(); ++iter) { - int oldPriority = m_Profile->getModPriority(*iter); - m_Profile->setModPriority(*iter, newPriority); - m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority); +void ModList::changeModPriority(std::vector sourceIndices, int newPriority) +{ + if (m_Profile == nullptr) return; + + emit layoutAboutToBeChanged(); + Profile *profile = m_Profile; + // sort rows to insert by their old priority (ascending) and insert them move them in that order + std::sort(sourceIndices.begin(), sourceIndices.end(), + [profile](const int &LHS, const int &RHS) { + return profile->getModPriority(LHS) < profile->getModPriority(RHS); + }); + + // odd stuff: if any of the dragged sources has priority lower than the destination then the + // target idx is that of the row BELOW the dropped location, otherwise it's the one above. why? + for (std::vector::const_iterator iter = sourceIndices.begin(); + iter != sourceIndices.end(); ++iter) { + if (profile->getModPriority(*iter) < newPriority) { + --newPriority; + break; } + } + for (std::vector::const_iterator iter = sourceIndices.begin(); + iter != sourceIndices.end(); ++iter) { + int oldPriority = m_Profile->getModPriority(*iter); + m_Profile->setModPriority(*iter, newPriority); + m_ModMoved(ModInfo::getByIndex(*iter)->name(), oldPriority, newPriority); + } - emit layoutChanged(); + emit layoutChanged(); - emit modorder_changed(); + emit modorder_changed(); } -void ModList::changeModPriority(int sourceIndex, int newPriority) { - if (m_Profile == nullptr) - return; - emit layoutAboutToBeChanged(); - m_Profile->setModPriority(sourceIndex, newPriority); +void ModList::changeModPriority(int sourceIndex, int newPriority) +{ + if (m_Profile == nullptr) return; + emit layoutAboutToBeChanged(); - emit layoutChanged(); + m_Profile->setModPriority(sourceIndex, newPriority); - emit modorder_changed(); -} + emit layoutChanged(); -void ModList::setOverwriteMarkers(const std::set& overwrite, const std::set& overwritten) { - m_Overwrite = overwrite; - m_Overwritten = overwritten; - notifyChange(0, rowCount() - 1); + emit modorder_changed(); } -bool ModList::modInfoAboutToChange(ModInfo::Ptr info) { - if (m_ChangeInfo.name.isEmpty()) { - m_ChangeInfo.name = info->name(); - m_ChangeInfo.state = state(info->name()); - return true; - } else { - return false; - } +void ModList::setOverwriteMarkers(const std::set &overwrite, const std::set &overwritten) +{ + m_Overwrite = overwrite; + m_Overwritten = overwritten; + notifyChange(0, rowCount() - 1); } -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); - } +bool ModList::modInfoAboutToChange(ModInfo::Ptr info) +{ + if (m_ChangeInfo.name.isEmpty()) { + m_ChangeInfo.name = info->name(); + m_ChangeInfo.state = state(info->name()); + return true; + } else { + return false; + } +} - int row = ModInfo::getIndex(info->name()); - info->testValid(); - emit dataChanged(index(row, 0), index(row, columnCount())); - } else { - qCritical("modInfoChanged not called after modInfoAboutToChange"); +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_ChangeInfo.name = QString(); + + int row = ModInfo::getIndex(info->name()); + info->testValid(); + emit dataChanged(index(row, 0), index(row, columnCount())); + } else { + qCritical("modInfoChanged not called after modInfoAboutToChange"); + } + m_ChangeInfo.name = QString(); } void ModList::disconnectSlots() { - m_ModMoved.disconnect_all_slots(); - m_ModStateChanged.disconnect_all_slots(); + m_ModMoved.disconnect_all_slots(); + m_ModStateChanged.disconnect_all_slots(); } -int ModList::timeElapsedSinceLastChecked() const { return m_LastCheck.elapsed(); } - -void ModList::highlightMods(const QItemSelection& selected, const MOShared::DirectoryEntry& directoryEntry) { - for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { - ModInfo::getByIndex(i)->setPluginSelected(false); - } - for (QModelIndex idx : selected.indexes()) { - QString modName = idx.data().toString(); - - const MOShared::FileEntry::Ptr fileEntry = directoryEntry.findFile(modName.toStdWString()); - if (fileEntry.get() != nullptr) { - QString fileName; - bool archive = false; - std::vector> origins; - { - std::vector> alternatives = fileEntry->getAlternatives(); - origins.insert(origins.end(), - std::pair(fileEntry->getOrigin(archive), fileEntry->getArchive())); - } - for (auto originInfo : origins) { - MOShared::FilesOrigin& origin = directoryEntry.getOriginByID(originInfo.first); - for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { - if (ModInfo::getByIndex(i)->internalName() == QString::fromStdWString(origin.getName())) { - ModInfo::getByIndex(i)->setPluginSelected(true); - break; - } - } - } - } - } - notifyChange(0, rowCount() - 1); +int ModList::timeElapsedSinceLastChecked() const +{ + return m_LastCheck.elapsed(); } -IModList::ModStates ModList::state(unsigned int modIndex) const { - IModList::ModStates result; - if (modIndex != UINT_MAX) { - result |= IModList::STATE_EXISTS; - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if (modInfo->isEmpty()) { - result |= IModList::STATE_EMPTY; - } - if (modInfo->endorsedState() == ModInfo::ENDORSED_TRUE) { - result |= IModList::STATE_ENDORSED; - } - if (modInfo->isValid()) { - result |= IModList::STATE_VALID; - } - if (modInfo->canBeEnabled()) { - if (m_Profile->modEnabled(modIndex)) { - result |= IModList::STATE_ACTIVE; - } - } else { - result |= IModList::STATE_ESSENTIAL; +void ModList::highlightMods(const QItemSelection &selected, const MOShared::DirectoryEntry &directoryEntry) +{ + for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { + ModInfo::getByIndex(i)->setPluginSelected(false); + } + for (QModelIndex idx : selected.indexes()) { + QString modName = idx.data().toString(); + + const MOShared::FileEntry::Ptr fileEntry = directoryEntry.findFile(modName.toStdWString()); + if (fileEntry.get() != nullptr) { + QString fileName; + bool archive = false; + std::vector> origins; + { + std::vector> alternatives = fileEntry->getAlternatives(); + origins.insert(origins.end(), std::pair(fileEntry->getOrigin(archive), fileEntry->getArchive())); + } + for (auto originInfo : origins) { + MOShared::FilesOrigin &origin = directoryEntry.getOriginByID(originInfo.first); + for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { + if (ModInfo::getByIndex(i)->internalName() == QString::fromStdWString(origin.getName())) { + ModInfo::getByIndex(i)->setPluginSelected(true); + break; + } } + } } - return result; + } + notifyChange(0, rowCount() - 1); } -QString ModList::displayName(const QString& internalName) const { - unsigned int modIndex = ModInfo::getIndex(internalName); - if (modIndex == UINT_MAX) { - // might be better to throw an exception? - return internalName; +IModList::ModStates ModList::state(unsigned int modIndex) const +{ + IModList::ModStates result; + if (modIndex != UINT_MAX) { + result |= IModList::STATE_EXISTS; + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + if (modInfo->isEmpty()) { + result |= IModList::STATE_EMPTY; + } + if (modInfo->endorsedState() == ModInfo::ENDORSED_TRUE) { + result |= IModList::STATE_ENDORSED; + } + if (modInfo->isValid()) { + result |= IModList::STATE_VALID; + } + if (modInfo->canBeEnabled()) { + if (m_Profile->modEnabled(modIndex)) { + result |= IModList::STATE_ACTIVE; + } } else { - return ModInfo::getByIndex(modIndex)->name(); + result |= IModList::STATE_ESSENTIAL; } + } + return result; } -QStringList ModList::allMods() const { - QStringList result; - for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { - result.append(ModInfo::getByIndex(i)->internalName()); - } - return result; +QString ModList::displayName(const QString &internalName) const +{ + unsigned int modIndex = ModInfo::getIndex(internalName); + if (modIndex == UINT_MAX) { + // might be better to throw an exception? + return internalName; + } else { + return ModInfo::getByIndex(modIndex)->name(); + } +} + +QStringList ModList::allMods() const +{ + QStringList result; + for (unsigned int i = 0; i < ModInfo::getNumMods(); ++i) { + result.append(ModInfo::getByIndex(i)->internalName()); + } + return result; } -IModList::ModStates ModList::state(const QString& name) const { - unsigned int modIndex = ModInfo::getIndex(name); +IModList::ModStates ModList::state(const QString &name) const +{ + unsigned int modIndex = ModInfo::getIndex(name); - return state(modIndex); + return state(modIndex); } -bool ModList::setActive(const QString& name, bool active) { - unsigned int modIndex = ModInfo::getIndex(name); - if (modIndex == UINT_MAX) { - return false; - } else { - m_Profile->setModEnabled(modIndex, active); +bool ModList::setActive(const QString &name, bool active) +{ + unsigned int modIndex = ModInfo::getIndex(name); + if (modIndex == UINT_MAX) { + return false; + } else { + m_Profile->setModEnabled(modIndex, active); - IModList::ModStates newState = state(modIndex); - m_ModStateChanged(name, newState); - return true; - } + IModList::ModStates newState = state(modIndex); + m_ModStateChanged(name, newState); + return true; + } } -int ModList::priority(const QString& name) const { - unsigned int modIndex = ModInfo::getIndex(name); - if (modIndex == UINT_MAX) { - return -1; - } else { - return m_Profile->getModPriority(modIndex); - } +int ModList::priority(const QString &name) const +{ + unsigned int modIndex = ModInfo::getIndex(name); + if (modIndex == UINT_MAX) { + return -1; + } else { + return m_Profile->getModPriority(modIndex); + } } -bool ModList::setPriority(const QString& name, int newPriority) { - if ((newPriority < 0) || (newPriority >= static_cast(m_Profile->numRegularMods()))) { - return false; - } +bool ModList::setPriority(const QString &name, int newPriority) +{ + if ((newPriority < 0) || (newPriority >= static_cast(m_Profile->numRegularMods()))) { + return false; + } - unsigned int modIndex = ModInfo::getIndex(name); - if (modIndex == UINT_MAX) { - return false; - } else { - m_Profile->setModPriority(modIndex, newPriority); - notifyChange(modIndex); - return true; - } + unsigned int modIndex = ModInfo::getIndex(name); + if (modIndex == UINT_MAX) { + return false; + } else { + m_Profile->setModPriority(modIndex, newPriority); + notifyChange(modIndex); + return true; + } } -bool ModList::onModStateChanged(const std::function& func) { - auto conn = m_ModStateChanged.connect(func); - return conn.connected(); +bool ModList::onModStateChanged(const std::function &func) +{ + auto conn = m_ModStateChanged.connect(func); + return conn.connected(); } -bool ModList::onModMoved(const std::function& func) { - auto conn = m_ModMoved.connect(func); - return conn.connected(); +bool ModList::onModMoved(const std::function &func) +{ + auto conn = m_ModMoved.connect(func); + return conn.connected(); } -bool ModList::dropURLs(const QMimeData* mimeData, int row, const QModelIndex& parent) { - QStringList source; - QStringList target; +bool ModList::dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent) +{ + QStringList source; + QStringList target; - if (row == -1) { - row = parent.row(); - } + if (row == -1) { + row = parent.row(); + } - ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - QDir modDirectory(modInfo->absolutePath()); - QDir gameDirectory(Settings::instance().getOverwriteDirectory()); + ModInfo::Ptr modInfo = ModInfo::getByIndex(row); + QDir modDirectory(modInfo->absolutePath()); + QDir gameDirectory(Settings::instance().getOverwriteDirectory()); - unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { - std::vector flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); - }); + unsigned int overwriteIndex = ModInfo::findMod([] (ModInfo::Ptr mod) -> bool { + std::vector flags = mod->getFlags(); + return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); - QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name(); + QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name(); - for (const QUrl& url : mimeData->urls()) { - if (!url.isLocalFile()) { - continue; - } - QString relativePath = gameDirectory.relativeFilePath(url.toLocalFile()); - if (relativePath.startsWith("..")) { - qDebug("%s drop ignored", qPrintable(url.toLocalFile())); - continue; - } - source.append(url.toLocalFile()); - target.append(modDirectory.absoluteFilePath(relativePath)); - emit fileMoved(relativePath, overwriteName, modInfo->name()); + for (const QUrl &url : mimeData->urls()) { + if (!url.isLocalFile()) { + continue; } - - if (source.count() != 0) { - shellMove(source, target); + QString relativePath = gameDirectory.relativeFilePath(url.toLocalFile()); + if (relativePath.startsWith("..")) { + qDebug("%s drop ignored", qPrintable(url.toLocalFile())); + continue; } + source.append(url.toLocalFile()); + target.append(modDirectory.absoluteFilePath(relativePath)); + emit fileMoved(relativePath, overwriteName, modInfo->name()); + } - return true; -} + if (source.count() != 0) { + shellMove(source, target); + } -bool ModList::dropMod(const QMimeData* mimeData, int row, const QModelIndex& parent) { + return true; +} - try { - 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) { - sourceRows.push_back(sourceRow); - } - } +bool ModList::dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent) +{ + + try { + 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) { + sourceRows.push_back(sourceRow); + } + } - if (row == -1) { - row = parent.row(); - } + if (row == -1) { + row = parent.row(); + } - if ((row < 0) || (static_cast(row) >= ModInfo::getNumMods())) { - return false; - } + if ((row < 0) || (static_cast(row) >= ModInfo::getNumMods())) { + return false; + } - int newPriority = 0; - { - if ((row < 0) || (row > static_cast(m_Profile->numRegularMods()))) { - newPriority = m_Profile->numRegularMods() + 1; - } else { - newPriority = m_Profile->getModPriority(row); - } - if (newPriority == -1) { - newPriority = m_Profile->numRegularMods() + 1; - } - } - changeModPriority(sourceRows, newPriority); - } catch (const std::exception& e) { - reportError(tr("drag&drop failed: %1").arg(e.what())); + int newPriority = 0; + { + if ((row < 0) || (row > static_cast(m_Profile->numRegularMods()))) { + newPriority = m_Profile->numRegularMods() + 1; + } else { + newPriority = m_Profile->getModPriority(row); + } + if (newPriority == -1) { + newPriority = m_Profile->numRegularMods() + 1; + } } + changeModPriority(sourceRows, newPriority); + } catch (const std::exception &e) { + reportError(tr("drag&drop failed: %1").arg(e.what())); + } - return false; + return false; } -bool ModList::dropMimeData(const QMimeData* mimeData, Qt::DropAction action, int row, int, const QModelIndex& parent) { - if (action == Qt::IgnoreAction) { - return true; - } - if (m_Profile == nullptr) - return false; +bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent) +{ + if (action == Qt::IgnoreAction) { + return true; + } - if (mimeData->hasUrls()) { - return dropURLs(mimeData, row, parent); - } else if (mimeData->hasText()) { - return dropMod(mimeData, row, parent); - } else { - return false; - } + if (m_Profile == nullptr) return false; + + if (mimeData->hasUrls()) { + return dropURLs(mimeData, row, parent); + } else if (mimeData->hasText()) { + return dropMod(mimeData, row, parent); + } else { + return false; + } } -void ModList::removeRowForce(int row, const QModelIndex& parent) { - if (static_cast(row) >= ModInfo::getNumMods()) { - return; - } - if (m_Profile == nullptr) - return; +void ModList::removeRowForce(int row, const QModelIndex &parent) +{ + if (static_cast(row) >= ModInfo::getNumMods()) { + return; + } + if (m_Profile == nullptr) return; - ModInfo::Ptr modInfo = ModInfo::getByIndex(row); + ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - bool wasEnabled = m_Profile->modEnabled(row); + bool wasEnabled = m_Profile->modEnabled(row); - m_Profile->setModEnabled(row, false); + m_Profile->setModEnabled(row, false); - m_Profile->cancelModlistWrite(); - beginRemoveRows(parent, row, row); - ModInfo::removeMod(row); - endRemoveRows(); - m_Profile->refreshModStatus(); // removes the mod from the status list - m_Profile->writeModlist(); // this ensures the modified list gets written back before new mods can be installed - - if (wasEnabled) { - emit removeOrigin(modInfo->name()); - } + m_Profile->cancelModlistWrite(); + beginRemoveRows(parent, row, row); + ModInfo::removeMod(row); + endRemoveRows(); + m_Profile->refreshModStatus(); // removes the mod from the status list + m_Profile->writeModlist(); // this ensures the modified list gets written back before new mods can be installed + + if (wasEnabled) { + emit removeOrigin(modInfo->name()); + } - emit modUninstalled(modInfo->getInstallationFile()); + emit modUninstalled(modInfo->getInstallationFile()); } -bool ModList::removeRows(int row, int count, const QModelIndex& parent) { - if (static_cast(row) >= ModInfo::getNumMods()) { - return false; - } - if (m_Profile == nullptr) { - return false; - } +bool ModList::removeRows(int row, int count, const QModelIndex &parent) +{ + if (static_cast(row) >= ModInfo::getNumMods()) { + return false; + } + if (m_Profile == nullptr) { + return false; + } - bool success = false; + bool success = false; - for (int i = 0; i < count; ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(row + i); - if (!modInfo->isRegular()) { - continue; - } + for (int i = 0; i < count; ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(row + i); + if (!modInfo->isRegular()) { + continue; + } - success = true; + success = true; - QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), - tr("Are you sure you want to remove \"%1\"?").arg(modInfo->name()), - QMessageBox::Yes | QMessageBox::No); + QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), + tr("Are you sure you want to remove \"%1\"?").arg(modInfo->name()), + QMessageBox::Yes | QMessageBox::No); - if (confirmBox.exec() == QMessageBox::Yes) { - m_Profile->setModEnabled(row + i, false); - removeRowForce(row + i, parent); - } + if (confirmBox.exec() == QMessageBox::Yes) { + m_Profile->setModEnabled(row + i, false); + removeRowForce(row + i, parent); } + } - return success; + return success; } -void ModList::notifyChange(int rowStart, int rowEnd) { - if (rowStart < 0) { - m_Overwrite.clear(); - m_Overwritten.clear(); - beginResetModel(); - endResetModel(); - } else { - if (rowEnd == -1) { - rowEnd = rowStart; - } - emit dataChanged(this->index(rowStart, 0), this->index(rowEnd, this->columnCount() - 1)); + +void ModList::notifyChange(int rowStart, int rowEnd) +{ + if (rowStart < 0) { + m_Overwrite.clear(); + m_Overwritten.clear(); + beginResetModel(); + endResetModel(); + } else { + if (rowEnd == -1) { + rowEnd = rowStart; } + 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())) { - return QModelIndex(); - } - QModelIndex res = createIndex(row, column, row); - return res; + +QModelIndex ModList::index(int row, int column, const QModelIndex&) const +{ + if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) { + return QModelIndex(); + } + QModelIndex res = createIndex(row, column, row); + return res; } -QModelIndex ModList::parent(const QModelIndex&) const { return QModelIndex(); } -QMap ModList::itemData(const QModelIndex& index) const { - QMap result = QAbstractItemModel::itemData(index); - result[Qt::UserRole] = data(index, Qt::UserRole); - return result; +QModelIndex ModList::parent(const QModelIndex&) const +{ + return QModelIndex(); } -void ModList::dropModeUpdate(bool dropOnItems) { - if (m_DropOnItems != dropOnItems) { - m_DropOnItems = dropOnItems; - } +QMap ModList::itemData(const QModelIndex &index) const +{ + QMap result = QAbstractItemModel::itemData(index); + result[Qt::UserRole] = data(index, Qt::UserRole); + return result; } -QString ModList::getColumnName(int column) { - switch (column) { - 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_MODID: - return tr("Nexus ID"); - case COL_INSTALLTIME: - return tr("Installation"); - default: - return tr("unknown"); - } + +void ModList::dropModeUpdate(bool dropOnItems) +{ + if (m_DropOnItems != dropOnItems) { + m_DropOnItems = dropOnItems; + } } -QString ModList::getColumnToolTip(int column) { - 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("Category of the mod."); - case COL_MODID: - return tr("Id of the mod as used on Nexus."); - case COL_FLAGS: - return tr("Emblemes to highlight things that might require attention."); - case COL_CONTENT: - return tr("Depicts the content of the mod:
    " - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "" - "
    Game plugins (esp/esm/esl)
    Interface
    Meshes
    BSA
    Textures
    Sounds
    Music
    Strings
    Scripts (Papyrus)
    Script Extender plugins
    SkyProc Patcher
    Mod Configuration Menu
    "); - case COL_INSTALLTIME: - return tr("Time this mod was installed"); - default: - return tr("unknown"); - } + +QString ModList::getColumnName(int column) +{ + switch (column) { + 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_MODID: return tr("Nexus ID"); + case COL_INSTALLTIME: return tr("Installation"); + default: return tr("unknown"); + } +} + + +QString ModList::getColumnToolTip(int column) +{ + 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("Category of the mod."); + case COL_MODID: return tr("Id of the mod as used on Nexus."); + case COL_FLAGS: return tr("Emblemes to highlight things that might require attention."); + case COL_CONTENT: return tr("Depicts the content of the mod:
    " + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "" + "
    Game plugins (esp/esm/esl)
    Interface
    Meshes
    BSA
    Textures
    Sounds
    Music
    Strings
    Scripts (Papyrus)
    Script Extender plugins
    SkyProc Patcher
    Mod Configuration Menu
    "); + case COL_INSTALLTIME: return tr("Time this mod was installed"); + default: return tr("unknown"); + } } -bool ModList::moveSelection(QAbstractItemView* itemView, int direction) { - QItemSelectionModel* selectionModel = itemView->selectionModel(); - const QAbstractProxyModel* proxyModel = qobject_cast(selectionModel->model()); - const QSortFilterProxyModel* filterModel = nullptr; +bool ModList::moveSelection(QAbstractItemView *itemView, int direction) +{ + QItemSelectionModel *selectionModel = itemView->selectionModel(); - while ((filterModel == nullptr) && (proxyModel != nullptr)) { - filterModel = qobject_cast(proxyModel); - if (filterModel == nullptr) { - proxyModel = qobject_cast(proxyModel->sourceModel()); - } - } + const QAbstractProxyModel *proxyModel = qobject_cast(selectionModel->model()); + const QSortFilterProxyModel *filterModel = nullptr; + + while ((filterModel == nullptr) && (proxyModel != nullptr)) { + filterModel = qobject_cast(proxyModel); if (filterModel == nullptr) { - return true; + proxyModel = qobject_cast(proxyModel->sourceModel()); } - - int offset = -1; - if (((direction < 0) && (filterModel->sortOrder() == Qt::DescendingOrder)) || - ((direction > 0) && (filterModel->sortOrder() == Qt::AscendingOrder))) { - offset = 1; + } + if (filterModel == nullptr) { + return true; + } + + int offset = -1; + if (((direction < 0) && (filterModel->sortOrder() == Qt::DescendingOrder)) || + ((direction > 0) && (filterModel->sortOrder() == Qt::AscendingOrder))) { + offset = 1; + } + + QModelIndexList rows = selectionModel->selectedRows(); + if (direction > 0) { + for (int i = 0; i < rows.size() / 2; ++i) { + rows.swap(i, rows.size() - i - 1); } - - QModelIndexList rows = selectionModel->selectedRows(); - if (direction > 0) { - for (int i = 0; i < rows.size() / 2; ++i) { - rows.swap(i, rows.size() - i - 1); - } + } + for (QModelIndex idx : rows) { + if (filterModel != nullptr) { + idx = filterModel->mapToSource(idx); } - for (QModelIndex idx : rows) { - if (filterModel != nullptr) { - idx = filterModel->mapToSource(idx); - } - int newPriority = m_Profile->getModPriority(idx.row()) + offset; - if ((newPriority >= 0) && (newPriority < static_cast(m_Profile->numRegularMods()))) { - m_Profile->setModPriority(idx.row(), newPriority); - notifyChange(idx.row()); - } + int newPriority = m_Profile->getModPriority(idx.row()) + offset; + if ((newPriority >= 0) && (newPriority < static_cast(m_Profile->numRegularMods()))) { + m_Profile->setModPriority(idx.row(), newPriority); + notifyChange(idx.row()); } - emit modorder_changed(); - return true; + } + emit modorder_changed(); + return true; } -bool ModList::deleteSelection(QAbstractItemView* itemView) { - QItemSelectionModel* selectionModel = itemView->selectionModel(); - - QModelIndexList rows = selectionModel->selectedRows(); - if (rows.count() > 1) { - emit removeSelectedMods(); - } else if (rows.count() == 1) { - removeRow(rows[0].data(Qt::UserRole + 1).toInt(), QModelIndex()); - } - return true; +bool ModList::deleteSelection(QAbstractItemView *itemView) +{ + QItemSelectionModel *selectionModel = itemView->selectionModel(); + + QModelIndexList rows = selectionModel->selectedRows(); + if (rows.count() > 1) { + emit removeSelectedMods(); + } else if (rows.count() == 1) { + removeRow(rows[0].data(Qt::UserRole + 1).toInt(), QModelIndex()); + } + return true; } -bool ModList::toggleSelection(QAbstractItemView* itemView) { - emit aboutToChangeData(); +bool ModList::toggleSelection(QAbstractItemView *itemView) +{ + emit aboutToChangeData(); - QItemSelectionModel* selectionModel = itemView->selectionModel(); + QItemSelectionModel *selectionModel = itemView->selectionModel(); - for (QModelIndex idx : selectionModel->selectedRows()) { - int modId = idx.data(Qt::UserRole + 1).toInt(); - m_Profile->setModEnabled(modId, !m_Profile->modEnabled(modId)); - emit modlist_changed(idx, 0); - } + for (QModelIndex idx : selectionModel->selectedRows()) { + int modId = idx.data(Qt::UserRole + 1).toInt(); + m_Profile->setModEnabled(modId, !m_Profile->modEnabled(modId)); + emit modlist_changed(idx, 0); + } - m_Modified = true; - m_LastCheck.restart(); + m_Modified = true; + m_LastCheck.restart(); - emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); + emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); - emit postDataChanged(); + emit postDataChanged(); - return true; + return true; } -bool ModList::eventFilter(QObject* obj, QEvent* event) { - if (event->type() == QEvent::ContextMenu) { - QContextMenuEvent* contextEvent = static_cast(event); - QWidget* object = qobject_cast(obj); - if ((object != nullptr) && (contextEvent->reason() == QContextMenuEvent::Mouse)) { - emit requestColumnSelect(object->mapToGlobal(contextEvent->pos())); +bool ModList::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::ContextMenu) { + QContextMenuEvent *contextEvent = static_cast(event); + QWidget *object = qobject_cast(obj); + if ((object != nullptr) && (contextEvent->reason() == QContextMenuEvent::Mouse)) { + emit requestColumnSelect(object->mapToGlobal(contextEvent->pos())); - return true; - } - } else if ((event->type() == QEvent::KeyPress) && (m_Profile != nullptr)) { - QAbstractItemView* itemView = qobject_cast(obj); - QKeyEvent* keyEvent = static_cast(event); - - if ((itemView != nullptr) && (keyEvent->modifiers() == Qt::ControlModifier) && - ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) { - return moveSelection(itemView, keyEvent->key() == Qt::Key_Up ? -1 : 1); - } else if (keyEvent->key() == Qt::Key_Delete) { - return deleteSelection(itemView); - } else if (keyEvent->key() == Qt::Key_Space) { - return toggleSelection(itemView); - } + return true; } - return QAbstractItemModel::eventFilter(obj, event); + } else if ((event->type() == QEvent::KeyPress) && (m_Profile != nullptr)) { + QAbstractItemView *itemView = qobject_cast(obj); + QKeyEvent *keyEvent = static_cast(event); + + if ((itemView != nullptr) + && (keyEvent->modifiers() == Qt::ControlModifier) + && ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) { + return moveSelection(itemView, keyEvent->key() == Qt::Key_Up ? -1 : 1); + } else if (keyEvent->key() == Qt::Key_Delete) { + return deleteSelection(itemView); + } else if (keyEvent->key() == Qt::Key_Space) { + return toggleSelection(itemView); + } + } + return QAbstractItemModel::eventFilter(obj, event); } + diff --git a/src/modlist.h b/src/modlist.h index 4cdfe294..bd715107 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -20,24 +20,26 @@ along with Mod Organizer. If not, see . #ifndef MODLIST_H #define MODLIST_H + #include "categories.h" -#include "modinfo.h" #include "nexusinterface.h" +#include "modinfo.h" #include "profile.h" -#include #include +#include #include #include -#include #include +#include #ifndef Q_MOC_RUN #include #endif -#include #include #include +#include + class QSortFilterProxyModel; @@ -46,278 +48,288 @@ class QSortFilterProxyModel; * 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, public MOBase::IModList { - Q_OBJECT +class ModList : public QAbstractItemModel, public MOBase::IModList +{ + Q_OBJECT public: - enum EColumn { - COL_NAME, - COL_FLAGS, - COL_CONTENT, - COL_CATEGORY, - COL_MODID, - COL_VERSION, - COL_INSTALLTIME, - COL_PRIORITY, - - COL_LASTCOLUMN = COL_PRIORITY - }; - - typedef boost::signals2::signal SignalModStateChanged; - typedef boost::signals2::signal SignalModMoved; + + enum EColumn { + COL_NAME, + COL_FLAGS, + COL_CONTENT, + COL_CATEGORY, + COL_MODID, + COL_VERSION, + COL_INSTALLTIME, + COL_PRIORITY, + + COL_LASTCOLUMN = COL_PRIORITY + }; + + typedef boost::signals2::signal SignalModStateChanged; + typedef boost::signals2::signal SignalModMoved; public: - /** - * @brief constructor - * @todo ensure this view works without a profile set, otherwise there are intransparent dependencies on the - *initialisation order - **/ - ModList(QObject* parent = nullptr); - ~ModList(); + /** + * @brief constructor + * @todo ensure this view works without a profile set, otherwise there are intransparent dependencies on the initialisation order + **/ + ModList(QObject *parent = nullptr); + + ~ModList(); - /** - * @brief set the profile used for status information - * - * @param profile the profile to use - **/ - void setProfile(Profile* profile); + /** + * @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 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); + /** + * @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 notifyChange(int rowStart, int rowEnd = -1); + static QString getColumnName(int column); - void changeModPriority(int sourceIndex, int newPriority); + void changeModPriority(int sourceIndex, int newPriority); - void setOverwriteMarkers(const std::set& overwrite, const std::set& overwritten); + void setOverwriteMarkers(const std::set &overwrite, const std::set &overwritten); - bool modInfoAboutToChange(ModInfo::Ptr info); - void modInfoChanged(ModInfo::Ptr info); + bool modInfoAboutToChange(ModInfo::Ptr info); + void modInfoChanged(ModInfo::Ptr info); - void disconnectSlots(); + void disconnectSlots(); - int timeElapsedSinceLastChecked() const; + int timeElapsedSinceLastChecked() const; - void highlightMods(const QItemSelection& selected, const MOShared::DirectoryEntry& directoryEntry); + void highlightMods(const QItemSelection &selected, const MOShared::DirectoryEntry &directoryEntry); public: - /// \copydoc MOBase::IModList::displayName - virtual QString displayName(const QString& internalName) const override; - /// \copydoc MOBase::IModList::allMods - virtual QStringList allMods() const override; + /// \copydoc MOBase::IModList::displayName + virtual QString displayName(const QString &internalName) const override; - /// \copydoc MOBase::IModList::state - virtual ModStates state(const QString& name) const override; + /// \copydoc MOBase::IModList::allMods + virtual QStringList allMods() const override; - /// \copydoc MOBase::IModList::setActive - virtual bool setActive(const QString& name, bool active) override; + /// \copydoc MOBase::IModList::state + virtual ModStates state(const QString &name) const override; - /// \copydoc MOBase::IModList::priority - virtual int priority(const QString& name) const override; + /// \copydoc MOBase::IModList::setActive + virtual bool setActive(const QString &name, bool active) override; - /// \copydoc MOBase::IModList::setPriority - virtual bool setPriority(const QString& name, int newPriority) override; + /// \copydoc MOBase::IModList::priority + virtual int priority(const QString &name) const override; - /// \copydoc MOBase::IModList::onModStateChanged - virtual bool onModStateChanged(const std::function& func) override; + /// \copydoc MOBase::IModList::setPriority + virtual bool setPriority(const QString &name, int newPriority) override; - /// \copydoc MOBase::IModList::onModMoved - virtual bool onModMoved(const std::function& func) override; + /// \copydoc MOBase::IModList::onModStateChanged + virtual bool onModStateChanged(const std::function &func) override; + + /// \copydoc MOBase::IModList::onModMoved + virtual bool onModMoved(const std::function &func) override; 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 Qt::DropActions supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } - virtual QStringList mimeTypes() const; - virtual QMimeData* mimeData(const QModelIndexList& indexes) const; - virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, - const QModelIndex& parent); - virtual bool removeRows(int row, int count, const QModelIndex& parent); - - 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; + + 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 Qt::DropActions supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } + virtual QStringList mimeTypes() const; + virtual QMimeData *mimeData(const QModelIndexList &indexes) const; + virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); + virtual bool removeRows(int row, int count, const QModelIndex &parent); + + 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 dropModeUpdate(bool dropOnItems); + void dropModeUpdate(bool dropOnItems); signals: - /** - * @brief emitted whenever the sorting in the list was changed by the user - * - * 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 modorder_changed(); - - /** - * @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 requestColumnSelect is emitted whenever the user requested a menu to select visible columns - * @param pos the position to display the menu at - */ - void requestColumnSelect(QPoint pos); - - /** - * @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 emitted whenever a row in the list has changed - * - * @param index the index of the changed field - * @param role role of the field that changed - * @note this signal must only be emitted if the row really did change. - * Slots handling this signal therefore do not have to verify that a change has happened - * @note this signal is currently only used in tutorials - **/ - void modlist_changed(const QModelIndex& index, int role); - - /** - * @brief emitted to have all selected mods deleted - */ - void removeSelectedMods(); - - /** - * @brief fileMoved emitted when a file is moved from one mod to another - * @param relativePath relative path of the file moved - * @param oldOriginName name of the origin that previously contained the file - * @param newOriginName name of the origin that now contains the file - */ - void fileMoved(const QString& relativePath, const QString& oldOriginName, const QString& newOriginName); - - void aboutToChangeData(); - - void postDataChanged(); + /** + * @brief emitted whenever the sorting in the list was changed by the user + * + * 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 modorder_changed(); + + /** + * @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 requestColumnSelect is emitted whenever the user requested a menu to select visible columns + * @param pos the position to display the menu at + */ + void requestColumnSelect(QPoint pos); + + /** + * @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 emitted whenever a row in the list has changed + * + * @param index the index of the changed field + * @param role role of the field that changed + * @note this signal must only be emitted if the row really did change. + * Slots handling this signal therefore do not have to verify that a change has happened + * @note this signal is currently only used in tutorials + **/ + void modlist_changed(const QModelIndex &index, int role); + + /** + * @brief emitted to have all selected mods deleted + */ + void removeSelectedMods(); + + /** + * @brief fileMoved emitted when a file is moved from one mod to another + * @param relativePath relative path of the file moved + * @param oldOriginName name of the origin that previously contained the file + * @param newOriginName name of the origin that now contains the file + */ + void fileMoved(const QString &relativePath, const QString &oldOriginName, const QString &newOriginName); + + void aboutToChangeData(); + + void postDataChanged(); protected: - // event filter, handles event from the header and the tree view itself - bool eventFilter(QObject* obj, QEvent* event); + + // event filter, handles event from the header and the tree view itself + bool eventFilter(QObject *obj, QEvent *event); private: - bool testValid(const QString& modDir); - void changeModPriority(std::vector sourceIndices, int newPriority); + bool testValid(const QString &modDir); - QVariant getOverwriteData(int column, int role) const; + void changeModPriority(std::vector sourceIndices, int newPriority); - QString getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const; + QVariant getOverwriteData(int column, int role) const; - static QString getColumnToolTip(int column); + QString getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const; - QVariantList contentsToIcons(const std::vector& content) const; + static QString getColumnToolTip(int column); - QString contentsToToolTip(const std::vector& contents) const; + QVariantList contentsToIcons(const std::vector &content) const; - ModList::EColumn getEnabledColumn(int index) const; + QString contentsToToolTip(const std::vector &contents) const; - QVariant categoryData(int categoryID, int column, int role) const; - QVariant modData(int modID, int modelColumn, int role) const; + ModList::EColumn getEnabledColumn(int index) const; - bool renameMod(int index, const QString& newName); + QVariant categoryData(int categoryID, int column, int role) const; + QVariant modData(int modID, int modelColumn, int role) const; - bool dropURLs(const QMimeData* mimeData, int row, const QModelIndex& parent); + bool renameMod(int index, const QString &newName); - bool dropMod(const QMimeData* mimeData, int row, const QModelIndex& parent); + bool dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent); - ModStates state(unsigned int modIndex) const; + bool dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent); - bool moveSelection(QAbstractItemView* itemView, int direction); + ModStates state(unsigned int modIndex) const; - bool deleteSelection(QAbstractItemView* itemView); + bool moveSelection(QAbstractItemView *itemView, int direction); - bool toggleSelection(QAbstractItemView* itemView); + bool deleteSelection(QAbstractItemView *itemView); + + bool toggleSelection(QAbstractItemView *itemView); private slots: 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; - }; + + 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: - Profile* m_Profile; - NexusInterface* m_NexusInterface; - std::set m_RequestIDs; + Profile *m_Profile; + + NexusInterface *m_NexusInterface; + std::set m_RequestIDs; - mutable bool m_Modified; + mutable bool m_Modified; - QFontMetrics m_FontMetrics; + QFontMetrics m_FontMetrics; - bool m_DropOnItems; + bool m_DropOnItems; - std::set m_Overwrite; - std::set m_Overwritten; + std::set m_Overwrite; + std::set m_Overwritten; - TModInfoChange m_ChangeInfo; + TModInfoChange m_ChangeInfo; - SignalModStateChanged m_ModStateChanged; - SignalModMoved m_ModMoved; + SignalModStateChanged m_ModStateChanged; + SignalModMoved m_ModMoved; - std::map> m_ContentIcons; + std::map > m_ContentIcons; + + QTime m_LastCheck; - QTime m_LastCheck; }; #endif // MODLIST_H + diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 775b3d97..435967c9 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -18,408 +18,416 @@ along with Mod Organizer. If not, see . */ #include "modlistsortproxy.h" -#include "messagedialog.h" #include "modinfo.h" #include "profile.h" +#include "messagedialog.h" #include "qtgroupingproxy.h" -#include -#include -#include #include +#include +#include +#include #include +#include #include -#include -ModListSortProxy::ModListSortProxy(Profile* profile, QObject* parent) - : QSortFilterProxyModel(parent), m_Profile(profile), m_CategoryFilter(), m_CurrentFilter(), m_FilterActive(false), - m_FilterMode(FILTER_AND) { - m_EnabledColumns.set(ModList::COL_FLAGS); - m_EnabledColumns.set(ModList::COL_NAME); - m_EnabledColumns.set(ModList::COL_VERSION); - m_EnabledColumns.set(ModList::COL_PRIORITY); - setDynamicSortFilter(true); // this seems to work without dynamicsortfilter - // but I don't know why. This should be necessary + +ModListSortProxy::ModListSortProxy(Profile* profile, QObject *parent) + : QSortFilterProxyModel(parent) + , m_Profile(profile) + , m_CategoryFilter() + , m_CurrentFilter() + , m_FilterActive(false) + , m_FilterMode(FILTER_AND) +{ + m_EnabledColumns.set(ModList::COL_FLAGS); + m_EnabledColumns.set(ModList::COL_NAME); + m_EnabledColumns.set(ModList::COL_VERSION); + m_EnabledColumns.set(ModList::COL_PRIORITY); + 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::setProfile(Profile *profile) +{ + m_Profile = profile; +} -void ModListSortProxy::updateFilterActive() { - m_FilterActive = ((m_CategoryFilter.size() > 0) || (m_ContentFilter.size() > 0) || !m_CurrentFilter.isEmpty()); - emit filterActive(m_FilterActive); +void ModListSortProxy::updateFilterActive() +{ + m_FilterActive = ((m_CategoryFilter.size() > 0) + || (m_ContentFilter.size() > 0) + || !m_CurrentFilter.isEmpty()); + emit filterActive(m_FilterActive); } -void ModListSortProxy::setCategoryFilter(const std::vector& categories) { - m_CategoryFilter = categories; - updateFilterActive(); - invalidate(); +void ModListSortProxy::setCategoryFilter(const std::vector &categories) +{ + m_CategoryFilter = categories; + updateFilterActive(); + invalidate(); } -void ModListSortProxy::setContentFilter(const std::vector& content) { - m_ContentFilter = content; - updateFilterActive(); - invalidate(); +void ModListSortProxy::setContentFilter(const std::vector &content) +{ + m_ContentFilter = content; + updateFilterActive(); + invalidate(); } -Qt::ItemFlags ModListSortProxy::flags(const QModelIndex& modelIndex) const { - Qt::ItemFlags flags = sourceModel()->flags(mapToSource(modelIndex)); +Qt::ItemFlags ModListSortProxy::flags(const QModelIndex &modelIndex) const +{ + Qt::ItemFlags flags = sourceModel()->flags(mapToSource(modelIndex)); - return flags; + return flags; } -void ModListSortProxy::enableAllVisible() { - if (m_Profile == nullptr) - return; +void ModListSortProxy::enableAllVisible() +{ + if (m_Profile == nullptr) return; - for (int i = 0; i < this->rowCount(); ++i) { - int modID = mapToSource(index(i, 0)).data(Qt::UserRole + 1).toInt(); - m_Profile->setModEnabled(modID, true); - } - invalidate(); + for (int i = 0; i < this->rowCount(); ++i) { + int modID = mapToSource(index(i, 0)).data(Qt::UserRole + 1).toInt(); + m_Profile->setModEnabled(modID, true); + } + invalidate(); } -void ModListSortProxy::disableAllVisible() { - if (m_Profile == nullptr) - return; +void ModListSortProxy::disableAllVisible() +{ + if (m_Profile == nullptr) return; - for (int i = 0; i < this->rowCount(); ++i) { - int modID = mapToSource(index(i, 0)).data(Qt::UserRole + 1).toInt(); - m_Profile->setModEnabled(modID, false); - } - invalidate(); + for (int i = 0; i < this->rowCount(); ++i) { + int modID = mapToSource(index(i, 0)).data(Qt::UserRole + 1).toInt(); + m_Profile->setModEnabled(modID, false); + } + invalidate(); } -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; - } +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; + } + return result; } -bool ModListSortProxy::lessThan(const QModelIndex& left, const QModelIndex& right) const { - if (sourceModel()->hasChildren(left) || sourceModel()->hasChildren(right)) { - return QSortFilterProxyModel::lessThan(left, right); - } - - bool lOk, rOk; - int leftIndex = left.data(Qt::UserRole + 1).toInt(&lOk); - int rightIndex = right.data(Qt::UserRole + 1).toInt(&rOk); - if (!lOk || !rOk) { - return false; - } +bool ModListSortProxy::lessThan(const QModelIndex &left, + const QModelIndex &right) const +{ + if (sourceModel()->hasChildren(left) || sourceModel()->hasChildren(right)) { + return QSortFilterProxyModel::lessThan(left, right); + } + + bool lOk, rOk; + int leftIndex = left.data(Qt::UserRole + 1).toInt(&lOk); + int rightIndex = right.data(Qt::UserRole + 1).toInt(&rOk); + if (!lOk || !rOk) { + return false; + } - ModInfo::Ptr leftMod = ModInfo::getByIndex(leftIndex); - ModInfo::Ptr rightMod = ModInfo::getByIndex(rightIndex); + ModInfo::Ptr leftMod = ModInfo::getByIndex(leftIndex); + ModInfo::Ptr rightMod = ModInfo::getByIndex(rightIndex); - bool lt = false; + bool lt = false; - { - QModelIndex leftPrioIdx = left.sibling(left.row(), ModList::COL_PRIORITY); - QVariant leftPrio = leftPrioIdx.data(); - if (!leftPrio.isValid()) - leftPrio = left.data(Qt::UserRole); - QModelIndex rightPrioIdx = right.sibling(right.row(), ModList::COL_PRIORITY); - QVariant rightPrio = rightPrioIdx.data(); - if (!rightPrio.isValid()) - rightPrio = right.data(Qt::UserRole); + { + QModelIndex leftPrioIdx = left.sibling(left.row(), ModList::COL_PRIORITY); + QVariant leftPrio = leftPrioIdx.data(); + if (!leftPrio.isValid()) leftPrio = left.data(Qt::UserRole); + QModelIndex rightPrioIdx = right.sibling(right.row(), ModList::COL_PRIORITY); + QVariant rightPrio = rightPrioIdx.data(); + if (!rightPrio.isValid()) rightPrio = right.data(Qt::UserRole); - lt = leftPrio.toInt() < rightPrio.toInt(); - } + lt = leftPrio.toInt() < rightPrio.toInt(); + } - switch (left.column()) { + 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); - } + 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_CONTENT: { - std::vector lContent = leftMod->getContents(); - std::vector rContent = rightMod->getContents(); - if (lContent.size() != rContent.size()) { - lt = lContent.size() < rContent.size(); - } - - int lValue = 0; - int rValue = 0; - for (ModInfo::EContent content : lContent) { - lValue += 2 << (unsigned int)content; - } - for (ModInfo::EContent content : rContent) { - rValue += 2 << (unsigned int)content; - } - - lt = lValue < rValue; + std::vector lContent = leftMod->getContents(); + std::vector rContent = rightMod->getContents(); + if (lContent.size() != rContent.size()) { + lt = lContent.size() < rContent.size(); + } + + int lValue = 0; + int rValue = 0; + for (ModInfo::EContent content : lContent) { + lValue += 2 << (unsigned int)content; + } + for (ModInfo::EContent content : rContent) { + rValue += 2 << (unsigned int)content; + } + + lt = lValue < rValue; } break; case ModList::COL_NAME: { - int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); - if (comp != 0) - lt = comp < 0; + int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); + if (comp != 0) + lt = comp < 0; } break; case ModList::COL_CATEGORY: { - if (leftMod->getPrimaryCategory() != rightMod->getPrimaryCategory()) { - if (leftMod->getPrimaryCategory() < 0) - lt = false; - else if (rightMod->getPrimaryCategory() < 0) - lt = true; - else { - try { - CategoryFactory& categories = CategoryFactory::instance(); - QString leftCatName = - categories.getCategoryName(categories.getCategoryIndex(leftMod->getPrimaryCategory())); - QString rightCatName = - categories.getCategoryName(categories.getCategoryIndex(rightMod->getPrimaryCategory())); - lt = leftCatName < rightCatName; - } catch (const std::exception& e) { - qCritical("failed to compare categories: %s", e.what()); - } - } + if (leftMod->getPrimaryCategory() != rightMod->getPrimaryCategory()) { + if (leftMod->getPrimaryCategory() < 0) lt = false; + else if (rightMod->getPrimaryCategory() < 0) lt = true; + else { + try { + CategoryFactory &categories = CategoryFactory::instance(); + QString leftCatName = categories.getCategoryName(categories.getCategoryIndex(leftMod->getPrimaryCategory())); + QString rightCatName = categories.getCategoryName(categories.getCategoryIndex(rightMod->getPrimaryCategory())); + lt = leftCatName < rightCatName; + } catch (const std::exception &e) { + qCritical("failed to compare categories: %s", e.what()); + } } + } } break; case ModList::COL_MODID: { - if (leftMod->getNexusID() != rightMod->getNexusID()) - lt = leftMod->getNexusID() < rightMod->getNexusID(); + if (leftMod->getNexusID() != rightMod->getNexusID()) + lt = leftMod->getNexusID() < rightMod->getNexusID(); } break; case ModList::COL_VERSION: { - if (leftMod->getVersion() != rightMod->getVersion()) - lt = leftMod->getVersion() < rightMod->getVersion(); + if (leftMod->getVersion() != rightMod->getVersion()) + lt = leftMod->getVersion() < rightMod->getVersion(); } break; case ModList::COL_INSTALLTIME: { - QDateTime leftTime = left.data().toDateTime(); - QDateTime rightTime = right.data().toDateTime(); - if (leftTime != rightTime) - return leftTime < rightTime; + QDateTime leftTime = left.data().toDateTime(); + QDateTime rightTime = right.data().toDateTime(); + if (leftTime != rightTime) + return leftTime < rightTime; } break; case ModList::COL_PRIORITY: { - // nop, already compared by priority + // nop, already compared by priority } break; - } - return lt; + } + return lt; } -void ModListSortProxy::updateFilter(const QString& filter) { - m_CurrentFilter = filter; - updateFilterActive(); - // using invalidateFilter here should be enough but that crashes the application? WTF? - // invalidateFilter(); - invalidate(); +void ModListSortProxy::updateFilter(const QString &filter) +{ + m_CurrentFilter = filter; + updateFilterActive(); + // using invalidateFilter here should be enough but that crashes the application? WTF? + // invalidateFilter(); + invalidate(); } -bool ModListSortProxy::hasConflictFlag(const std::vector& flags) const { - for (ModInfo::EFlag flag : flags) { - if ((flag == ModInfo::FLAG_CONFLICT_MIXED) || (flag == ModInfo::FLAG_CONFLICT_OVERWRITE) || - (flag == ModInfo::FLAG_CONFLICT_OVERWRITTEN) || (flag == ModInfo::FLAG_CONFLICT_REDUNDANT)) { - return true; - } +bool ModListSortProxy::hasConflictFlag(const std::vector &flags) const +{ + for (ModInfo::EFlag flag : flags) { + if ((flag == ModInfo::FLAG_CONFLICT_MIXED) || + (flag == ModInfo::FLAG_CONFLICT_OVERWRITE) || + (flag == ModInfo::FLAG_CONFLICT_OVERWRITTEN) || + (flag == ModInfo::FLAG_CONFLICT_REDUNDANT)) { + return true; } + } - return false; + return false; } -bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const { - for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { - switch (*iter) { - case CategoryFactory::CATEGORY_SPECIAL_CHECKED: { - if (!enabled && !info->alwaysEnabled()) - return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UNCHECKED: { - if (enabled || info->alwaysEnabled()) - return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: { - if (!info->updateAvailable() && !info->downgradeAvailable()) - return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: { - if (info->getCategories().size() > 0) - return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_CONFLICT: { - if (!hasConflictFlag(info->getFlags())) - return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: { - ModInfo::EEndorsedState state = info->endorsedState(); - if (state != ModInfo::ENDORSED_FALSE) - return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_MANAGED: { - if (info->hasFlag(ModInfo::FLAG_FOREIGN)) - return false; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: { - if (!info->hasFlag(ModInfo::FLAG_FOREIGN)) - return false; - } break; - default: { - if (!info->categorySet(*iter)) - return false; - } break; - } +bool ModListSortProxy::filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const +{ + for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { + switch (*iter) { + case CategoryFactory::CATEGORY_SPECIAL_CHECKED: { + if (!enabled && !info->alwaysEnabled()) return false; + } break; + case CategoryFactory::CATEGORY_SPECIAL_UNCHECKED: { + if (enabled || info->alwaysEnabled()) return false; + } break; + case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: { + if (!info->updateAvailable() && !info->downgradeAvailable()) return false; + } break; + case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: { + if (info->getCategories().size() > 0) return false; + } break; + case CategoryFactory::CATEGORY_SPECIAL_CONFLICT: { + if (!hasConflictFlag(info->getFlags())) return false; + } break; + case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: { + ModInfo::EEndorsedState state = info->endorsedState(); + if (state != ModInfo::ENDORSED_FALSE) return false; + } break; + case CategoryFactory::CATEGORY_SPECIAL_MANAGED: { + if (info->hasFlag(ModInfo::FLAG_FOREIGN)) return false; + } break; + case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: { + if (!info->hasFlag(ModInfo::FLAG_FOREIGN)) return false; + } break; + default: { + if (!info->categorySet(*iter)) return false; + } break; } + } - foreach (int content, m_ContentFilter) { - if (!info->hasContent(static_cast(content))) - return false; - } + foreach (int content, m_ContentFilter) { + if (!info->hasContent(static_cast(content))) return false; + } - return true; + return true; } -bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const { - for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { - switch (*iter) { - case CategoryFactory::CATEGORY_SPECIAL_CHECKED: { - if (enabled || info->alwaysEnabled()) - return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UNCHECKED: { - if (!enabled && !info->alwaysEnabled()) - return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: { - if (info->updateAvailable() || info->downgradeAvailable()) - return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: { - if (info->getCategories().size() == 0) - return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_CONFLICT: { - if (hasConflictFlag(info->getFlags())) - return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: { - ModInfo::EEndorsedState state = info->endorsedState(); - if ((state == ModInfo::ENDORSED_FALSE) || (state == ModInfo::ENDORSED_NEVER)) - return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_MANAGED: { - if (!info->hasFlag(ModInfo::FLAG_FOREIGN)) - return true; - } break; - case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: { - if (info->hasFlag(ModInfo::FLAG_FOREIGN)) - return true; - } break; - default: { - if (info->categorySet(*iter)) - return true; - } break; - } +bool ModListSortProxy::filterMatchesModOr(ModInfo::Ptr info, bool enabled) const +{ + for (auto iter = m_CategoryFilter.begin(); iter != m_CategoryFilter.end(); ++iter) { + switch (*iter) { + case CategoryFactory::CATEGORY_SPECIAL_CHECKED: { + if (enabled || info->alwaysEnabled()) return true; + } break; + case CategoryFactory::CATEGORY_SPECIAL_UNCHECKED: { + if (!enabled && !info->alwaysEnabled()) return true; + } break; + case CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE: { + if (info->updateAvailable() || info->downgradeAvailable()) return true; + } break; + case CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY: { + if (info->getCategories().size() == 0) return true; + } break; + case CategoryFactory::CATEGORY_SPECIAL_CONFLICT: { + if (hasConflictFlag(info->getFlags())) return true; + } break; + case CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED: { + ModInfo::EEndorsedState state = info->endorsedState(); + if ((state == ModInfo::ENDORSED_FALSE) || (state == ModInfo::ENDORSED_NEVER)) return true; + } break; + case CategoryFactory::CATEGORY_SPECIAL_MANAGED: { + if (!info->hasFlag(ModInfo::FLAG_FOREIGN)) return true; + } break; + case CategoryFactory::CATEGORY_SPECIAL_UNMANAGED: { + if (info->hasFlag(ModInfo::FLAG_FOREIGN)) return true; + } break; + default: { + if (info->categorySet(*iter)) return true; + } break; } + } - foreach (int content, m_ContentFilter) { - if (info->hasContent(static_cast(content))) - return true; - } + foreach (int content, m_ContentFilter) { + if (info->hasContent(static_cast(content))) return true; + } - return false; + return false; } -bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const { - if (!m_CurrentFilter.isEmpty() && !info->name().contains(m_CurrentFilter, Qt::CaseInsensitive)) { - return false; - } +bool ModListSortProxy::filterMatchesMod(ModInfo::Ptr info, bool enabled) const +{ + if (!m_CurrentFilter.isEmpty() && + !info->name().contains(m_CurrentFilter, Qt::CaseInsensitive)) { + return false; + } - if (m_FilterMode == FILTER_AND) { - return filterMatchesModAnd(info, enabled); - } else { - return filterMatchesModOr(info, enabled); - } + if (m_FilterMode == FILTER_AND) { + return filterMatchesModAnd(info, enabled); + } else { + return filterMatchesModOr(info, enabled); + } } -void ModListSortProxy::setFilterMode(ModListSortProxy::FilterMode mode) { - if (m_FilterMode != mode) { - m_FilterMode = mode; - this->invalidate(); - } +void ModListSortProxy::setFilterMode(ModListSortProxy::FilterMode mode) +{ + if (m_FilterMode != mode) { + m_FilterMode = mode; + this->invalidate(); + } } -bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex& parent) const { - if (m_Profile == nullptr) { - return false; - } +bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex &parent) const +{ + if (m_Profile == nullptr) { + return false; + } - if (row >= static_cast(m_Profile->numMods())) { - qWarning("invalid row idx %d", row); - return false; - } + if (row >= static_cast(m_Profile->numMods())) { + qWarning("invalid row idx %d", row); + return false; + } - QModelIndex idx = sourceModel()->index(row, 0, parent); - if (!idx.isValid()) { - qDebug("invalid index"); - return false; + QModelIndex idx = sourceModel()->index(row, 0, parent); + if (!idx.isValid()) { + qDebug("invalid index"); + return false; + } + if (sourceModel()->hasChildren(idx)) { + for (int i = 0; i < sourceModel()->rowCount(idx); ++i) { + if (filterAcceptsRow(i, idx)) { + return true; + } } - if (sourceModel()->hasChildren(idx)) { - for (int i = 0; i < sourceModel()->rowCount(idx); ++i) { - if (filterAcceptsRow(i, idx)) { - return true; - } - } - return false; - } else { - bool modEnabled = idx.sibling(row, 0).data(Qt::CheckStateRole).toInt() == Qt::Checked; - unsigned int index = idx.data(Qt::UserRole + 1).toInt(); - return filterMatchesMod(ModInfo::getByIndex(index), modEnabled); - } + return false; + } else { + bool modEnabled = idx.sibling(row, 0).data(Qt::CheckStateRole).toInt() == Qt::Checked; + unsigned int index = idx.data(Qt::UserRole + 1).toInt(); + return filterMatchesMod(ModInfo::getByIndex(index), modEnabled); + } } -bool ModListSortProxy::dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, - const QModelIndex& parent) { - if (!data->hasUrls() && (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. - // 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 ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent) +{ + if (!data->hasUrls() && (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. + // 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()); } -void ModListSortProxy::setSourceModel(QAbstractItemModel* sourceModel) { - QSortFilterProxyModel::setSourceModel(sourceModel); - QtGroupingProxy* proxy = qobject_cast(sourceModel); - if (proxy != nullptr) { - sourceModel = proxy->sourceModel(); - } - connect(sourceModel, SIGNAL(aboutToChangeData()), this, SLOT(aboutToChangeData()), Qt::UniqueConnection); - connect(sourceModel, SIGNAL(postDataChanged()), this, SLOT(postDataChanged()), Qt::UniqueConnection); +void ModListSortProxy::setSourceModel(QAbstractItemModel *sourceModel) +{ + QSortFilterProxyModel::setSourceModel(sourceModel); + QtGroupingProxy *proxy = qobject_cast(sourceModel); + if (proxy != nullptr) { + sourceModel = proxy->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_PreChangeFilters = categoryFilter(); - setCategoryFilter(std::vector()); +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_PreChangeFilters = categoryFilter(); + setCategoryFilter(std::vector()); } -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]() { - setCategoryFilter(m_PreChangeFilters); - m_PreChangeFilters.clear(); - }); +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] () { + setCategoryFilter(m_PreChangeFilters); + m_PreChangeFilters.clear(); + }); } + diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 157889f0..b3c01fea 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -20,104 +20,120 @@ along with Mod Organizer. If not, see . #ifndef MODLISTSORTPROXY_H #define MODLISTSORTPROXY_H -#include "modlist.h" #include #include +#include "modlist.h" class Profile; -class ModListSortProxy : public QSortFilterProxyModel { - Q_OBJECT +class ModListSortProxy : public QSortFilterProxyModel +{ + Q_OBJECT public: - enum FilterMode { FILTER_AND, FILTER_OR }; - enum FilterType { TYPE_SPECIAL, TYPE_CATEGORY, TYPE_CONTENT }; + enum FilterMode { + FILTER_AND, + FILTER_OR + }; + + enum FilterType { + TYPE_SPECIAL, + TYPE_CATEGORY, + TYPE_CONTENT + }; public: - explicit ModListSortProxy(Profile* profile, QObject* parent = 0); - void setProfile(Profile* profile); + explicit ModListSortProxy(Profile *profile, QObject *parent = 0); + + void setProfile(Profile *profile); - void setCategoryFilter(const std::vector& categories); - std::vector categoryFilter() const { return m_CategoryFilter; } + void setCategoryFilter(const std::vector &categories); + std::vector categoryFilter() const { return m_CategoryFilter; } - void setContentFilter(const std::vector& content); + void setContentFilter(const std::vector &content); - virtual Qt::ItemFlags flags(const QModelIndex& modelIndex) const; - virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, - const QModelIndex& parent); + virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; + virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, + int row, int column, const QModelIndex &parent); - virtual void setSourceModel(QAbstractItemModel* sourceModel) override; + virtual void setSourceModel(QAbstractItemModel *sourceModel) override; - /** - * @brief enable all mods visible under the current filter - **/ - void enableAllVisible(); + /** + * @brief enable all mods visible under the current filter + **/ + void enableAllVisible(); - /** - * @brief disable all mods visible under the current filter - **/ - void disableAllVisible(); + /** + * @brief disable all mods visible under the current filter + **/ + void disableAllVisible(); - /** - * @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; + /** + * @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; } + /** + * @return true if a filter is currently active + */ + bool isFilterActive() const { return m_FilterActive; } - void setFilterMode(FilterMode mode); + void setFilterMode(FilterMode mode); - /** - * @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 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; + } public slots: - void updateFilter(const QString& filter); + void updateFilter(const QString &filter); signals: - void filterActive(bool active); + void filterActive(bool active); protected: - virtual bool lessThan(const QModelIndex& left, const QModelIndex& right) const; - virtual bool filterAcceptsRow(int row, const QModelIndex& parent) const; + + 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; - 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; + + unsigned long flagsId(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; private slots: - void aboutToChangeData(); - void postDataChanged(); + void aboutToChangeData(); + void postDataChanged(); private: - Profile* m_Profile; - std::vector m_CategoryFilter; - std::vector m_ContentFilter; - std::bitset m_EnabledColumns; - QString m_CurrentFilter; + Profile *m_Profile; + + std::vector m_CategoryFilter; + std::vector m_ContentFilter; + std::bitset m_EnabledColumns; + QString m_CurrentFilter; + + bool m_FilterActive; + FilterMode m_FilterMode; - bool m_FilterActive; - FilterMode m_FilterMode; + std::vector m_PreChangeFilters; - std::vector m_PreChangeFilters; }; #endif // MODLISTSORTPROXY_H diff --git a/src/modlistview.cpp b/src/modlistview.cpp index 9b98a3b7..fe2d3e57 100644 --- a/src/modlistview.cpp +++ b/src/modlistview.cpp @@ -1,47 +1,56 @@ #include "modlistview.h" +#include #include #include -#include -class ModListViewStyle : public QProxyStyle { -public: - ModListViewStyle(QStyle* style, int indentation); - void drawPrimitive(PrimitiveElement element, const QStyleOption* option, QPainter* painter, - const QWidget* widget = 0) const; +class ModListViewStyle: public QProxyStyle { +public: + ModListViewStyle(QStyle *style, int indentation); + void drawPrimitive (PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget = 0) const; private: - int m_Indentation; + int m_Indentation; }; -ModListViewStyle::ModListViewStyle(QStyle* style, int indentation) : QProxyStyle(style), m_Indentation(indentation) {} - -void ModListViewStyle::drawPrimitive(PrimitiveElement element, const QStyleOption* option, QPainter* painter, - const QWidget* widget) const { - if (element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull()) { - QStyleOption opt(*option); - opt.rect.setLeft(m_Indentation); - if (widget) { - opt.rect.setRight(widget->width() - 5); // 5 is an arbitrary value that seems to work ok - } - QProxyStyle::drawPrimitive(element, &opt, painter, widget); - } else { - QProxyStyle::drawPrimitive(element, option, painter, widget); +ModListViewStyle::ModListViewStyle(QStyle *style, int indentation) + : QProxyStyle(style), m_Indentation(indentation) +{ +} + +void ModListViewStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const +{ + if (element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull()) { + QStyleOption opt(*option); + opt.rect.setLeft(m_Indentation); + if (widget) { + opt.rect.setRight(widget->width() - 5); // 5 is an arbitrary value that seems to work ok } + QProxyStyle::drawPrimitive(element, &opt, painter, widget); + } else { + QProxyStyle::drawPrimitive(element, option, painter, widget); + } } -ModListView::ModListView(QWidget* parent) - : QTreeView(parent), m_Scrollbar(new ViewMarkingScrollBar(this->model(), this)) { - setVerticalScrollBar(m_Scrollbar); +ModListView::ModListView(QWidget *parent) + : QTreeView(parent) + , m_Scrollbar(new ViewMarkingScrollBar(this->model(), this)) +{ + setVerticalScrollBar(m_Scrollbar); } -void ModListView::dragEnterEvent(QDragEnterEvent* event) { - emit dropModeUpdate(event->mimeData()->hasUrls()); +void ModListView::dragEnterEvent(QDragEnterEvent *event) +{ + emit dropModeUpdate(event->mimeData()->hasUrls()); - QTreeView::dragEnterEvent(event); + QTreeView::dragEnterEvent(event); } -void ModListView::setModel(QAbstractItemModel* model) { - QTreeView::setModel(model); - setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); +void ModListView::setModel(QAbstractItemModel *model) +{ + QTreeView::setModel(model); + setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); } + diff --git a/src/modlistview.h b/src/modlistview.h index a4f56efc..6cd114b0 100644 --- a/src/modlistview.h +++ b/src/modlistview.h @@ -1,22 +1,24 @@ #ifndef MODLISTVIEW_H #define MODLISTVIEW_H -#include "viewmarkingscrollbar.h" -#include #include +#include +#include "viewmarkingscrollbar.h" -class ModListView : public QTreeView { - Q_OBJECT +class ModListView : public QTreeView +{ + Q_OBJECT public: - explicit ModListView(QWidget* parent = 0); - virtual void dragEnterEvent(QDragEnterEvent* event); - virtual void setModel(QAbstractItemModel* model); + explicit ModListView(QWidget *parent = 0); + virtual void dragEnterEvent(QDragEnterEvent *event); + virtual void setModel(QAbstractItemModel *model); signals: - void dropModeUpdate(bool dropOnRows); - + void dropModeUpdate(bool dropOnRows); + public slots: private: - ViewMarkingScrollBar* m_Scrollbar; + + ViewMarkingScrollBar *m_Scrollbar; }; #endif // MODLISTVIEW_H diff --git a/src/moshortcut.cpp b/src/moshortcut.cpp index 75157c1d..c8c2ef6f 100644 --- a/src/moshortcut.cpp +++ b/src/moshortcut.cpp @@ -17,17 +17,23 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ + #include "moshortcut.h" -MOShortcut::MOShortcut(const QString& link) : m_valid(link.startsWith("moshortcut://")), m_hasInstance(false) { - if (m_valid) { - int start = (int)strlen("moshortcut://"); - 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_executable = link.mid(start); + +MOShortcut::MOShortcut(const QString& link) + : m_valid(link.startsWith("moshortcut://")) + , m_hasInstance(false) +{ + if (m_valid) { + int start = (int)strlen("moshortcut://"); + 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_executable = link.mid(start); + } } diff --git a/src/moshortcut.h b/src/moshortcut.h index 8ae5bb24..7b7574b9 100644 --- a/src/moshortcut.h +++ b/src/moshortcut.h @@ -17,27 +17,30 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ + #pragma once + #include + class MOShortcut { public: - MOShortcut(const QString& link); + MOShortcut(const QString& link); - /// true iff intialized using a valid moshortcut link - operator bool() const { return m_valid; } + /// true iff intialized using a valid moshortcut link + operator bool() const { return m_valid; } - bool hasInstance() const { return m_hasInstance; } + bool hasInstance() const { return m_hasInstance; } - const QString& instance() const { return m_instance; } + const QString& instance() const { return m_instance; } - const QString& executable() const { return m_executable; } + const QString& executable() const { return m_executable; } private: - QString m_instance; - QString m_executable; - bool m_valid; - bool m_hasInstance; + QString m_instance; + QString m_executable; + bool m_valid; + bool m_hasInstance; }; diff --git a/src/motddialog.cpp b/src/motddialog.cpp index a03bdf75..96d88542 100644 --- a/src/motddialog.cpp +++ b/src/motddialog.cpp @@ -19,20 +19,29 @@ along with Mod Organizer. If not, see . #include "motddialog.h" #include "bbcode.h" -#include "ui_motddialog.h" #include "utility.h" +#include "ui_motddialog.h" #include -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(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; } +MotDDialog::~MotDDialog() +{ + delete ui; +} -void MotDDialog::on_okButton_clicked() { this->close(); } +void MotDDialog::on_okButton_clicked() +{ + this->close(); +} -void MotDDialog::linkClicked(const QUrl& url) { - ::ShellExecuteW(nullptr, L"open", MOBase::ToWString(url.toString()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); +void MotDDialog::linkClicked(const QUrl &url) +{ + ::ShellExecuteW(nullptr, L"open", MOBase::ToWString(url.toString()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); } diff --git a/src/motddialog.h b/src/motddialog.h index 17636584..5791321f 100644 --- a/src/motddialog.h +++ b/src/motddialog.h @@ -27,19 +27,20 @@ namespace Ui { class MotDDialog; } -class MotDDialog : public QDialog { - Q_OBJECT - +class MotDDialog : public QDialog +{ + Q_OBJECT + public: - explicit MotDDialog(const QString& message, QWidget* parent = 0); - ~MotDDialog(); - + explicit MotDDialog(const QString &message, QWidget *parent = 0); + ~MotDDialog(); + private slots: - void on_okButton_clicked(); - void linkClicked(const QUrl& url); + void on_okButton_clicked(); + void linkClicked(const QUrl &url); private: - Ui::MotDDialog* ui; + Ui::MotDDialog *ui; }; #endif // MOTDDIALOG_H diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index cadcc663..eba02a6f 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -20,530 +20,647 @@ along with Mod Organizer. If not, see . #include "nexusinterface.h" #include "iplugingame.h" -#include "json.h" #include "nxmaccessmanager.h" +#include "json.h" #include "selectiondialog.h" -#include #include +#include #include #include #include + using namespace MOBase; using namespace MOShared; -NexusBridge::NexusBridge(const QString& subModule) : m_Interface(NexusInterface::instance()), m_SubModule(subModule) {} -void NexusBridge::requestDescription(int modID, QVariant userData) { - m_RequestIDs.insert(m_Interface->requestDescription(modID, this, userData, m_SubModule)); +NexusBridge::NexusBridge(const QString &subModule) + : m_Interface(NexusInterface::instance()) + , m_SubModule(subModule) +{ } -void NexusBridge::requestFiles(int modID, QVariant userData) { - m_RequestIDs.insert(m_Interface->requestFiles(modID, this, userData, m_SubModule)); +void NexusBridge::requestDescription(int modID, QVariant userData) +{ + m_RequestIDs.insert(m_Interface->requestDescription(modID, this, userData, m_SubModule)); } -void NexusBridge::requestFileInfo(int modID, int fileID, QVariant userData) { - m_RequestIDs.insert(m_Interface->requestFileInfo(modID, fileID, this, userData, m_SubModule)); +void NexusBridge::requestFiles(int modID, QVariant userData) +{ + m_RequestIDs.insert(m_Interface->requestFiles(modID, this, userData, m_SubModule)); } -void NexusBridge::requestDownloadURL(int modID, int fileID, QVariant userData) { - m_RequestIDs.insert(m_Interface->requestDownloadURL(modID, fileID, this, userData, m_SubModule)); +void NexusBridge::requestFileInfo(int modID, int fileID, QVariant userData) +{ + m_RequestIDs.insert(m_Interface->requestFileInfo(modID, fileID, this, userData, m_SubModule)); } -void NexusBridge::requestToggleEndorsement(int modID, bool endorse, QVariant userData) { - m_RequestIDs.insert(m_Interface->requestToggleEndorsement(modID, endorse, this, userData, m_SubModule)); +void NexusBridge::requestDownloadURL(int modID, int fileID, QVariant userData) +{ + m_RequestIDs.insert(m_Interface->requestDownloadURL(modID, fileID, this, userData, m_SubModule)); } -void NexusBridge::nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID) { - std::set::iterator iter = m_RequestIDs.find(requestID); - if (iter != m_RequestIDs.end()) { - m_RequestIDs.erase(iter); - - emit descriptionAvailable(modID, userData, resultData); - } +void NexusBridge::requestToggleEndorsement(int modID, bool endorse, QVariant userData) +{ + m_RequestIDs.insert(m_Interface->requestToggleEndorsement(modID, endorse, this, userData, m_SubModule)); } -void NexusBridge::nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID) { - std::set::iterator iter = m_RequestIDs.find(requestID); - if (iter != m_RequestIDs.end()) { - m_RequestIDs.erase(iter); +void NexusBridge::nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); - QList fileInfoList; + emit descriptionAvailable(modID, userData, resultData); + } +} - QVariantList resultList = resultData.toList(); +void NexusBridge::nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); - for (const QVariant& file : resultList) { - ModRepositoryFileInfo temp; - QVariantMap fileInfo = file.toMap(); - temp.uri = fileInfo["uri"].toString(); - temp.name = fileInfo["name"].toString(); - temp.description = fileInfo["description"].toString(); - temp.version = VersionInfo(fileInfo["version"].toString()); - temp.categoryID = fileInfo["category_id"].toInt(); - temp.fileID = fileInfo["id"].toInt(); - temp.fileSize = fileInfo["size"].toInt(); - fileInfoList.append(temp); - } + QList fileInfoList; + + QVariantList resultList = resultData.toList(); - emit filesAvailable(modID, userData, fileInfoList); + for (const QVariant &file : resultList) { + ModRepositoryFileInfo temp; + QVariantMap fileInfo = file.toMap(); + temp.uri = fileInfo["uri"].toString(); + temp.name = fileInfo["name"].toString(); + temp.description = fileInfo["description"].toString(); + temp.version = VersionInfo(fileInfo["version"].toString()); + temp.categoryID = fileInfo["category_id"].toInt(); + temp.fileID = fileInfo["id"].toInt(); + temp.fileSize = fileInfo["size"].toInt(); + fileInfoList.append(temp); } + + emit filesAvailable(modID, userData, fileInfoList); + } } -void NexusBridge::nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID) { - std::set::iterator iter = m_RequestIDs.find(requestID); - if (iter != m_RequestIDs.end()) { - m_RequestIDs.erase(iter); - emit fileInfoAvailable(modID, fileID, userData, resultData); - } +void NexusBridge::nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); + emit fileInfoAvailable(modID, fileID, userData, resultData); + } } -void NexusBridge::nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, - int requestID) { - std::set::iterator iter = m_RequestIDs.find(requestID); - if (iter != m_RequestIDs.end()) { - m_RequestIDs.erase(iter); - emit downloadURLsAvailable(modID, fileID, userData, resultData); - } +void NexusBridge::nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); + emit downloadURLsAvailable(modID, fileID, userData, resultData); + } } -void NexusBridge::nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData, int requestID) { - std::set::iterator iter = m_RequestIDs.find(requestID); - if (iter != m_RequestIDs.end()) { - m_RequestIDs.erase(iter); - emit endorsementToggled(modID, userData, resultData); - } +void NexusBridge::nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData, int requestID) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); + emit endorsementToggled(modID, userData, resultData); + } } -void NexusBridge::nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, - const QString& errorMessage) { - std::set::iterator iter = m_RequestIDs.find(requestID); - if (iter != m_RequestIDs.end()) { - m_RequestIDs.erase(iter); - emit requestFailed(modID, fileID, userData, errorMessage); - } +void NexusBridge::nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorMessage) +{ + std::set::iterator iter = m_RequestIDs.find(requestID); + if (iter != m_RequestIDs.end()) { + m_RequestIDs.erase(iter); + emit requestFailed(modID, fileID, userData, errorMessage); + } } + QAtomicInt NexusInterface::NXMRequestInfo::s_NextID(0); -NexusInterface::NexusInterface() : m_NMMVersion() { - VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); - m_MOVersion = - VersionInfo(version.dwFileVersionMS >> 16, version.dwFileVersionMS & 0xFFFF, version.dwFileVersionLS >> 16); - - m_AccessManager = new NXMAccessManager(this, m_MOVersion.displayString()); - m_DiskCache = new QNetworkDiskCache(this); - connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); -} - -NXMAccessManager* NexusInterface::getAccessManager() { return m_AccessManager; } - -NexusInterface::~NexusInterface() { cleanup(); } - -NexusInterface* NexusInterface::instance() { - static NexusInterface s_Instance; - return &s_Instance; -} - -void NexusInterface::setCacheDirectory(const QString& directory) { - m_DiskCache->setCacheDirectory(directory); - m_AccessManager->setCache(m_DiskCache); -} - -void NexusInterface::setNMMVersion(const QString& nmmVersion) { - m_NMMVersion = nmmVersion; - m_AccessManager->setNMMVersion(nmmVersion); -} - -void NexusInterface::loginCompleted() { nextRequest(); } - -void NexusInterface::interpretNexusFileName(const QString& fileName, QString& modName, int& modID, bool query) { - // Look for something along the lines of modulename-Vn-m + any old rubbish. - static std::regex exp(R"exp(^([a-zA-Z0-9_'"\-.() ]*?)([-_ ][VvRr]?[0-9_]+)?-([1-9][0-9]*).*\.(zip|rar|7z))exp"); - static std::regex simpleexp("^([a-zA-Z0-9_]+)"); - - QByteArray fileNameUTF8 = fileName.toUtf8(); - std::cmatch result; - if (std::regex_search(fileNameUTF8.constData(), result, exp)) { - modName = QString::fromUtf8(result[1].str().c_str()); - modName = modName.replace('_', ' ').trimmed(); - - std::string candidate = result[3].str(); - std::string candidate2 = result[2].str(); - if (candidate2.length() != 0 && (candidate2.find_last_of("VvRr") == std::string::npos)) { - // well, that second match might be an id too... - size_t offset = strspn(candidate2.c_str(), "-_ "); - if (offset < candidate2.length() && query) { - SelectionDialog selection( - tr("Failed to guess mod id for \"%1\", please pick the correct one").arg(fileName)); - QString r2Highlight(fileName); - r2Highlight.insert(result.position(2) + result.length(2), "* ") - .insert(result.position(2) + static_cast(offset), " *"); - QString r3Highlight(fileName); - r3Highlight.insert(result.position(3) + result.length(3), "* ").insert(result.position(3), " *"); - - selection.addChoice(candidate.c_str(), r3Highlight, - static_cast(strtol(candidate.c_str(), nullptr, 10))); - selection.addChoice(candidate2.c_str() + offset, r2Highlight, - static_cast(abs(strtol(candidate2.c_str() + offset, nullptr, 10)))); - if (selection.exec() == QDialog::Accepted) { - modID = selection.getChoiceData().toInt(); - } else { - modID = -1; - } - } else { - modID = -1; - } + +NexusInterface::NexusInterface() + : m_NMMVersion() +{ + VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); + m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, + version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16); + + m_AccessManager = new NXMAccessManager(this, m_MOVersion.displayString()); + m_DiskCache = new QNetworkDiskCache(this); + connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); +} + +NXMAccessManager *NexusInterface::getAccessManager() +{ + return m_AccessManager; +} + +NexusInterface::~NexusInterface() +{ + cleanup(); +} + +NexusInterface *NexusInterface::instance() +{ + static NexusInterface s_Instance; + return &s_Instance; +} + +void NexusInterface::setCacheDirectory(const QString &directory) +{ + m_DiskCache->setCacheDirectory(directory); + m_AccessManager->setCache(m_DiskCache); +} + +void NexusInterface::setNMMVersion(const QString &nmmVersion) +{ + m_NMMVersion = nmmVersion; + m_AccessManager->setNMMVersion(nmmVersion); +} + +void NexusInterface::loginCompleted() +{ + nextRequest(); +} + + +void NexusInterface::interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query) +{ + //Look for something along the lines of modulename-Vn-m + any old rubbish. + static std::regex exp(R"exp(^([a-zA-Z0-9_'"\-.() ]*?)([-_ ][VvRr]?[0-9_]+)?-([1-9][0-9]*).*\.(zip|rar|7z))exp"); + static std::regex simpleexp("^([a-zA-Z0-9_]+)"); + + QByteArray fileNameUTF8 = fileName.toUtf8(); + std::cmatch result; + if (std::regex_search(fileNameUTF8.constData(), result, exp)) { + modName = QString::fromUtf8(result[1].str().c_str()); + modName = modName.replace('_', ' ').trimmed(); + + std::string candidate = result[3].str(); + std::string candidate2 = result[2].str(); + if (candidate2.length() != 0 && (candidate2.find_last_of("VvRr") == std::string::npos)) { + // well, that second match might be an id too... + size_t offset = strspn(candidate2.c_str(), "-_ "); + if (offset < candidate2.length() && query) { + SelectionDialog selection(tr("Failed to guess mod id for \"%1\", please pick the correct one").arg(fileName)); + QString r2Highlight(fileName); + r2Highlight.insert(result.position(2) + result.length(2), "* ") + .insert(result.position(2) + static_cast(offset), " *"); + QString r3Highlight(fileName); + r3Highlight.insert(result.position(3) + result.length(3), "* ").insert(result.position(3), " *"); + + selection.addChoice(candidate.c_str(), r3Highlight, static_cast(strtol(candidate.c_str(), nullptr, 10))); + selection.addChoice(candidate2.c_str() + offset, r2Highlight, static_cast(abs(strtol(candidate2.c_str() + offset, nullptr, 10)))); + if (selection.exec() == QDialog::Accepted) { + modID = selection.getChoiceData().toInt(); } else { - modID = strtol(candidate.c_str(), nullptr, 10); + modID = -1; } - qDebug("mod id guessed: %s -> %d", qPrintable(fileName), modID); - } else if (std::regex_search(fileNameUTF8.constData(), result, simpleexp)) { - qDebug("simple expression matched, using name only"); - modName = QString::fromUtf8(result[1].str().c_str()); - modName = modName.replace('_', ' ').trimmed(); - + } else { modID = -1; + } } else { - qDebug("no expression matched!"); - modName.clear(); - modID = -1; + modID = strtol(candidate.c_str(), nullptr, 10); } + qDebug("mod id guessed: %s -> %d", qPrintable(fileName), modID); + } else if (std::regex_search(fileNameUTF8.constData(), result, simpleexp)) { + qDebug("simple expression matched, using name only"); + modName = QString::fromUtf8(result[1].str().c_str()); + modName = modName.replace('_', ' ').trimmed(); + + modID = -1; + } else { + qDebug("no expression matched!"); + modName.clear(); + modID = -1; + } } -bool NexusInterface::isURLGameRelated(const QUrl& url) const { - QString const name(url.toString()); - return name.startsWith(getGameURL() + "/") || name.startsWith(getOldModsURL() + "/"); +bool NexusInterface::isURLGameRelated(const QUrl &url) const +{ + QString const name(url.toString()); + return name.startsWith(getGameURL() + "/") || + name.startsWith(getOldModsURL() + "/"); } -QString NexusInterface::getGameURL() const { return "http://www.nexusmods.com/" + m_Game->gameNexusName().toLower(); } +QString NexusInterface::getGameURL() const +{ + return "http://www.nexusmods.com/" + m_Game->gameNexusName().toLower(); +} -QString NexusInterface::getOldModsURL() const { - return "http://" + m_Game->gameNexusName().toLower() + ".nexusmods.com/mods"; +QString NexusInterface::getOldModsURL() const +{ + return "http://" + m_Game->gameNexusName().toLower() + ".nexusmods.com/mods"; } -QString NexusInterface::getModURL(int modID) const { return QString("%1/mods/%2").arg(getGameURL()).arg(modID); } -bool NexusInterface::isModURL(int modID, const QString& url) const { - if (QUrl(url) == QUrl(getModURL(modID))) { - return true; - } - // Try the alternate (old style) mod name - QString alt = QString("%1/%2").arg(getOldModsURL()).arg(modID); - return QUrl(alt) == QUrl(url); +QString NexusInterface::getModURL(int modID) const +{ + return QString("%1/mods/%2").arg(getGameURL()).arg(modID); } -int NexusInterface::requestDescription(int modID, QObject* receiver, QVariant userData, const QString& subModule, - MOBase::IPluginGame const* game) { - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_DESCRIPTION, userData, subModule, game); - m_RequestQueue.enqueue(requestInfo); +bool NexusInterface::isModURL(int modID, const QString &url) const +{ + if (QUrl(url) == QUrl(getModURL(modID))) { + return true; + } + //Try the alternate (old style) mod name + QString alt = QString("%1/%2").arg(getOldModsURL()).arg(modID); + return QUrl(alt) == QUrl(url); +} - connect(this, SIGNAL(nxmDescriptionAvailable(int, QVariant, QVariant, int)), receiver, - SLOT(nxmDescriptionAvailable(int, QVariant, QVariant, int)), Qt::UniqueConnection); +int NexusInterface::requestDescription(int modID, QObject *receiver, QVariant userData, + const QString &subModule, MOBase::IPluginGame const *game) +{ + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_DESCRIPTION, userData, subModule, game); + m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmRequestFailed(int, int, QVariant, int, QString)), receiver, - SLOT(nxmRequestFailed(int, int, QVariant, int, QString)), Qt::UniqueConnection); + connect(this, SIGNAL(nxmDescriptionAvailable(int,QVariant,QVariant,int)), + receiver, SLOT(nxmDescriptionAvailable(int,QVariant,QVariant,int)), Qt::UniqueConnection); - nextRequest(); - return requestInfo.m_ID; + connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), + receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; } -int NexusInterface::requestUpdates(const std::vector& modIDs, QObject* receiver, QVariant userData, - const QString& subModule, MOBase::IPluginGame const* game) { - NXMRequestInfo requestInfo(modIDs, NXMRequestInfo::TYPE_GETUPDATES, userData, subModule, game); - m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmUpdatesAvailable(std::vector, QVariant, QVariant, int)), receiver, - SLOT(nxmUpdatesAvailable(std::vector, QVariant, QVariant, int)), Qt::UniqueConnection); +int NexusInterface::requestUpdates(const std::vector &modIDs, QObject *receiver, QVariant userData, + const QString &subModule, MOBase::IPluginGame const *game) +{ + NXMRequestInfo requestInfo(modIDs, NXMRequestInfo::TYPE_GETUPDATES, userData, subModule, game); + m_RequestQueue.enqueue(requestInfo); + + connect(this, SIGNAL(nxmUpdatesAvailable(std::vector,QVariant,QVariant,int)), + receiver, SLOT(nxmUpdatesAvailable(std::vector,QVariant,QVariant,int)), Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(int, int, QVariant, int, QString)), receiver, - SLOT(nxmRequestFailed(int, int, QVariant, int, QString)), Qt::UniqueConnection); + connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), + receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); - nextRequest(); - return requestInfo.m_ID; + nextRequest(); + return requestInfo.m_ID; } -void NexusInterface::fakeFiles() { - static int id = 42; - QVariantList result; - QVariantMap fileMap; - fileMap["uri"] = "fakeURI"; - fileMap["name"] = "fakeName"; - fileMap["description"] = "fakeDescription"; - fileMap["version"] = "1.0.0"; - fileMap["category_id"] = "1"; - fileMap["id"] = "1"; - fileMap["size"] = "512"; - result.append(fileMap); +void NexusInterface::fakeFiles() +{ + static int id = 42; - emit nxmFilesAvailable(1234, "fake", result, id++); + QVariantList result; + QVariantMap fileMap; + fileMap["uri"] = "fakeURI"; + fileMap["name"] = "fakeName"; + fileMap["description"] = "fakeDescription"; + fileMap["version"] = "1.0.0"; + fileMap["category_id"] = "1"; + fileMap["id"] = "1"; + fileMap["size"] = "512"; + result.append(fileMap); + + emit nxmFilesAvailable(1234, "fake", result, id++); } -int NexusInterface::requestFiles(int modID, QObject* receiver, QVariant userData, const QString& subModule, - MOBase::IPluginGame const* game) { - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_FILES, userData, subModule, game); - m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmFilesAvailable(int, QVariant, QVariant, int)), receiver, - SLOT(nxmFilesAvailable(int, QVariant, QVariant, int)), Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(int, int, QVariant, int, QString)), receiver, - SLOT(nxmRequestFailed(int, int, QVariant, int, QString)), Qt::UniqueConnection); +int NexusInterface::requestFiles(int modID, QObject *receiver, QVariant userData, + const QString &subModule, MOBase::IPluginGame const *game) +{ + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_FILES, userData, subModule, game); + m_RequestQueue.enqueue(requestInfo); + connect(this, SIGNAL(nxmFilesAvailable(int,QVariant,QVariant,int)), + receiver, SLOT(nxmFilesAvailable(int,QVariant,QVariant,int)), Qt::UniqueConnection); + + connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), + receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); - nextRequest(); - return requestInfo.m_ID; + nextRequest(); + return requestInfo.m_ID; } -int NexusInterface::requestFileInfo(int modID, int fileID, QObject* receiver, QVariant userData, - const QString& subModule, MOBase::IPluginGame const* game) { - NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_FILEINFO, userData, subModule, game); - m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmFileInfoAvailable(int, int, QVariant, QVariant, int)), receiver, - SLOT(nxmFileInfoAvailable(int, int, QVariant, QVariant, int)), Qt::UniqueConnection); +int NexusInterface::requestFileInfo(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule, + MOBase::IPluginGame const *game) +{ + NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_FILEINFO, userData, subModule, game); + m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmRequestFailed(int, int, QVariant, int, QString)), receiver, - SLOT(nxmRequestFailed(int, int, QVariant, int, QString)), Qt::UniqueConnection); + connect(this, SIGNAL(nxmFileInfoAvailable(int,int,QVariant,QVariant,int)), + receiver, SLOT(nxmFileInfoAvailable(int,int,QVariant,QVariant,int)), Qt::UniqueConnection); - nextRequest(); - return requestInfo.m_ID; + connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), + receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); + + nextRequest(); + return requestInfo.m_ID; } -int NexusInterface::requestDownloadURL(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); - m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmDownloadURLsAvailable(int, int, QVariant, QVariant, int)), receiver, - SLOT(nxmDownloadURLsAvailable(int, int, QVariant, QVariant, int)), Qt::UniqueConnection); +int NexusInterface::requestDownloadURL(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); + m_RequestQueue.enqueue(requestInfo); + + connect(this, SIGNAL(nxmDownloadURLsAvailable(int,int,QVariant,QVariant,int)), + receiver, SLOT(nxmDownloadURLsAvailable(int,int,QVariant,QVariant,int)), Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(int, int, QVariant, int, QString)), receiver, - SLOT(nxmRequestFailed(int, int, QVariant, int, QString)), Qt::UniqueConnection); + connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), + receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); - nextRequest(); - return requestInfo.m_ID; + nextRequest(); + return requestInfo.m_ID; } -int NexusInterface::requestToggleEndorsement(int modID, bool endorse, QObject* receiver, QVariant userData, - const QString& subModule, MOBase::IPluginGame const* game) { - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLEENDORSEMENT, userData, subModule, game); - requestInfo.m_Endorse = endorse; - m_RequestQueue.enqueue(requestInfo); - connect(this, SIGNAL(nxmEndorsementToggled(int, QVariant, QVariant, int)), receiver, - SLOT(nxmEndorsementToggled(int, QVariant, QVariant, int)), Qt::UniqueConnection); +int NexusInterface::requestToggleEndorsement(int modID, bool endorse, QObject *receiver, QVariant userData, + const QString &subModule, MOBase::IPluginGame const *game) +{ + NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLEENDORSEMENT, userData, subModule, game); + requestInfo.m_Endorse = endorse; + m_RequestQueue.enqueue(requestInfo); + + connect(this, SIGNAL(nxmEndorsementToggled(int,QVariant,QVariant,int)), + receiver, SLOT(nxmEndorsementToggled(int,QVariant,QVariant,int)), Qt::UniqueConnection); - connect(this, SIGNAL(nxmRequestFailed(int, int, QVariant, int, QString)), receiver, - SLOT(nxmRequestFailed(int, int, QVariant, int, QString)), Qt::UniqueConnection); + connect(this, SIGNAL(nxmRequestFailed(int,int,QVariant,int,QString)), + receiver, SLOT(nxmRequestFailed(int,int,QVariant,int,QString)), Qt::UniqueConnection); - nextRequest(); - return requestInfo.m_ID; + nextRequest(); + return requestInfo.m_ID; } -bool NexusInterface::requiresLogin(const NXMRequestInfo& info) { - return (info.m_Type == NXMRequestInfo::TYPE_TOGGLEENDORSEMENT) || (info.m_Type == NXMRequestInfo::TYPE_DOWNLOADURL); +bool NexusInterface::requiresLogin(const NXMRequestInfo &info) +{ + return (info.m_Type == NXMRequestInfo::TYPE_TOGGLEENDORSEMENT) + || (info.m_Type == NXMRequestInfo::TYPE_DOWNLOADURL); } -void NexusInterface::cleanup() { - // delete m_AccessManager; - // delete m_DiskCache; - m_AccessManager = nullptr; - m_DiskCache = nullptr; +void NexusInterface::cleanup() +{ +// delete m_AccessManager; +// delete m_DiskCache; + m_AccessManager = nullptr; + m_DiskCache = nullptr; } -void NexusInterface::clearCache() { - m_DiskCache->clear(); - m_AccessManager->clearCookies(); +void NexusInterface::clearCache() +{ + m_DiskCache->clear(); + m_AccessManager->clearCookies(); } -void NexusInterface::nextRequest() { - if ((m_ActiveRequest.size() >= MAX_ACTIVE_DOWNLOADS) || m_RequestQueue.isEmpty()) { - return; - } +void NexusInterface::nextRequest() +{ + if ((m_ActiveRequest.size() >= MAX_ACTIVE_DOWNLOADS) + || m_RequestQueue.isEmpty()) { + return; + } - if (requiresLogin(m_RequestQueue.head()) && !getAccessManager()->loggedIn()) { - if (!getAccessManager()->loginAttempted()) { - emit needLogin(); - return; - } else if (getAccessManager()->loginWaiting()) { - return; - } + if (requiresLogin(m_RequestQueue.head()) && !getAccessManager()->loggedIn()) { + if (!getAccessManager()->loginAttempted()) { + emit needLogin(); + return; + } else if (getAccessManager()->loginWaiting()) { + return; } - - NXMRequestInfo info = m_RequestQueue.dequeue(); - info.m_Timeout = new QTimer(this); - info.m_Timeout->setInterval(60000); - - QString url; - if (!info.m_Reroute) { - bool hasParams = false; - switch (info.m_Type) { - case NXMRequestInfo::TYPE_DESCRIPTION: { - url = QString("%1/Mods/%2/").arg(info.m_URL).arg(info.m_ModID); - } break; - case NXMRequestInfo::TYPE_FILES: { - url = QString("%1/Files/indexfrommod/%2/").arg(info.m_URL).arg(info.m_ModID); - } break; - case NXMRequestInfo::TYPE_FILEINFO: { - url = QString("%1/Files/%2/").arg(info.m_URL).arg(info.m_FileID); - } break; - case NXMRequestInfo::TYPE_DOWNLOADURL: { - url = QString("%1/Files/download/%2").arg(info.m_URL).arg(info.m_FileID); - } break; - case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { - url = - QString("%1/Mods/toggleendorsement/%2?lvote=%3").arg(info.m_URL).arg(info.m_ModID).arg(!info.m_Endorse); - hasParams = true; - } break; - case NXMRequestInfo::TYPE_GETUPDATES: { - QString modIDList = VectorJoin(info.m_ModIDList, ","); - modIDList = "[" + modIDList + "]"; - url = QString("%1/Mods/GetUpdates?ModList=%2").arg(info.m_URL).arg(modIDList); - hasParams = true; - } break; - } - url.append(QString("%1game_id=%2").arg(hasParams ? '&' : '?').arg(info.m_NexusGameID)); - } else { - url = info.m_URL; + } + + NXMRequestInfo info = m_RequestQueue.dequeue(); + info.m_Timeout = new QTimer(this); + info.m_Timeout->setInterval(60000); + + QString url; + if (!info.m_Reroute) { + bool hasParams = false; + switch (info.m_Type) { + case NXMRequestInfo::TYPE_DESCRIPTION: { + url = QString("%1/Mods/%2/").arg(info.m_URL).arg(info.m_ModID); + } break; + case NXMRequestInfo::TYPE_FILES: { + url = QString("%1/Files/indexfrommod/%2/").arg(info.m_URL).arg(info.m_ModID); + } break; + case NXMRequestInfo::TYPE_FILEINFO: { + url = QString("%1/Files/%2/").arg(info.m_URL).arg(info.m_FileID); + } break; + case NXMRequestInfo::TYPE_DOWNLOADURL: { + url = QString("%1/Files/download/%2").arg(info.m_URL).arg(info.m_FileID); + } break; + case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { + url = QString("%1/Mods/toggleendorsement/%2?lvote=%3").arg(info.m_URL).arg(info.m_ModID).arg(!info.m_Endorse); + hasParams = true; + } break; + case NXMRequestInfo::TYPE_GETUPDATES: { + QString modIDList = VectorJoin(info.m_ModIDList, ","); + modIDList = "[" + modIDList + "]"; + url = QString("%1/Mods/GetUpdates?ModList=%2").arg(info.m_URL).arg(modIDList); + hasParams = true; + } break; } - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/xml"); - request.setRawHeader("User-Agent", m_AccessManager->userAgent(info.m_SubModule).toUtf8()); - - info.m_Reply = m_AccessManager->get(request); - - connect(info.m_Reply, SIGNAL(finished()), this, SLOT(requestFinished())); - connect(info.m_Reply, SIGNAL(error(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) { emit requestNXMDownload(url); } - -void NexusInterface::requestFinished(std::list::iterator iter) { - QNetworkReply* reply = iter->m_Reply; - - if (reply->error() != QNetworkReply::NoError) { - qWarning("request failed: %s", reply->errorString().toUtf8().constData()); - emit nxmRequestFailed(iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->errorString()); + url.append(QString("%1game_id=%2").arg(hasParams ? '&' : '?').arg(info.m_NexusGameID)); + } else { + url = info.m_URL; + } + QNetworkRequest request(url); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/xml"); + request.setRawHeader("User-Agent", m_AccessManager->userAgent(info.m_SubModule).toUtf8()); + + info.m_Reply = m_AccessManager->get(request); + + connect(info.m_Reply, SIGNAL(finished()), this, SLOT(requestFinished())); + connect(info.m_Reply, SIGNAL(error(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) +{ + emit requestNXMDownload(url); +} + +void NexusInterface::requestFinished(std::list::iterator iter) +{ + QNetworkReply *reply = iter->m_Reply; + + if (reply->error() != QNetworkReply::NoError) { + qWarning("request failed: %s", reply->errorString().toUtf8().constData()); + emit nxmRequestFailed(iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, reply->errorString()); + } 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_Reroute = true; + m_RequestQueue.enqueue(*iter); + nextRequest(); + return; + } + QByteArray data = reply->readAll(); + if (data.isNull() || data.isEmpty() || (strcmp(data.constData(), "null") == 0)) { + QString nexusError(reply->rawHeader("NexusErrorInfo")); + if (nexusError.length() == 0) { + nexusError = tr("empty response"); + } + qDebug("nexus error: %s", qPrintable(nexusError)); + emit nxmRequestFailed(iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, nexusError); } 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_Reroute = true; - m_RequestQueue.enqueue(*iter); - nextRequest(); - return; - } - QByteArray data = reply->readAll(); - if (data.isNull() || data.isEmpty() || (strcmp(data.constData(), "null") == 0)) { - QString nexusError(reply->rawHeader("NexusErrorInfo")); - if (nexusError.length() == 0) { - nexusError = tr("empty response"); - } - qDebug("nexus error: %s", qPrintable(nexusError)); - emit nxmRequestFailed(iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, nexusError); - } else { - bool ok; - QVariant result = QtJson::parse(data, ok); - if (result.isValid() && ok) { - switch (iter->m_Type) { - case NXMRequestInfo::TYPE_DESCRIPTION: { - emit nxmDescriptionAvailable(iter->m_ModID, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_FILES: { - emit nxmFilesAvailable(iter->m_ModID, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_FILEINFO: { - emit nxmFileInfoAvailable(iter->m_ModID, iter->m_FileID, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_DOWNLOADURL: { - emit nxmDownloadURLsAvailable(iter->m_ModID, iter->m_FileID, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_GETUPDATES: { - emit nxmUpdatesAvailable(iter->m_ModIDList, iter->m_UserData, result, iter->m_ID); - } break; - case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { - emit nxmEndorsementToggled(iter->m_ModID, iter->m_UserData, result, iter->m_ID); - } break; - } - } else { - emit nxmRequestFailed(iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, - tr("invalid response")); - } + bool ok; + QVariant result = QtJson::parse(data, ok); + if (result.isValid() && ok) { + switch (iter->m_Type) { + case NXMRequestInfo::TYPE_DESCRIPTION: { + emit nxmDescriptionAvailable(iter->m_ModID, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_FILES: { + emit nxmFilesAvailable(iter->m_ModID, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_FILEINFO: { + emit nxmFileInfoAvailable(iter->m_ModID, iter->m_FileID, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_DOWNLOADURL: { + emit nxmDownloadURLsAvailable(iter->m_ModID, iter->m_FileID, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_GETUPDATES: { + emit nxmUpdatesAvailable(iter->m_ModIDList, iter->m_UserData, result, iter->m_ID); + } break; + case NXMRequestInfo::TYPE_TOGGLEENDORSEMENT: { + emit nxmEndorsementToggled(iter->m_ModID, iter->m_UserData, result, iter->m_ID); + } break; } + } else { + emit nxmRequestFailed(iter->m_ModID, iter->m_FileID, iter->m_UserData, iter->m_ID, tr("invalid response")); + } } -} - -void NexusInterface::requestFinished() { - 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(); - requestFinished(iter); - iter->m_Reply->deleteLater(); - m_ActiveRequest.erase(iter); - nextRequest(); - return; - } + } +} + + +void NexusInterface::requestFinished() +{ + 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(); + requestFinished(iter); + iter->m_Reply->deleteLater(); + m_ActiveRequest.erase(iter); + nextRequest(); + return; } + } } -void NexusInterface::requestError(QNetworkReply::NetworkError) { - QNetworkReply* reply = qobject_cast(sender()); - if (reply == nullptr) { - qWarning("invalid sender type"); - return; - } - qCritical("request (%s) error: %s (%d)", qPrintable(reply->url().toString()), qPrintable(reply->errorString()), - reply->error()); +void NexusInterface::requestError(QNetworkReply::NetworkError) +{ + QNetworkReply *reply = qobject_cast(sender()); + if (reply == nullptr) { + qWarning("invalid sender type"); + return; + } + + qCritical("request (%s) error: %s (%d)", + qPrintable(reply->url().toString()), + qPrintable(reply->errorString()), + reply->error()); } -void NexusInterface::requestTimeout() { - QTimer* timer = qobject_cast(sender()); - if (timer == nullptr) { - qWarning("invalid sender type"); - return; - } - 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(); - return; - } + +void NexusInterface::requestTimeout() +{ + QTimer *timer = qobject_cast(sender()); + if (timer == nullptr) { + qWarning("invalid sender type"); + return; + } + 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(); + return; } + } } -void NexusInterface::managedGameChanged(IPluginGame const* game) { m_Game = game; } +void NexusInterface::managedGameChanged(IPluginGame const *game) +{ + m_Game = game; +} namespace { -QString get_management_url(MOBase::IPluginGame const* game) { + QString get_management_url(MOBase::IPluginGame const *game) + { return "http://nmm.nexusmods.com/" + game->gameNexusName().toLower(); -} -} // namespace - -NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID, NexusInterface::NXMRequestInfo::Type type, QVariant userData, - const QString& subModule, MOBase::IPluginGame const* game) - : m_ModID(modID), m_FileID(0), m_Reply(nullptr), m_Type(type), m_UserData(userData), m_Timeout(nullptr), - m_Reroute(false), m_ID(s_NextID.fetchAndAddAcquire(1)), m_URL(get_management_url(game)), m_SubModule(subModule), - m_NexusGameID(game->nexusGameID()), m_Endorse(false) {} - -NexusInterface::NXMRequestInfo::NXMRequestInfo(std::vector modIDList, NexusInterface::NXMRequestInfo::Type type, - QVariant userData, const QString& subModule, - MOBase::IPluginGame const* game) - : m_ModID(-1), m_ModIDList(modIDList), m_FileID(0), m_Reply(nullptr), m_Type(type), m_UserData(userData), - m_Timeout(nullptr), m_Reroute(false), m_ID(s_NextID.fetchAndAddAcquire(1)), m_URL(get_management_url(game)), - m_SubModule(subModule), m_NexusGameID(game->nexusGameID()), m_Endorse(false) {} - -NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID, int fileID, NexusInterface::NXMRequestInfo::Type type, - QVariant userData, const QString& subModule, - MOBase::IPluginGame const* game) - : m_ModID(modID), m_FileID(fileID), m_Reply(nullptr), m_Type(type), m_UserData(userData), m_Timeout(nullptr), - m_Reroute(false), m_ID(s_NextID.fetchAndAddAcquire(1)), m_URL(get_management_url(game)), m_SubModule(subModule), - m_NexusGameID(game->nexusGameID()), m_Endorse(false) {} + } +} + +NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID + , NexusInterface::NXMRequestInfo::Type type + , QVariant userData + , const QString &subModule + , MOBase::IPluginGame const *game + ) + : m_ModID(modID) + , m_FileID(0) + , m_Reply(nullptr) + , m_Type(type) + , m_UserData(userData) + , m_Timeout(nullptr) + , m_Reroute(false) + , m_ID(s_NextID.fetchAndAddAcquire(1)) + , m_URL(get_management_url(game)) + , m_SubModule(subModule) + , m_NexusGameID(game->nexusGameID()) + , m_Endorse(false) +{} + +NexusInterface::NXMRequestInfo::NXMRequestInfo(std::vector modIDList + , NexusInterface::NXMRequestInfo::Type type + , QVariant userData + , const QString &subModule + , MOBase::IPluginGame const *game + ) + : m_ModID(-1) + , m_ModIDList(modIDList) + , m_FileID(0) + , m_Reply(nullptr) + , m_Type(type) + , m_UserData(userData) + , m_Timeout(nullptr) + , m_Reroute(false) + , m_ID(s_NextID.fetchAndAddAcquire(1)) + , m_URL(get_management_url(game)) + , m_SubModule(subModule) + , m_NexusGameID(game->nexusGameID()) + , m_Endorse(false) +{} + +NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID + , int fileID + , NexusInterface::NXMRequestInfo::Type type + , QVariant userData + , const QString &subModule + , MOBase::IPluginGame const *game + ) + : m_ModID(modID) + , m_FileID(fileID) + , m_Reply(nullptr) + , m_Type(type) + , m_UserData(userData) + , m_Timeout(nullptr) + , m_Reroute(false) + , m_ID(s_NextID.fetchAndAddAcquire(1)) + , m_URL(get_management_url(game)) + , m_SubModule(subModule) + , m_NexusGameID(game->nexusGameID()) + , m_Endorse(false) +{} diff --git a/src/nexusinterface.h b/src/nexusinterface.h index ad0d40bf..9e7f3642 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -20,22 +20,20 @@ along with Mod Organizer. If not, see . #ifndef NEXUSINTERFACE_H #define NEXUSINTERFACE_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; @@ -48,70 +46,75 @@ class NXMAccessManager; * 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 { +class NexusBridge : public MOBase::IModRepositoryBridge +{ - Q_OBJECT + Q_OBJECT public: - NexusBridge(const QString& subModule = ""); - - /** - * @brief request description for a mod - * - * @param modID id of the mod caller is interested in - * @param userData user data to be returned with the result - * @param url the url to request from - **/ - virtual void requestDescription(int modID, QVariant userData); - - /** - * @brief request a list of the files belonging to a mod - * - * @param modID id of the mod caller is interested in - * @param userData user data to be returned with the result - **/ - virtual void requestFiles(int modID, QVariant userData); - - /** - * @brief request info about a single file of a mod - * - * @param modID id of the mod caller is interested in - * @param fileID id of the file the caller is interested in - * @param userData user data to be returned with the result - **/ - virtual void requestFileInfo(int modID, int fileID, QVariant userData); - - /** - * @brief request the download url of a file - * - * @param modID id of the mod caller is interested in - * @param fileID id of the file the caller is interested in - * @param userData user data to be returned with the result - **/ - virtual void requestDownloadURL(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(int modID, bool endorse, QVariant userData); + + NexusBridge(const QString &subModule = ""); + + /** + * @brief request description for a mod + * + * @param modID id of the mod caller is interested in + * @param userData user data to be returned with the result + * @param url the url to request from + **/ + virtual void requestDescription(int modID, QVariant userData); + + /** + * @brief request a list of the files belonging to a mod + * + * @param modID id of the mod caller is interested in + * @param userData user data to be returned with the result + **/ + virtual void requestFiles(int modID, QVariant userData); + + /** + * @brief request info about a single file of a mod + * + * @param modID id of the mod caller is interested in + * @param fileID id of the file the caller is interested in + * @param userData user data to be returned with the result + **/ + virtual void requestFileInfo(int modID, int fileID, QVariant userData); + + /** + * @brief request the download url of a file + * + * @param modID id of the mod caller is interested in + * @param fileID id of the file the caller is interested in + * @param userData user data to be returned with the result + **/ + virtual void requestDownloadURL(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(int modID, bool endorse, QVariant userData); public slots: - void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - void nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString& errorMessage); + void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorMessage); private: - NexusInterface* m_Interface; - QString m_SubModule; - std::set m_RequestIDs; + + NexusInterface *m_Interface; + QString m_SubModule; + std::set m_RequestIDs; + }; + /** * @brief Makes asynchronous requests to the nexus API * @@ -119,316 +122,325 @@ private: * 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 +class NexusInterface : public QObject +{ + Q_OBJECT public: - ~NexusInterface(); - - static NexusInterface* instance(); - - /** - * @return the access manager object used to connect to nexus - **/ - NXMAccessManager* getAccessManager(); - - /** - * @brief cleanup this interface. this is destructive, afterwards it can't be used again - */ - void cleanup(); - - /** - * @brief clear webcache and cookies associated with this access manager - */ - void clearCache(); - - /** - * @brief request description for a mod - * - * @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(int modID, QObject* receiver, QVariant userData, const QString& subModule) { - return requestDescription(modID, receiver, userData, subModule, m_Game); - } - - /** - * @brief request description for a mod - * - * @param modID id of the mod caller is interested in - * @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(int modID, QObject* receiver, QVariant userData, const QString& subModule, - MOBase::IPluginGame const* game); - - /** - * @brief request nexus descriptions for multiple mods at once - * @param modIDs a list of ids of mods the 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 requestUpdates(const std::vector& modIDs, QObject* receiver, QVariant userData, const QString& subModule) { - return requestUpdates(modIDs, receiver, userData, subModule, m_Game); - } - - /** - * @brief request nexus descriptions for multiple mods at once - * @param modIDs a list of ids of mods the caller is interested in - * @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 the game with which the mods are associated - * @return int an id to identify the request - */ - int requestUpdates(const std::vector& modIDs, QObject* receiver, QVariant userData, const QString& subModule, - MOBase::IPluginGame const* game); - - /** - * @brief request a list of the files belonging to a mod - * - * @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(int modID, QObject* receiver, QVariant userData, const QString& subModule) { - return requestFiles(modID, receiver, userData, subModule, m_Game); - } - - /** - * @brief request a list of the files belonging to a mod - * - * @param modID id of the mod caller is interested in - * @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(int modID, QObject* receiver, QVariant userData, const QString& subModule, - MOBase::IPluginGame const* game); - - /** - * @brief request info about a single file of a mod - * - * @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 userData user data to be returned with the result - * @return int an id to identify the request - **/ - int requestFileInfo(int modID, int fileID, QObject* receiver, QVariant userData, const QString& subModule) { - return requestFileInfo(modID, fileID, receiver, userData, subModule, m_Game); - } - - /** - * @brief request info about a single file of a mod - * - * @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 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 requestFileInfo(int modID, int fileID, QObject* receiver, QVariant userData, const QString& subModule, - MOBase::IPluginGame const* game); - - /** - * @brief request the download url of a file - * - * @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 userData user data to be returned with the result - * @return int an id to identify the request - **/ - int requestDownloadURL(int modID, int fileID, QObject* receiver, QVariant userData, const QString& subModule) { - return requestDownloadURL(modID, fileID, receiver, userData, subModule, m_Game); - } - - /** - * @brief request the download url of a file - * - * @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 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(int modID, int fileID, QObject* receiver, QVariant userData, const QString& subModule, - MOBase::IPluginGame const* game); - - /** - * @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 userData user data to be returned with the result - * @return int an id to identify the request - */ - int requestToggleEndorsement(int modID, bool endorse, QObject* receiver, QVariant userData, - const QString& subModule) { - return requestToggleEndorsement(modID, endorse, receiver, userData, subModule, m_Game); - } - - /** - * @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 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(int modID, bool endorse, QObject* receiver, QVariant userData, - const QString& subModule, MOBase::IPluginGame const* game); - - /** - * @param directory the directory to store cache files - **/ - void setCacheDirectory(const QString& directory); - - /** - * MO has to send a "Nexus Client Vx.y.z" as part of the user agent to be allowed to use the API - * @param nmmVersion the version of nmm to impersonate - **/ - void setNMMVersion(const QString& nmmVersion); - - /** - * @brief called when the log-in completes. This was, requests waiting for the log-in can be run - */ - void loginCompleted(); + + ~NexusInterface(); + + static NexusInterface *instance(); + + /** + * @return the access manager object used to connect to nexus + **/ + NXMAccessManager *getAccessManager(); + + /** + * @brief cleanup this interface. this is destructive, afterwards it can't be used again + */ + void cleanup(); + + /** + * @brief clear webcache and cookies associated with this access manager + */ + void clearCache(); + + /** + * @brief request description for a mod + * + * @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(int modID, QObject *receiver, QVariant userData, const QString &subModule) + { + return requestDescription(modID, receiver, userData, subModule, m_Game); + } + + /** + * @brief request description for a mod + * + * @param modID id of the mod caller is interested in + * @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(int modID, QObject *receiver, QVariant userData, const QString &subModule, + MOBase::IPluginGame const *game); + + /** + * @brief request nexus descriptions for multiple mods at once + * @param modIDs a list of ids of mods the 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 requestUpdates(const std::vector &modIDs, QObject *receiver, QVariant userData, const QString &subModule) + { + return requestUpdates(modIDs, receiver, userData, subModule, m_Game); + } + + /** + * @brief request nexus descriptions for multiple mods at once + * @param modIDs a list of ids of mods the caller is interested in + * @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 the game with which the mods are associated + * @return int an id to identify the request + */ + int requestUpdates(const std::vector &modIDs, QObject *receiver, QVariant userData, const QString &subModule, + MOBase::IPluginGame const *game); + + /** + * @brief request a list of the files belonging to a mod + * + * @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(int modID, QObject *receiver, QVariant userData, const QString &subModule) + { + return requestFiles(modID, receiver, userData, subModule, m_Game); + } + + + /** + * @brief request a list of the files belonging to a mod + * + * @param modID id of the mod caller is interested in + * @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(int modID, QObject *receiver, QVariant userData, const QString &subModule, + MOBase::IPluginGame const *game); + + /** + * @brief request info about a single file of a mod + * + * @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 userData user data to be returned with the result + * @return int an id to identify the request + **/ + int requestFileInfo(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule) + { + return requestFileInfo(modID, fileID, receiver, userData, subModule, m_Game); + } + + /** + * @brief request info about a single file of a mod + * + * @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 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 requestFileInfo(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule, + MOBase::IPluginGame const *game); + + /** + * @brief request the download url of a file + * + * @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 userData user data to be returned with the result + * @return int an id to identify the request + **/ + int requestDownloadURL(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule) + { + return requestDownloadURL(modID, fileID, receiver, userData, subModule, m_Game); + } + + /** + * @brief request the download url of a file + * + * @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 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(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); + + /** + * @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 userData user data to be returned with the result + * @return int an id to identify the request + */ + int requestToggleEndorsement(int modID, bool endorse, QObject *receiver, QVariant userData, const QString &subModule) + { + return requestToggleEndorsement(modID, endorse, receiver, userData, subModule, m_Game); + } + + /** + * @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 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(int modID, bool endorse, QObject *receiver, QVariant userData, const QString &subModule, + MOBase::IPluginGame const *game); + + /** + * @param directory the directory to store cache files + **/ + void setCacheDirectory(const QString &directory); + + /** + * MO has to send a "Nexus Client Vx.y.z" as part of the user agent to be allowed to use the API + * @param nmmVersion the version of nmm to impersonate + **/ + void setNMMVersion(const QString &nmmVersion); + + /** + * @brief called when the log-in completes. This was, requests waiting for the log-in can be run + */ + void loginCompleted(); 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 - */ - static void interpretNexusFileName(const QString& fileName, QString& modName, int& modID, bool query); - - /** - * @brief get the currently managed game - */ - 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; - - /** - * @brief Get the nexus page for the current game - * - * Arguably, this should optionally take a gameplugin pointer - */ - QString getGameURL() const; - - /** - * @brief Get the URL for the mod web page - * @param modID - */ - QString getModURL(int modID) const; - - /** - * @brief Checks if the specified URL might correspond to a nexus mod - * @param modID - * @param url - * @return - */ - bool isModURL(int modID, QString const& url) const; + + /** + * @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 + */ + static void interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query); + + /** + * @brief get the currently managed game + */ + 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; + + /** + * @brief Get the nexus page for the current game + * + * Arguably, this should optionally take a gameplugin pointer + */ + QString getGameURL() const; + + /** + * @brief Get the URL for the mod web page + * @param modID + */ + QString getModURL(int modID) const; + + /** + * @brief Checks if the specified URL might correspond to a nexus mod + * @param modID + * @param url + * @return + */ + bool isModURL(int modID, QString const &url) const; signals: - void requestNXMDownload(const QString& url); + void requestNXMDownload(const QString &url); - void needLogin(); + void needLogin(); - void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmUpdatesAvailable(const std::vector& modIDs, QVariant userData, QVariant resultData, int requestID); - void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - void nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString& errorString); + void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmUpdatesAvailable(const std::vector &modIDs, QVariant userData, QVariant resultData, int requestID); + void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); + void nxmEndorsementToggled(int modID, QVariant userData, QVariant resultData, int requestID); + void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorString); public slots: - void managedGameChanged(MOBase::IPluginGame const* game); + void managedGameChanged(MOBase::IPluginGame const *game); private slots: - void requestFinished(); - void requestError(QNetworkReply::NetworkError error); - void requestTimeout(); + void requestFinished(); + void requestError(QNetworkReply::NetworkError error); + void requestTimeout(); - void downloadRequestedNXM(const QString& url); + void downloadRequestedNXM(const QString &url); - void fakeFiles(); + void fakeFiles(); private: - struct NXMRequestInfo { - int m_ModID; - std::vector m_ModIDList; - int m_FileID; - QNetworkReply* m_Reply; - enum Type { - TYPE_DESCRIPTION, - TYPE_FILES, - TYPE_FILEINFO, - TYPE_DOWNLOADURL, - TYPE_TOGGLEENDORSEMENT, - TYPE_GETUPDATES - } m_Type; - QVariant m_UserData; - QTimer* m_Timeout; - QString m_URL; - QString m_SubModule; - int m_NexusGameID; - bool m_Reroute; - int m_ID; - int m_Endorse; - - NXMRequestInfo(int modID, Type type, QVariant userData, const QString& subModule, - MOBase::IPluginGame const* game); - NXMRequestInfo(std::vector modIDList, 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); - - private: - static QAtomicInt s_NextID; - }; - - static const int MAX_ACTIVE_DOWNLOADS = 2; + + struct NXMRequestInfo { + int m_ModID; + std::vector m_ModIDList; + int m_FileID; + QNetworkReply *m_Reply; + enum Type { + TYPE_DESCRIPTION, + TYPE_FILES, + TYPE_FILEINFO, + TYPE_DOWNLOADURL, + TYPE_TOGGLEENDORSEMENT, + TYPE_GETUPDATES + } m_Type; + QVariant m_UserData; + QTimer *m_Timeout; + QString m_URL; + QString m_SubModule; + int m_NexusGameID; + bool m_Reroute; + int m_ID; + int m_Endorse; + + NXMRequestInfo(int modID, Type type, QVariant userData, const QString &subModule, MOBase::IPluginGame const *game); + NXMRequestInfo(std::vector modIDList, 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); + + private: + static QAtomicInt s_NextID; + }; + + static const int MAX_ACTIVE_DOWNLOADS = 2; private: - NexusInterface(); - void nextRequest(); - void requestFinished(std::list::iterator iter); - bool requiresLogin(const NXMRequestInfo& info); - QString getOldModsURL() const; + + NexusInterface(); + void nextRequest(); + void requestFinished(std::list::iterator iter); + bool requiresLogin(const NXMRequestInfo &info); + QString getOldModsURL() const; private: - QNetworkDiskCache* m_DiskCache; - NXMAccessManager* m_AccessManager; + QNetworkDiskCache *m_DiskCache; + + NXMAccessManager *m_AccessManager; + + std::list m_ActiveRequest; + QQueue m_RequestQueue; - std::list m_ActiveRequest; - QQueue m_RequestQueue; + MOBase::VersionInfo m_MOVersion; + QString m_NMMVersion; - MOBase::VersionInfo m_MOVersion; - QString m_NMMVersion; + MOBase::IPluginGame const *m_Game; - MOBase::IPluginGame const* m_Game; }; #endif // NEXUSINTERFACE_H diff --git a/src/noeditdelegate.cpp b/src/noeditdelegate.cpp index ad7a378a..d5147b9f 100644 --- a/src/noeditdelegate.cpp +++ b/src/noeditdelegate.cpp @@ -1,7 +1,10 @@ #include "noeditdelegate.h" -NoEditDelegate::NoEditDelegate(QObject* parent) : QStyledItemDelegate(parent) {} +NoEditDelegate::NoEditDelegate(QObject *parent) + : QStyledItemDelegate(parent) +{ +} -QWidget* NoEditDelegate::createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const { - return nullptr; +QWidget *NoEditDelegate::createEditor(QWidget*, const QStyleOptionViewItem&, const QModelIndex&) const { + return nullptr; } diff --git a/src/noeditdelegate.h b/src/noeditdelegate.h index c269a4de..6fd5ba76 100644 --- a/src/noeditdelegate.h +++ b/src/noeditdelegate.h @@ -3,10 +3,10 @@ #include -class NoEditDelegate : public QStyledItemDelegate { +class NoEditDelegate: public QStyledItemDelegate { public: - NoEditDelegate(QObject* parent = nullptr); - virtual QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const; + 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 b299db84..17c50e35 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -21,283 +21,331 @@ along with Mod Organizer. If not, see . #include "iplugingame.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; namespace { -QString const Nexus_Management_URL("http://nmm.nexusmods.com"); + QString const Nexus_Management_URL("http://nmm.nexusmods.com"); } // unfortunately Nexus doesn't seem to document these states, all I know is all these listed // are considered premium (27 should be lifetime premium) -const std::set NXMAccessManager::s_PremiumAccountStates{4, 6, 13, 27, 31, 32}; - -NXMAccessManager::NXMAccessManager(QObject* parent, const QString& moVersion) - : QNetworkAccessManager(parent), m_LoginReply(nullptr), m_MOVersion(moVersion) { - m_LoginTimeout.setSingleShot(true); - m_LoginTimeout.setInterval(30000); - setCookieJar(new PersistentCookieJar( - QDir::fromNativeSeparators(Settings::instance().getCacheDirectory() + "/nexus_cookies.dat"))); - - if (networkAccessible() == QNetworkAccessManager::UnknownAccessibility) { - // why is this necessary all of a sudden? - setNetworkAccessible(QNetworkAccessManager::Accessible); - } +const std::set NXMAccessManager::s_PremiumAccountStates { 4, 6, 13, 27, 31, 32 }; + + +NXMAccessManager::NXMAccessManager(QObject *parent, const QString &moVersion) + : QNetworkAccessManager(parent) + , m_LoginReply(nullptr) + , m_MOVersion(moVersion) +{ + m_LoginTimeout.setSingleShot(true); + m_LoginTimeout.setInterval(30000); + setCookieJar(new PersistentCookieJar( + QDir::fromNativeSeparators(Settings::instance().getCacheDirectory() + "/nexus_cookies.dat"))); + + if (networkAccessible() == QNetworkAccessManager::UnknownAccessibility) { + // why is this necessary all of a sudden? + setNetworkAccessible(QNetworkAccessManager::Accessible); + } } -NXMAccessManager::~NXMAccessManager() { - if (m_LoginReply != nullptr) { - m_LoginReply->deleteLater(); - m_LoginReply = nullptr; - } +NXMAccessManager::~NXMAccessManager() +{ + if (m_LoginReply != nullptr) { + m_LoginReply->deleteLater(); + m_LoginReply = nullptr; + } } -void NXMAccessManager::setNMMVersion(const QString& nmmVersion) { m_NMMVersion = nmmVersion; } - -QNetworkReply* NXMAccessManager::createRequest(QNetworkAccessManager::Operation operation, - const QNetworkRequest& request, QIODevice* device) { - if (request.url().scheme() != "nxm") { - return QNetworkAccessManager::createRequest(operation, request, device); - } - if (operation == GetOperation) { - emit requestNXMDownload(request.url().toString()); - - // eat the request, everything else will be done by the download manager - return QNetworkAccessManager::createRequest(QNetworkAccessManager::GetOperation, QNetworkRequest(QUrl())); - } else if (operation == PostOperation) { - return QNetworkAccessManager::createRequest(operation, request, device); - ; - } else { - return QNetworkAccessManager::createRequest(operation, request, device); - } +void NXMAccessManager::setNMMVersion(const QString &nmmVersion) +{ + m_NMMVersion = nmmVersion; } -void NXMAccessManager::showCookies() const { - QUrl url(Nexus_Management_URL + "/"); - for (const QNetworkCookie& cookie : cookieJar()->cookiesForUrl(url)) { - qDebug("%s - %s (expires: %s)", cookie.name().constData(), cookie.value().constData(), - qPrintable(cookie.expirationDate().toString())); - } +QNetworkReply *NXMAccessManager::createRequest( + QNetworkAccessManager::Operation operation, const QNetworkRequest &request, + QIODevice *device) +{ + if (request.url().scheme() != "nxm") { + return QNetworkAccessManager::createRequest(operation, request, device); + } + if (operation == GetOperation) { + emit requestNXMDownload(request.url().toString()); + + // eat the request, everything else will be done by the download manager + return QNetworkAccessManager::createRequest(QNetworkAccessManager::GetOperation, + QNetworkRequest(QUrl())); + } else if (operation == PostOperation) { + return QNetworkAccessManager::createRequest(operation, request, device);; + } else { + return QNetworkAccessManager::createRequest(operation, request, device); + } } -void NXMAccessManager::clearCookies() { - PersistentCookieJar* jar = qobject_cast(cookieJar()); - if (jar != nullptr) { - jar->clear(); - } else { - qWarning("failed to clear cookies, invalid cookie jar"); - } -} -void NXMAccessManager::startLoginCheck() { - if (hasLoginCookies()) { - qDebug("validating login cookies"); - QNetworkRequest request(Nexus_Management_URL + "/Sessions/?Validate"); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - request.setRawHeader("User-Agent", userAgent().toUtf8()); - - m_LoginReply = get(request); - m_LoginTimeout.start(); - m_LoginState = LOGIN_CHECKING; - connect(m_LoginReply, SIGNAL(finished()), this, SLOT(loginChecked())); - connect(m_LoginReply, SIGNAL(error(QNetworkReply::NetworkError)), this, - SLOT(loginError(QNetworkReply::NetworkError))); - } +void NXMAccessManager::showCookies() const +{ + QUrl url(Nexus_Management_URL + "/"); + for (const QNetworkCookie &cookie : cookieJar()->cookiesForUrl(url)) { + qDebug("%s - %s (expires: %s)", + cookie.name().constData(), cookie.value().constData(), + qPrintable(cookie.expirationDate().toString())); + } } -void NXMAccessManager::retrieveCredentials() { - qDebug("retrieving credentials"); +void NXMAccessManager::clearCookies() +{ + PersistentCookieJar *jar = qobject_cast(cookieJar()); + if (jar != nullptr) { + jar->clear(); + } else { + qWarning("failed to clear cookies, invalid cookie jar"); + } +} - QNetworkRequest request(Nexus_Management_URL + "/Core/Libs/Flamework/Entities/User?GetCredentials"); +void NXMAccessManager::startLoginCheck() +{ + if (hasLoginCookies()) { + qDebug("validating login cookies"); + QNetworkRequest request(Nexus_Management_URL + "/Sessions/?Validate"); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); request.setRawHeader("User-Agent", userAgent().toUtf8()); - QNetworkReply* reply = get(request); - QTimer timeout; - connect(&timeout, &QTimer::timeout, [reply]() { reply->deleteLater(); }); - timeout.start(); - - connect(reply, &QNetworkReply::finished, [reply, this]() { - QJsonDocument jdoc = QJsonDocument::fromJson(reply->readAll()); - QJsonArray credentialsData = jdoc.array(); - emit credentialsReceived(credentialsData.at(2).toString(), - s_PremiumAccountStates.find(credentialsData.at(1).toInt()) != - s_PremiumAccountStates.end()); - reply->deleteLater(); - }); - - connect(reply, static_cast(&QNetworkReply::error), - [=](QNetworkReply::NetworkError) { - qDebug("failed to retrieve account credentials: %s", qPrintable(reply->errorString())); - reply->deleteLater(); - }); + m_LoginReply = get(request); + m_LoginTimeout.start(); + m_LoginState = LOGIN_CHECKING; + connect(m_LoginReply, SIGNAL(finished()), this, SLOT(loginChecked())); + connect(m_LoginReply, SIGNAL(error(QNetworkReply::NetworkError)), + this, SLOT(loginError(QNetworkReply::NetworkError))); + } } -bool NXMAccessManager::loggedIn() const { - if (m_LoginState == LOGIN_CHECKING) { - QProgressDialog progress; - progress.setLabelText(tr("Verifying Nexus login")); - progress.show(); - while (m_LoginState == LOGIN_CHECKING) { - QCoreApplication::processEvents(); - QThread::msleep(100); - } - progress.hide(); - } - return m_LoginState == LOGIN_VALID; +void NXMAccessManager::retrieveCredentials() +{ + qDebug("retrieving credentials"); + + QNetworkRequest request(Nexus_Management_URL + "/Core/Libs/Flamework/Entities/User?GetCredentials"); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + request.setRawHeader("User-Agent", userAgent().toUtf8()); + + QNetworkReply *reply = get(request); + QTimer timeout; + connect(&timeout, &QTimer::timeout, [reply] () { + reply->deleteLater(); + }); + timeout.start(); + + connect(reply, &QNetworkReply::finished, [reply, this] () { + QJsonDocument jdoc = QJsonDocument::fromJson(reply->readAll()); + QJsonArray credentialsData = jdoc.array(); + emit credentialsReceived(credentialsData.at(2).toString(), + s_PremiumAccountStates.find(credentialsData.at(1).toInt()) + != s_PremiumAccountStates.end()); + reply->deleteLater(); + }); + + connect(reply, static_cast(&QNetworkReply::error), + [=] (QNetworkReply::NetworkError) { + qDebug("failed to retrieve account credentials: %s", qPrintable(reply->errorString())); + reply->deleteLater(); + }); } -void NXMAccessManager::refuseLogin() { m_LoginState = LOGIN_REFUSED; } - -bool NXMAccessManager::loginAttempted() const { return m_LoginState != LOGIN_NOT_CHECKED; } -bool NXMAccessManager::loginWaiting() const { return m_LoginReply != nullptr; } - -void NXMAccessManager::login(const QString& username, const QString& password) { - if (m_LoginReply != nullptr) { - return; +bool NXMAccessManager::loggedIn() const +{ + if (m_LoginState == LOGIN_CHECKING) { + QProgressDialog progress; + progress.setLabelText(tr("Verifying Nexus login")); + progress.show(); + while (m_LoginState == LOGIN_CHECKING) { + QCoreApplication::processEvents(); + QThread::msleep(100); } + progress.hide(); + } - if (m_LoginState == LOGIN_VALID) { - emit loginSuccessful(false); - return; - } - m_Username = username; - m_Password = password; - pageLogin(); + return m_LoginState == LOGIN_VALID; } -QString NXMAccessManager::userAgent(const QString& subModule) const { - QStringList comments; - comments << "compatible to Nexus Client v" + m_NMMVersion; - if (!subModule.isEmpty()) { - comments << "module: " + subModule; - } - return QString("Mod Organizer v%1 (%2)").arg(m_MOVersion, comments.join("; ")); +void NXMAccessManager::refuseLogin() +{ + m_LoginState = LOGIN_REFUSED; } -void NXMAccessManager::pageLogin() { - qDebug("logging %s in on Nexus", qPrintable(m_Username)); - QString requestString = - (Nexus_Management_URL + "/Sessions/?Login&uri=%1").arg(QString(QUrl::toPercentEncoding(Nexus_Management_URL))); - QNetworkRequest request(requestString); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); +bool NXMAccessManager::loginAttempted() const +{ + return m_LoginState != LOGIN_NOT_CHECKED; +} - QByteArray postDataQuery; - QUrlQuery postData; - postData.addQueryItem("username", m_Username); - postData.addQueryItem("password", QUrl::toPercentEncoding(m_Password)); - postDataQuery = postData.query(QUrl::EncodeReserved).toUtf8(); - request.setRawHeader("User-Agent", userAgent().toUtf8()); +bool NXMAccessManager::loginWaiting() const +{ + return m_LoginReply != nullptr; +} - m_ProgressDialog = new QProgressDialog(nullptr); - m_ProgressDialog->setLabelText(tr("Logging into Nexus")); - QList buttons = m_ProgressDialog->findChildren(); - buttons.at(0)->setEnabled(false); - m_ProgressDialog->show(); - QCoreApplication::processEvents(); // for some reason the whole app hangs during the login. This way the user has at - // least a little feedback - m_LoginReply = post(request, postDataQuery); - m_LoginTimeout.start(); - m_LoginState = LOGIN_CHECKING; - connect(m_LoginReply, SIGNAL(finished()), this, SLOT(loginFinished())); - connect(m_LoginReply, SIGNAL(error(QNetworkReply::NetworkError)), this, - SLOT(loginError(QNetworkReply::NetworkError))); +void NXMAccessManager::login(const QString &username, const QString &password) +{ + if (m_LoginReply != nullptr) { + return; + } + + if (m_LoginState == LOGIN_VALID) { + emit loginSuccessful(false); + return; + } + m_Username = username; + m_Password = password; + pageLogin(); } -void NXMAccessManager::loginTimeout() { - m_LoginReply->deleteLater(); - m_LoginReply = nullptr; - m_LoginAttempted = false; // this usually means we might have success later - m_Username.clear(); - m_Password.clear(); - m_LoginState = LOGIN_NOT_VALID; - emit loginFailed(tr("timeout")); +QString NXMAccessManager::userAgent(const QString &subModule) const +{ + QStringList comments; + comments << "compatible to Nexus Client v" + m_NMMVersion; + if (!subModule.isEmpty()) { + comments << "module: " + subModule; + } + + return QString("Mod Organizer v%1 (%2)").arg(m_MOVersion, comments.join("; ")); } -void NXMAccessManager::loginError(QNetworkReply::NetworkError) { - qDebug("login error"); - if (m_ProgressDialog != nullptr) { - m_ProgressDialog->deleteLater(); - m_ProgressDialog = nullptr; - } - m_Username.clear(); - m_Password.clear(); - m_LoginState = LOGIN_NOT_VALID; - if (m_LoginReply != nullptr) { - emit loginFailed(m_LoginReply->errorString()); - m_LoginReply->deleteLater(); - m_LoginReply = nullptr; - } else { - emit loginFailed(tr("Unknown error")); - } +void NXMAccessManager::pageLogin() +{ + qDebug("logging %s in on Nexus", qPrintable(m_Username)); + QString requestString = (Nexus_Management_URL + "/Sessions/?Login&uri=%1") + .arg(QString(QUrl::toPercentEncoding(Nexus_Management_URL))); + + QNetworkRequest request(requestString); + request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); + + QByteArray postDataQuery; + QUrlQuery postData; + postData.addQueryItem("username", m_Username); + postData.addQueryItem("password", QUrl::toPercentEncoding(m_Password)); + postDataQuery = postData.query(QUrl::EncodeReserved).toUtf8(); + + request.setRawHeader("User-Agent", userAgent().toUtf8()); + + m_ProgressDialog = new QProgressDialog(nullptr); + m_ProgressDialog->setLabelText(tr("Logging into Nexus")); + QList buttons = m_ProgressDialog->findChildren(); + buttons.at(0)->setEnabled(false); + m_ProgressDialog->show(); + QCoreApplication::processEvents(); // for some reason the whole app hangs during the login. This way the user has at least a little feedback + + m_LoginReply = post(request, postDataQuery); + m_LoginTimeout.start(); + m_LoginState = LOGIN_CHECKING; + connect(m_LoginReply, SIGNAL(finished()), this, SLOT(loginFinished())); + connect(m_LoginReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(loginError(QNetworkReply::NetworkError))); } -bool NXMAccessManager::hasLoginCookies() const { - QUrl url(Nexus_Management_URL + "/"); - QList cookies = cookieJar()->cookiesForUrl(url); - for (const QNetworkCookie& cookie : cookies) { - if (cookie.name() == "sid") { - return true; - } - } - return false; + +void NXMAccessManager::loginTimeout() +{ + m_LoginReply->deleteLater(); + m_LoginReply = nullptr; + m_LoginAttempted = false; // this usually means we might have success later + m_Username.clear(); + m_Password.clear(); + m_LoginState = LOGIN_NOT_VALID; + + emit loginFailed(tr("timeout")); } -void NXMAccessManager::loginFinished() { - if (m_ProgressDialog != nullptr) { - m_ProgressDialog->deleteLater(); - m_ProgressDialog = nullptr; - } +void NXMAccessManager::loginError(QNetworkReply::NetworkError) +{ + qDebug("login error"); + if (m_ProgressDialog != nullptr) { + m_ProgressDialog->deleteLater(); + m_ProgressDialog = nullptr; + } + m_Username.clear(); + m_Password.clear(); + m_LoginState = LOGIN_NOT_VALID; + + if (m_LoginReply != nullptr) { + emit loginFailed(m_LoginReply->errorString()); m_LoginReply->deleteLater(); m_LoginReply = nullptr; - m_Username.clear(); - m_Password.clear(); - - if (hasLoginCookies()) { - m_LoginState = LOGIN_VALID; - retrieveCredentials(); - emit loginSuccessful(true); - } else { - m_LoginState = LOGIN_NOT_VALID; - emit loginFailed(tr("Please check your password")); - } + } else { + emit loginFailed(tr("Unknown error")); + } } -void NXMAccessManager::loginChecked() { - QNetworkReply* reply = static_cast(sender()); - QByteArray data = reply->readAll(); - m_LoginState = data == "null" ? LOGIN_NOT_VALID : LOGIN_VALID; - if (m_LoginState == LOGIN_VALID) { - retrieveCredentials(); - } else { - qDebug("cookies seem to be invalid"); + +bool NXMAccessManager::hasLoginCookies() const +{ + QUrl url(Nexus_Management_URL + "/"); + QList cookies = cookieJar()->cookiesForUrl(url); + for (const QNetworkCookie &cookie : cookies) { + if (cookie.name() == "sid") { + return true; } - m_LoginReply->deleteLater(); - m_LoginReply = nullptr; + } + return false; +} + + +void NXMAccessManager::loginFinished() +{ + if (m_ProgressDialog != nullptr) { + m_ProgressDialog->deleteLater(); + m_ProgressDialog = nullptr; + } + + m_LoginReply->deleteLater(); + m_LoginReply = nullptr; + m_Username.clear(); + m_Password.clear(); + + if (hasLoginCookies()) { + m_LoginState = LOGIN_VALID; + retrieveCredentials(); + emit loginSuccessful(true); + } else { + m_LoginState = LOGIN_NOT_VALID; + emit loginFailed(tr("Please check your password")); + } +} + + +void NXMAccessManager::loginChecked() +{ + QNetworkReply *reply = static_cast(sender()); + QByteArray data = reply->readAll(); + m_LoginState = data == "null" ? LOGIN_NOT_VALID + : LOGIN_VALID; + if (m_LoginState == LOGIN_VALID) { + retrieveCredentials(); + } else { + qDebug("cookies seem to be invalid"); + } + m_LoginReply->deleteLater(); + m_LoginReply = nullptr; } diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index e9c026f0..c58c4cc3 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -20,107 +20,114 @@ along with Mod Organizer. If not, see . #ifndef NXMACCESSMANAGER_H #define NXMACCESSMANAGER_H + #include +#include #include #include -#include #include -namespace MOBase { -class IPluginGame; -} +namespace MOBase { class IPluginGame; } /** * @brief access manager extended to handle nxm links **/ -class NXMAccessManager : public QNetworkAccessManager { - Q_OBJECT +class NXMAccessManager : public QNetworkAccessManager +{ + Q_OBJECT public: - explicit NXMAccessManager(QObject* parent, const QString& moVersion); - ~NXMAccessManager(); + explicit NXMAccessManager(QObject *parent, const QString &moVersion); - void setNMMVersion(const QString& nmmVersion); + ~NXMAccessManager(); - bool loggedIn() const; + void setNMMVersion(const QString &nmmVersion); - bool loginAttempted() const; - bool loginWaiting() const; + bool loggedIn() const; - void login(const QString& username, const QString& password); + bool loginAttempted() const; + bool loginWaiting() const; - void showCookies() const; + void login(const QString &username, const QString &password); - void clearCookies(); + void showCookies() const; - QString userAgent(const QString& subModule = QString()) const; + void clearCookies(); - void startLoginCheck(); + QString userAgent(const QString &subModule = QString()) const; - void refuseLogin(); + void startLoginCheck(); + + void refuseLogin(); signals: - /** - * @brief emitted when a nxm:// link is opened - * - * @param url the nxm-link - **/ - void requestNXMDownload(const QString& url); + /** + * @brief emitted when a nxm:// link is opened + * + * @param url the nxm-link + **/ + 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 - **/ - void loginSuccessful(bool necessary); + /** + * @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 + **/ + void loginSuccessful(bool necessary); - void loginFailed(const QString& message); + void loginFailed(const QString &message); - void credentialsReceived(const QString& userName, bool premium); + void credentialsReceived(const QString &userName, bool premium); private slots: - void loginChecked(); - void loginFinished(); - void loginError(QNetworkReply::NetworkError errorCode); - void loginTimeout(); + void loginChecked(); + void loginFinished(); + void loginError(QNetworkReply::NetworkError errorCode); + void loginTimeout(); protected: - virtual QNetworkReply* createRequest(QNetworkAccessManager::Operation operation, const QNetworkRequest& request, - QIODevice* device); + + virtual QNetworkReply *createRequest( + QNetworkAccessManager::Operation operation, const QNetworkRequest &request, + QIODevice *device); private: - void pageLogin(); - // void dlLogin(); - bool hasLoginCookies() const; + void pageLogin(); +// void dlLogin(); + + bool hasLoginCookies() const; - void retrieveCredentials(); + void retrieveCredentials(); private: - static const std::set s_PremiumAccountStates; + + static const std::set s_PremiumAccountStates; private: - QTimer m_LoginTimeout; - QNetworkReply* m_LoginReply; - QProgressDialog* m_ProgressDialog{nullptr}; - - QString m_MOVersion; - QString m_NMMVersion; - - QString m_Username; - QString m_Password; - - bool m_LoginAttempted; - enum { - LOGIN_NOT_CHECKED, - LOGIN_CHECKING, - LOGIN_NOT_VALID, - LOGIN_ATTEMPT_FAILED, - LOGIN_REFUSED, - LOGIN_VALID - } m_LoginState = LOGIN_NOT_CHECKED; + + QTimer m_LoginTimeout; + QNetworkReply *m_LoginReply; + QProgressDialog *m_ProgressDialog { nullptr }; + + QString m_MOVersion; + QString m_NMMVersion; + + QString m_Username; + QString m_Password; + + bool m_LoginAttempted; + enum { + LOGIN_NOT_CHECKED, + LOGIN_CHECKING, + LOGIN_NOT_VALID, + LOGIN_ATTEMPT_FAILED, + LOGIN_REFUSED, + LOGIN_VALID + } m_LoginState = LOGIN_NOT_CHECKED; + }; #endif // NXMACCESSMANAGER_H diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 4b6567c5..b84488da 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1,39 +1,39 @@ #include "organizercore.h" -#include "appconfig.h" -#include "credentialsdialog.h" #include "delayedfilewriter.h" -#include "filedialogmemory.h" #include "guessedvalue.h" #include "imodinterface.h" #include "imoinfo.h" -#include "instancemanager.h" #include "iplugingame.h" #include "iuserinterface.h" #include "loadmechanism.h" -#include "lockeddialog.h" -#include "logbuffer.h" #include "messagedialog.h" -#include "modinfodialog.h" #include "modlistsortproxy.h" #include "modrepositoryfileinfo.h" #include "nexusinterface.h" -#include "nxmaccessmanager.h" #include "plugincontainer.h" #include "pluginlistsortproxy.h" #include "profile.h" +#include "logbuffer.h" +#include "credentialsdialog.h" +#include "filedialogmemory.h" +#include "modinfodialog.h" #include "spawn.h" #include "syncoverwritedialog.h" -#include -#include +#include "nxmaccessmanager.h" #include +#include #include -#include -#include +#include #include -#include -#include #include +#include +#include "appconfig.h" +#include +#include +#include "lockeddialog.h" +#include "instancemanager.h" +#include #include #include @@ -51,1834 +51,2129 @@ #include #include -#include // for _tcsicmp #include +#include // for _tcsicmp #include #include #include // for memset, wcsrchr -#include #include #include +#include #include #include #include //for wstring #include #include + using namespace MOShared; using namespace MOBase; -// static +//static CrashDumpsType OrganizerCore::m_globalCrashDumpsType = CrashDumpsType::None; -static bool isOnline() { - QList interfaces = QNetworkInterface::allInterfaces(); - - bool connected = false; - for (auto iter = interfaces.begin(); iter != interfaces.end() && !connected; ++iter) { - if ((iter->flags() & QNetworkInterface::IsUp) && (iter->flags() & QNetworkInterface::IsRunning) && - !(iter->flags() & QNetworkInterface::IsLoopBack)) { - auto addresses = iter->addressEntries(); - if (addresses.count() == 0) { - continue; - } - qDebug("interface %s seems to be up (address: %s)", qPrintable(iter->humanReadableName()), - qPrintable(addresses[0].ip().toString())); - connected = true; - } +static bool isOnline() +{ + QList interfaces = QNetworkInterface::allInterfaces(); + + bool connected = false; + for (auto iter = interfaces.begin(); iter != interfaces.end() && !connected; + ++iter) { + if ((iter->flags() & QNetworkInterface::IsUp) + && (iter->flags() & QNetworkInterface::IsRunning) + && !(iter->flags() & QNetworkInterface::IsLoopBack)) { + auto addresses = iter->addressEntries(); + if (addresses.count() == 0) { + continue; + } + qDebug("interface %s seems to be up (address: %s)", + qPrintable(iter->humanReadableName()), + qPrintable(addresses[0].ip().toString())); + connected = true; } + } - return connected; + return connected; } -static bool renameFile(const QString& oldName, const QString& newName, bool overwrite = true) { - if (overwrite && QFile::exists(newName)) { - QFile::remove(newName); - } - return QFile::rename(oldName, newName); +static bool renameFile(const QString &oldName, const QString &newName, + bool overwrite = true) +{ + if (overwrite && QFile::exists(newName)) { + QFile::remove(newName); + } + return QFile::rename(oldName, newName); } -static std::wstring getProcessName(HANDLE process) { - wchar_t buffer[MAX_PATH]; - wchar_t* fileName = L"unknown"; +static std::wstring getProcessName(HANDLE process) +{ + wchar_t buffer[MAX_PATH]; + wchar_t *fileName = L"unknown"; - if (process == nullptr) - return fileName; + if (process == nullptr) return fileName; - if (::GetProcessImageFileNameW(process, buffer, MAX_PATH) != 0) { - fileName = wcsrchr(buffer, L'\\'); - if (fileName == nullptr) { - fileName = buffer; - } else { - fileName += 1; - } - } + if (::GetProcessImageFileNameW(process, buffer, MAX_PATH) != 0) { + fileName = wcsrchr(buffer, L'\\'); + if (fileName == nullptr) { + fileName = buffer; + } + else { + fileName += 1; + } + } - return fileName; + return fileName; } // Get parent PID for the given process, return 0 on failure -static DWORD getProcessParentID(DWORD pid) { - HANDLE th = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); - PROCESSENTRY32 pe = {0}; - pe.dwSize = sizeof(PROCESSENTRY32); - - DWORD res = 0; - if (Process32First(th, &pe)) - do { - if (pe.th32ProcessID == pid) { - res = pe.th32ParentProcessID; - break; - } - } while (Process32Next(th, &pe)); - - CloseHandle(th); - - return res; -} - -static void startSteam(QWidget* widget) { - QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam", QSettings::NativeFormat); - QString exe = steamSettings.value("SteamExe", "").toString(); - if (!exe.isEmpty()) { - exe = QString("\"%1\"").arg(exe); - // See if username and password supplied. If so, pass them into steam. - QStringList args; - QString username; - QString password; - if (Settings::instance().getSteamLogin(username, password)) { - args << "-login"; - args << username; - if (password != "") { - args << password; - } - } - if (!QProcess::startDetached(exe, args)) { - reportError(QObject::tr("Failed to start \"%1\"").arg(exe)); - } else { - QMessageBox::information(widget, QObject::tr("Waiting"), - QObject::tr("Please press OK once you're logged into steam.")); - } +static DWORD getProcessParentID(DWORD pid) +{ + HANDLE th = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0); + PROCESSENTRY32 pe = { 0 }; + pe.dwSize = sizeof(PROCESSENTRY32); + + DWORD res = 0; + if (Process32First(th, &pe)) + do { + if (pe.th32ProcessID == pid) { + res = pe.th32ParentProcessID; + break; + } + } while (Process32Next(th, &pe)); + + CloseHandle(th); + + return res; +} + +static void startSteam(QWidget *widget) +{ + QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam", + QSettings::NativeFormat); + QString exe = steamSettings.value("SteamExe", "").toString(); + if (!exe.isEmpty()) { + exe = QString("\"%1\"").arg(exe); + // See if username and password supplied. If so, pass them into steam. + QStringList args; + QString username; + QString password; + if (Settings::instance().getSteamLogin(username, password)) { + args << "-login"; + args << username; + if (password != "") { + args << password; + } + } + if (!QProcess::startDetached(exe, args)) { + reportError(QObject::tr("Failed to start \"%1\"").arg(exe)); + } else { + QMessageBox::information( + widget, QObject::tr("Waiting"), + QObject::tr("Please press OK once you're logged into steam.")); } + } } template -QStringList toStringList(InputIterator current, InputIterator end) { - QStringList result; - for (; current != end; ++current) { - result.append(*current); - } - return result; -} - -OrganizerCore::OrganizerCore(const QSettings& initSettings) - : m_UserInterface(nullptr), m_PluginContainer(nullptr), m_GameName(), m_CurrentProfile(nullptr), - m_Settings(initSettings), m_Updater(NexusInterface::instance()), m_AboutToRun(), m_FinishedRun(), - m_ModInstalled(), m_ModList(this), m_PluginList(this), m_DirectoryRefresher(), - m_DirectoryStructure(new DirectoryEntry(L"data", nullptr, 0)), - m_DownloadManager(NexusInterface::instance(), this), m_InstallationManager(), m_RefresherThread(), - m_AskForNexusPW(false), m_DirectoryUpdate(false), m_ArchivesInit(false), - m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this)) { - m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); - m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers()); - - NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory()); - NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion()); - - MOBase::QuestionBoxMemory::init(initSettings.fileName()); - - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); - - connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString, int)), this, SLOT(downloadSpeed(QString, int))); - connect(&m_DirectoryRefresher, SIGNAL(refreshed()), this, SLOT(directory_refreshed())); - - connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, SLOT(removeOrigin(QString))); - - connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginSuccessful(bool)), this, - SLOT(loginSuccessful(bool))); - connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(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, SIGNAL(managedGameChanged(MOBase::IPluginGame const*)), NexusInterface::instance(), - SLOT(managedGameChanged(MOBase::IPluginGame const*))); - - connect(&m_PluginList, &PluginList::writePluginsList, &m_PluginListsWriter, &DelayedFileWriterBase::write); - - // make directory refresher run in a separate thread - m_RefresherThread.start(); - m_DirectoryRefresher.moveToThread(&m_RefresherThread); - - m_AskForNexusPW = initSettings.value("ask_for_nexuspw", true).toBool(); -} - -OrganizerCore::~OrganizerCore() { - m_RefresherThread.exit(); - m_RefresherThread.wait(); - - prepareStart(); - - // profile has to be cleaned up before the modinfo-buffer is cleared - delete m_CurrentProfile; - m_CurrentProfile = nullptr; - - ModInfo::clear(); - LogBuffer::cleanQuit(); - m_ModList.setProfile(nullptr); - // NexusInterface::instance()->cleanup(); - - delete m_DirectoryStructure; -} - -QString OrganizerCore::commitSettings(const QString& iniFile) { - if (!shellRename(iniFile + ".new", iniFile, true, qApp->activeWindow())) { - DWORD err = ::GetLastError(); - // make a second attempt using qt functions but if that fails print the - // error from the first attempt - if (!renameFile(iniFile + ".new", iniFile)) { - return windowsErrorString(err); - } - } - return QString(); -} - -QSettings::Status OrganizerCore::storeSettings(const QString& fileName) { - QSettings settings(fileName, QSettings::IniFormat); - if (m_UserInterface != nullptr) { - m_UserInterface->storeSettings(settings); - } - if (m_CurrentProfile != nullptr) { - settings.setValue("selected_profile", m_CurrentProfile->name().toUtf8().constData()); - } - settings.setValue("ask_for_nexuspw", m_AskForNexusPW); - - settings.remove("customExecutables"); - settings.beginWriteArray("customExecutables"); - std::vector::const_iterator current, end; - m_ExecutablesList.getExecutables(current, end); - int count = 0; - for (; current != end; ++current) { - const Executable& item = *current; - settings.setArrayIndex(count++); - settings.setValue("title", item.m_Title); - settings.setValue("custom", item.isCustom()); - settings.setValue("toolbar", item.isShownOnToolbar()); - settings.setValue("ownicon", item.usesOwnIcon()); - if (item.isCustom()) { - settings.setValue("binary", item.m_BinaryInfo.absoluteFilePath()); - settings.setValue("arguments", item.m_Arguments); - settings.setValue("workingDirectory", item.m_WorkingDirectory); - settings.setValue("steamAppID", item.m_SteamAppID); - } - } - settings.endArray(); - - FileDialogMemory::save(settings); - - settings.sync(); - return settings.status(); -} - -void OrganizerCore::storeSettings() { - QString iniFile = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::iniFileName()); - if (QFileInfo(iniFile).exists()) { - if (!shellCopy(iniFile, iniFile + ".new", true, qApp->activeWindow())) { - QMessageBox::critical(qApp->activeWindow(), tr("Failed to write settings"), - tr("An error occured trying to update MO settings to %1: %2") - .arg(iniFile, windowsErrorString(::GetLastError()))); - return; - } - } - - QString writeTarget = iniFile + ".new"; - - QSettings::Status result = storeSettings(writeTarget); - - if (result == QSettings::NoError) { - QString errMsg = commitSettings(iniFile); - if (!errMsg.isEmpty()) { - qWarning("settings file not writable, may be locked by another " - "application, trying direct write"); - writeTarget = iniFile; - result = storeSettings(iniFile); - } - } - if (result != QSettings::NoError) { - QString reason = result == QSettings::AccessError - ? tr("File is write protected") - : result == QSettings::FormatError ? tr("Invalid file format (probably a bug)") - : tr("Unknown error %1").arg(result); - QMessageBox::critical( - qApp->activeWindow(), tr("Failed to write settings"), - tr("An error occured trying to write back MO settings to %1: %2").arg(writeTarget, reason)); - } -} - -bool OrganizerCore::testForSteam() { - size_t currentSize = 1024; - std::unique_ptr processIDs; - DWORD bytesReturned; - bool success = false; - while (!success) { - processIDs.reset(new DWORD[currentSize]); - if (!::EnumProcesses(processIDs.get(), static_cast(currentSize) * sizeof(DWORD), &bytesReturned)) { - qWarning("failed to determine if steam is running"); +QStringList toStringList(InputIterator current, InputIterator end) +{ + QStringList result; + for (; current != end; ++current) { + result.append(*current); + } + return result; +} + +OrganizerCore::OrganizerCore(const QSettings &initSettings) + : m_UserInterface(nullptr) + , m_PluginContainer(nullptr) + , m_GameName() + , m_CurrentProfile(nullptr) + , m_Settings(initSettings) + , m_Updater(NexusInterface::instance()) + , m_AboutToRun() + , m_FinishedRun() + , m_ModInstalled() + , m_ModList(this) + , m_PluginList(this) + , m_DirectoryRefresher() + , m_DirectoryStructure(new DirectoryEntry(L"data", nullptr, 0)) + , m_DownloadManager(NexusInterface::instance(), this) + , m_InstallationManager() + , m_RefresherThread() + , m_AskForNexusPW(false) + , m_DirectoryUpdate(false) + , m_ArchivesInit(false) + , m_PluginListsWriter(std::bind(&OrganizerCore::savePluginList, this)) +{ + m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); + m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers()); + + NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory()); + NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion()); + + MOBase::QuestionBoxMemory::init(initSettings.fileName()); + + m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); + + connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString, int)), this, + SLOT(downloadSpeed(QString, int))); + connect(&m_DirectoryRefresher, SIGNAL(refreshed()), this, + SLOT(directory_refreshed())); + + connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, + SLOT(removeOrigin(QString))); + + connect(NexusInterface::instance()->getAccessManager(), + SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool))); + connect(NexusInterface::instance()->getAccessManager(), + SIGNAL(loginFailed(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, SIGNAL(managedGameChanged(MOBase::IPluginGame const *)), + NexusInterface::instance(), + SLOT(managedGameChanged(MOBase::IPluginGame const *))); + + connect(&m_PluginList, &PluginList::writePluginsList, &m_PluginListsWriter, + &DelayedFileWriterBase::write); + + // make directory refresher run in a separate thread + m_RefresherThread.start(); + m_DirectoryRefresher.moveToThread(&m_RefresherThread); + + m_AskForNexusPW = initSettings.value("ask_for_nexuspw", true).toBool(); +} + +OrganizerCore::~OrganizerCore() +{ + m_RefresherThread.exit(); + m_RefresherThread.wait(); + + prepareStart(); + + // profile has to be cleaned up before the modinfo-buffer is cleared + delete m_CurrentProfile; + m_CurrentProfile = nullptr; + + ModInfo::clear(); + LogBuffer::cleanQuit(); + m_ModList.setProfile(nullptr); + // NexusInterface::instance()->cleanup(); + + delete m_DirectoryStructure; +} + +QString OrganizerCore::commitSettings(const QString &iniFile) +{ + if (!shellRename(iniFile + ".new", iniFile, true, qApp->activeWindow())) { + DWORD err = ::GetLastError(); + // make a second attempt using qt functions but if that fails print the + // error from the first attempt + if (!renameFile(iniFile + ".new", iniFile)) { + return windowsErrorString(err); + } + } + return QString(); +} + +QSettings::Status OrganizerCore::storeSettings(const QString &fileName) +{ + QSettings settings(fileName, QSettings::IniFormat); + if (m_UserInterface != nullptr) { + m_UserInterface->storeSettings(settings); + } + if (m_CurrentProfile != nullptr) { + settings.setValue("selected_profile", + m_CurrentProfile->name().toUtf8().constData()); + } + settings.setValue("ask_for_nexuspw", m_AskForNexusPW); + + settings.remove("customExecutables"); + settings.beginWriteArray("customExecutables"); + std::vector::const_iterator current, end; + m_ExecutablesList.getExecutables(current, end); + int count = 0; + for (; current != end; ++current) { + const Executable &item = *current; + settings.setArrayIndex(count++); + settings.setValue("title", item.m_Title); + settings.setValue("custom", item.isCustom()); + settings.setValue("toolbar", item.isShownOnToolbar()); + settings.setValue("ownicon", item.usesOwnIcon()); + if (item.isCustom()) { + settings.setValue("binary", item.m_BinaryInfo.absoluteFilePath()); + settings.setValue("arguments", item.m_Arguments); + settings.setValue("workingDirectory", item.m_WorkingDirectory); + settings.setValue("steamAppID", item.m_SteamAppID); + } + } + settings.endArray(); + + FileDialogMemory::save(settings); + + settings.sync(); + return settings.status(); +} + +void OrganizerCore::storeSettings() +{ + QString iniFile = qApp->property("dataPath").toString() + "/" + + QString::fromStdWString(AppConfig::iniFileName()); + if (QFileInfo(iniFile).exists()) { + if (!shellCopy(iniFile, iniFile + ".new", true, qApp->activeWindow())) { + QMessageBox::critical( + qApp->activeWindow(), tr("Failed to write settings"), + tr("An error occured trying to update MO settings to %1: %2") + .arg(iniFile, windowsErrorString(::GetLastError()))); + return; + } + } + + QString writeTarget = iniFile + ".new"; + + QSettings::Status result = storeSettings(writeTarget); + + if (result == QSettings::NoError) { + QString errMsg = commitSettings(iniFile); + if (!errMsg.isEmpty()) { + qWarning("settings file not writable, may be locked by another " + "application, trying direct write"); + writeTarget = iniFile; + result = storeSettings(iniFile); + } + } + if (result != QSettings::NoError) { + QString reason = result == QSettings::AccessError + ? tr("File is write protected") + : result == QSettings::FormatError + ? tr("Invalid file format (probably a bug)") + : tr("Unknown error %1").arg(result); + QMessageBox::critical( + qApp->activeWindow(), tr("Failed to write settings"), + tr("An error occured trying to write back MO settings to %1: %2") + .arg(writeTarget, reason)); + } +} + +bool OrganizerCore::testForSteam() +{ + size_t currentSize = 1024; + std::unique_ptr processIDs; + DWORD bytesReturned; + bool success = false; + while (!success) { + processIDs.reset(new DWORD[currentSize]); + if (!::EnumProcesses(processIDs.get(), + static_cast(currentSize) * sizeof(DWORD), + &bytesReturned)) { + qWarning("failed to determine if steam is running"); + return true; + } + if (bytesReturned == (currentSize * sizeof(DWORD))) { + // maximum size used, list probably truncated + currentSize *= 2; + } else { + success = true; + } + } + TCHAR processName[MAX_PATH]; + for (unsigned int i = 0; i < bytesReturned / sizeof(DWORD); ++i) { + memset(processName, '\0', sizeof(TCHAR) * MAX_PATH); + if (processIDs[i] != 0) { + HANDLE process = ::OpenProcess( + PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processIDs[i]); + + if (process != nullptr) { + + ON_BLOCK_EXIT([&]() { + if (process != INVALID_HANDLE_VALUE) + ::CloseHandle(process); + }); + + HMODULE module; + DWORD ignore; + + // first module in a process is always the binary + if (EnumProcessModules(process, &module, sizeof(HMODULE) * 1, + &ignore)) { + ::GetModuleBaseName(process, module, processName, MAX_PATH); + if ((_tcsicmp(processName, TEXT("steam.exe")) == 0) + || (_tcsicmp(processName, TEXT("steamservice.exe")) == 0)) { return true; + } } - if (bytesReturned == (currentSize * sizeof(DWORD))) { - // maximum size used, list probably truncated - currentSize *= 2; - } else { - success = true; - } - } - TCHAR processName[MAX_PATH]; - for (unsigned int i = 0; i < bytesReturned / sizeof(DWORD); ++i) { - memset(processName, '\0', sizeof(TCHAR) * MAX_PATH); - if (processIDs[i] != 0) { - HANDLE process = ::OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, processIDs[i]); - - if (process != nullptr) { - - ON_BLOCK_EXIT([&]() { - if (process != INVALID_HANDLE_VALUE) - ::CloseHandle(process); - }); - - HMODULE module; - DWORD ignore; - - // first module in a process is always the binary - if (EnumProcessModules(process, &module, sizeof(HMODULE) * 1, &ignore)) { - ::GetModuleBaseName(process, module, processName, MAX_PATH); - if ((_tcsicmp(processName, TEXT("steam.exe")) == 0) || - (_tcsicmp(processName, TEXT("steamservice.exe")) == 0)) { - return true; - } - } - } - } - } - - return false; -} - -void OrganizerCore::updateExecutablesList(QSettings& settings) { - if (m_PluginContainer == nullptr) { - qCritical("can't update executables list now"); - return; - } - - m_ExecutablesList.init(managedGame()); - - qDebug("setting up configured executables"); - - int numCustomExecutables = settings.beginReadArray("customExecutables"); - for (int i = 0; i < numCustomExecutables; ++i) { - settings.setArrayIndex(i); - - Executable::Flags flags; - if (settings.value("custom", true).toBool()) - flags |= Executable::CustomExecutable; - if (settings.value("toolbar", false).toBool()) - flags |= Executable::ShowInToolbar; - if (settings.value("ownicon", false).toBool()) - flags |= Executable::UseApplicationIcon; - - m_ExecutablesList.addExecutable(settings.value("title").toString(), settings.value("binary").toString(), - settings.value("arguments").toString(), - settings.value("workingDirectory", "").toString(), - settings.value("steamAppID", "").toString(), flags); + } + } + } + + return false; +} + +void OrganizerCore::updateExecutablesList(QSettings &settings) +{ + if (m_PluginContainer == nullptr) { + qCritical("can't update executables list now"); + return; + } + + m_ExecutablesList.init(managedGame()); + + qDebug("setting up configured executables"); + + int numCustomExecutables = settings.beginReadArray("customExecutables"); + for (int i = 0; i < numCustomExecutables; ++i) { + settings.setArrayIndex(i); + + Executable::Flags flags; + if (settings.value("custom", true).toBool()) + flags |= Executable::CustomExecutable; + if (settings.value("toolbar", false).toBool()) + flags |= Executable::ShowInToolbar; + if (settings.value("ownicon", false).toBool()) + flags |= Executable::UseApplicationIcon; + + m_ExecutablesList.addExecutable( + settings.value("title").toString(), settings.value("binary").toString(), + settings.value("arguments").toString(), + settings.value("workingDirectory", "").toString(), + settings.value("steamAppID", "").toString(), flags); + } + + settings.endArray(); + + // TODO this has nothing to do with executables list move to an appropriate + // function! + ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, + m_Settings.displayForeign(), managedGame()); +} + +void OrganizerCore::setUserInterface(IUserInterface *userInterface, + QWidget *widget) +{ + storeSettings(); + + m_UserInterface = userInterface; + + if (widget != nullptr) { + connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), widget, + SLOT(modlistChanged(QModelIndex, int))); + connect(&m_ModList, SIGNAL(showMessage(QString)), widget, + SLOT(showMessage(QString))); + connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), widget, + SLOT(modRenamed(QString, QString))); + connect(&m_ModList, SIGNAL(modUninstalled(QString)), widget, + SLOT(modRemoved(QString))); + connect(&m_ModList, SIGNAL(removeSelectedMods()), widget, + SLOT(removeMod_clicked())); + connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), widget, + SLOT(displayColumnSelection(QPoint))); + connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), widget, + SLOT(fileMoved(QString, QString, QString))); + connect(&m_ModList, SIGNAL(modorder_changed()), widget, + SLOT(modorder_changed())); + connect(&m_DownloadManager, SIGNAL(showMessage(QString)), widget, + SLOT(showMessage(QString))); + } + + m_InstallationManager.setParentWidget(widget); + m_Updater.setUserInterface(widget); + + if (userInterface != nullptr) { + // this currently wouldn't work reliably if the ui isn't initialized yet to + // display the result + if (isOnline() && !m_Settings.offlineMode()) { + m_Updater.testForUpdate(); + } else { + qDebug("user doesn't seem to be connected to the internet"); } - - settings.endArray(); - - // TODO this has nothing to do with executables list move to an appropriate - // function! - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign(), - managedGame()); + } } -void OrganizerCore::setUserInterface(IUserInterface* userInterface, QWidget* widget) { - storeSettings(); - - m_UserInterface = userInterface; - - if (widget != nullptr) { - connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), widget, SLOT(modlistChanged(QModelIndex, int))); - connect(&m_ModList, SIGNAL(showMessage(QString)), widget, SLOT(showMessage(QString))); - connect(&m_ModList, SIGNAL(modRenamed(QString, QString)), widget, SLOT(modRenamed(QString, QString))); - connect(&m_ModList, SIGNAL(modUninstalled(QString)), widget, SLOT(modRemoved(QString))); - connect(&m_ModList, SIGNAL(removeSelectedMods()), widget, SLOT(removeMod_clicked())); - connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), widget, SLOT(displayColumnSelection(QPoint))); - connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), widget, - SLOT(fileMoved(QString, QString, QString))); - connect(&m_ModList, SIGNAL(modorder_changed()), widget, SLOT(modorder_changed())); - connect(&m_DownloadManager, SIGNAL(showMessage(QString)), widget, SLOT(showMessage(QString))); - } - - m_InstallationManager.setParentWidget(widget); - m_Updater.setUserInterface(widget); +void OrganizerCore::connectPlugins(PluginContainer *container) +{ + m_DownloadManager.setSupportedExtensions( + m_InstallationManager.getSupportedExtensions()); + m_PluginContainer = container; - if (userInterface != nullptr) { - // this currently wouldn't work reliably if the ui isn't initialized yet to - // display the result - if (isOnline() && !m_Settings.offlineMode()) { - m_Updater.testForUpdate(); - } else { - qDebug("user doesn't seem to be connected to the internet"); - } - } + if (!m_GameName.isEmpty()) { + m_GamePlugin = m_PluginContainer->managedGame(m_GameName); + emit managedGameChanged(m_GamePlugin); + } } -void OrganizerCore::connectPlugins(PluginContainer* container) { - m_DownloadManager.setSupportedExtensions(m_InstallationManager.getSupportedExtensions()); - m_PluginContainer = container; +void OrganizerCore::disconnectPlugins() +{ + m_AboutToRun.disconnect_all_slots(); + m_FinishedRun.disconnect_all_slots(); + m_ModInstalled.disconnect_all_slots(); + m_ModList.disconnectSlots(); + m_PluginList.disconnectSlots(); - if (!m_GameName.isEmpty()) { - m_GamePlugin = m_PluginContainer->managedGame(m_GameName); - emit managedGameChanged(m_GamePlugin); - } + m_Settings.clearPlugins(); + m_GamePlugin = nullptr; + m_PluginContainer = nullptr; } -void OrganizerCore::disconnectPlugins() { - m_AboutToRun.disconnect_all_slots(); - m_FinishedRun.disconnect_all_slots(); - m_ModInstalled.disconnect_all_slots(); - m_ModList.disconnectSlots(); - m_PluginList.disconnectSlots(); - - m_Settings.clearPlugins(); - m_GamePlugin = nullptr; - m_PluginContainer = nullptr; +void OrganizerCore::setManagedGame(MOBase::IPluginGame *game) +{ + m_GameName = game->gameName(); + m_GamePlugin = game; + qApp->setProperty("managed_game", QVariant::fromValue(m_GamePlugin)); + emit managedGameChanged(m_GamePlugin); } -void OrganizerCore::setManagedGame(MOBase::IPluginGame* game) { - m_GameName = game->gameName(); - m_GamePlugin = game; - qApp->setProperty("managed_game", QVariant::fromValue(m_GamePlugin)); - emit managedGameChanged(m_GamePlugin); +Settings &OrganizerCore::settings() +{ + return m_Settings; } -Settings& OrganizerCore::settings() { return m_Settings; } - -bool OrganizerCore::nexusLogin(bool retry) { - NXMAccessManager* accessManager = NexusInterface::instance()->getAccessManager(); +bool OrganizerCore::nexusLogin(bool retry) +{ + NXMAccessManager *accessManager + = NexusInterface::instance()->getAccessManager(); - if ((accessManager->loginAttempted() || accessManager->loggedIn()) && !retry) { - // previous attempt, maybe even successful - return false; + if ((accessManager->loginAttempted() || accessManager->loggedIn()) + && !retry) { + // previous attempt, maybe even successful + return false; + } else { + QString username, password; + if ((!retry && m_Settings.getNexusLogin(username, password)) + || (m_AskForNexusPW && queryLogin(username, password))) { + // credentials stored or user entered them manually + qDebug("attempt login with username %s", qPrintable(username)); + accessManager->login(username, password); + return true; } else { - QString username, password; - if ((!retry && m_Settings.getNexusLogin(username, password)) || - (m_AskForNexusPW && queryLogin(username, password))) { - // credentials stored or user entered them manually - qDebug("attempt login with username %s", qPrintable(username)); - accessManager->login(username, password); - return true; - } else { - // no credentials stored and user didn't enter them - accessManager->refuseLogin(); - return false; - } + // no credentials stored and user didn't enter them + accessManager->refuseLogin(); + return false; + } + } +} + +bool OrganizerCore::queryLogin(QString &username, QString &password) +{ + CredentialsDialog dialog(qApp->activeWindow()); + int res = dialog.exec(); + if (dialog.neverAsk()) { + m_AskForNexusPW = false; + } + if (res == QDialog::Accepted) { + username = dialog.username(); + password = dialog.password(); + if (dialog.store()) { + m_Settings.setNexusLogin(username, password); } + return true; + } else { + return false; + } } -bool OrganizerCore::queryLogin(QString& username, QString& password) { - CredentialsDialog dialog(qApp->activeWindow()); - int res = dialog.exec(); - if (dialog.neverAsk()) { - m_AskForNexusPW = false; - } - if (res == QDialog::Accepted) { - username = dialog.username(); - password = dialog.password(); - if (dialog.store()) { - m_Settings.setNexusLogin(username, password); - } - return true; - } else { - return false; - } +void OrganizerCore::startMOUpdate() +{ + if (nexusLogin()) { + m_PostLoginTasks.append([&]() { m_Updater.startUpdate(); }); + } else { + m_Updater.startUpdate(); + } } -void OrganizerCore::startMOUpdate() { - if (nexusLogin()) { - m_PostLoginTasks.append([&]() { m_Updater.startUpdate(); }); - } else { - m_Updater.startUpdate(); - } +void OrganizerCore::downloadRequestedNXM(const QString &url) +{ + qDebug("download requested: %s", qPrintable(url)); + if (nexusLogin()) { + m_PendingDownloads.append(url); + } else { + m_DownloadManager.addNXMDownload(url); + } } -void OrganizerCore::downloadRequestedNXM(const QString& url) { - qDebug("download requested: %s", qPrintable(url)); - if (nexusLogin()) { - m_PendingDownloads.append(url); - } else { - m_DownloadManager.addNXMDownload(url); - } +void OrganizerCore::externalMessage(const QString &message) +{ + if (MOShortcut moshortcut{ message }) { + runShortcut(moshortcut); + } + else if (isNxmLink(message)) { + MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); + downloadRequestedNXM(message); + } } -void OrganizerCore::externalMessage(const QString& message) { - if (MOShortcut moshortcut{message}) { - runShortcut(moshortcut); - } else if (isNxmLink(message)) { - MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); - downloadRequestedNXM(message); +void OrganizerCore::downloadRequested(QNetworkReply *reply, int modID, + const QString &fileName) +{ + try { + if (m_DownloadManager.addDownload(reply, QStringList(), fileName, modID, 0, + new ModRepositoryFileInfo(modID))) { + MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); } + } catch (const std::exception &e) { + MessageDialog::showMessage(tr("Download failed"), qApp->activeWindow()); + qCritical("exception starting download: %s", e.what()); + } } -void OrganizerCore::downloadRequested(QNetworkReply* reply, int modID, const QString& fileName) { - try { - if (m_DownloadManager.addDownload(reply, QStringList(), fileName, modID, 0, new ModRepositoryFileInfo(modID))) { - MessageDialog::showMessage(tr("Download started"), qApp->activeWindow()); - } - } catch (const std::exception& e) { - MessageDialog::showMessage(tr("Download failed"), qApp->activeWindow()); - qCritical("exception starting download: %s", e.what()); - } +void OrganizerCore::removeOrigin(const QString &name) +{ + FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(name)); + origin.enable(false); + refreshLists(); } -void OrganizerCore::removeOrigin(const QString& name) { - FilesOrigin& origin = m_DirectoryStructure->getOriginByName(ToWString(name)); - origin.enable(false); - refreshLists(); +void OrganizerCore::downloadSpeed(const QString &serverName, int bytesPerSecond) +{ + m_Settings.setDownloadSpeed(serverName, bytesPerSecond); } -void OrganizerCore::downloadSpeed(const QString& serverName, int bytesPerSecond) { - m_Settings.setDownloadSpeed(serverName, bytesPerSecond); +InstallationManager *OrganizerCore::installationManager() +{ + return &m_InstallationManager; } -InstallationManager* OrganizerCore::installationManager() { return &m_InstallationManager; } - -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 " - "account probably lacks permission.") - .arg(QDir::toNativeSeparators(path))); - return false; - } else { - return true; - } +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 " + "account probably lacks permission.") + .arg(QDir::toNativeSeparators(path))); + return false; + } else { + return true; + } } bool OrganizerCore::bootstrap() { - return createDirectory(m_Settings.getProfileDirectory()) && createDirectory(m_Settings.getModDirectory()) && - createDirectory(m_Settings.getDownloadDirectory()) && createDirectory(m_Settings.getOverwriteDirectory()) && - createDirectory(QString::fromStdWString(crashDumpsPath())) && cycleDiagnostics(); + return createDirectory(m_Settings.getProfileDirectory()) && + createDirectory(m_Settings.getModDirectory()) && + createDirectory(m_Settings.getDownloadDirectory()) && + createDirectory(m_Settings.getOverwriteDirectory()) && + createDirectory(QString::fromStdWString(crashDumpsPath())) && cycleDiagnostics(); } -void OrganizerCore::createDefaultProfile() { - QString profilesPath = settings().getProfileDirectory(); - if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size() == 0) { - Profile newProf("Default", managedGame(), false); - } +void OrganizerCore::createDefaultProfile() +{ + QString profilesPath = settings().getProfileDirectory(); + if (QDir(profilesPath).entryList(QDir::AllDirs | QDir::NoDotAndDotDot).size() + == 0) { + Profile newProf("Default", managedGame(), false); + } } -void OrganizerCore::prepareVFS() { m_USVFS.updateMapping(fileMapping(m_CurrentProfile->name(), QString())); } +void OrganizerCore::prepareVFS() +{ + m_USVFS.updateMapping(fileMapping(m_CurrentProfile->name(), QString())); +} void OrganizerCore::updateVFSParams(int logLevel, int crashDumpsType) { - setGlobalCrashDumpsType(crashDumpsType); - m_USVFS.updateParams(logLevel, crashDumpsType); + setGlobalCrashDumpsType(crashDumpsType); + m_USVFS.updateParams(logLevel, crashDumpsType); } bool OrganizerCore::cycleDiagnostics() { - if (int maxDumps = settings().crashDumpsMax()) - removeOldFiles(QString::fromStdWString(crashDumpsPath()), "*.dmp", maxDumps, QDir::Time | QDir::Reversed); - return true; + if (int maxDumps = settings().crashDumpsMax()) + removeOldFiles(QString::fromStdWString(crashDumpsPath()), "*.dmp", maxDumps, QDir::Time|QDir::Reversed); + return true; } -// static +//static void OrganizerCore::setGlobalCrashDumpsType(int crashDumpsType) { - m_globalCrashDumpsType = ::crashDumpsType(crashDumpsType); + m_globalCrashDumpsType = ::crashDumpsType(crashDumpsType); } -// static +//static std::wstring OrganizerCore::crashDumpsPath() { - return (qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::dumpsDir())) - .toStdWString(); + return ( + qApp->property("dataPath").toString() + "/" + + QString::fromStdWString(AppConfig::dumpsDir()) + ).toStdWString(); } -void OrganizerCore::setCurrentProfile(const QString& profileName) { - if ((m_CurrentProfile != nullptr) && (profileName == m_CurrentProfile->name())) { - return; - } +void OrganizerCore::setCurrentProfile(const QString &profileName) +{ + if ((m_CurrentProfile != nullptr) + && (profileName == m_CurrentProfile->name())) { + return; + } - QDir profileBaseDir(settings().getProfileDirectory()); - QString profileDir = profileBaseDir.absoluteFilePath(profileName); + QDir profileBaseDir(settings().getProfileDirectory()); + QString profileDir = profileBaseDir.absoluteFilePath(profileName); - if (!QDir(profileDir).exists()) { - // selected profile doesn't exist. Ensure there is at least one profile, - // then pick any one - createDefaultProfile(); + if (!QDir(profileDir).exists()) { + // selected profile doesn't exist. Ensure there is at least one profile, + // then pick any one + createDefaultProfile(); - profileDir = - profileBaseDir.absoluteFilePath(profileBaseDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot).at(0)); - } + profileDir = profileBaseDir.absoluteFilePath( + profileBaseDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot).at(0)); + } - Profile* newProfile = new Profile(QDir(profileDir), managedGame()); + Profile *newProfile = new Profile(QDir(profileDir), managedGame()); - delete m_CurrentProfile; - m_CurrentProfile = newProfile; - m_ModList.setProfile(newProfile); + delete m_CurrentProfile; + m_CurrentProfile = newProfile; + m_ModList.setProfile(newProfile); - if (m_CurrentProfile->invalidationActive(nullptr)) { - m_CurrentProfile->activateInvalidation(); - } else { - m_CurrentProfile->deactivateInvalidation(); - } + if (m_CurrentProfile->invalidationActive(nullptr)) { + m_CurrentProfile->activateInvalidation(); + } else { + m_CurrentProfile->deactivateInvalidation(); + } - connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint))); - refreshDirectoryStructure(); + connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, + SLOT(modStatusChanged(uint))); + refreshDirectoryStructure(); } -MOBase::IModRepositoryBridge* OrganizerCore::createNexusBridge() const { return new NexusBridge(); } - -QString OrganizerCore::profileName() const { - if (m_CurrentProfile != nullptr) { - return m_CurrentProfile->name(); - } else { - return ""; - } +MOBase::IModRepositoryBridge *OrganizerCore::createNexusBridge() const +{ + return new NexusBridge(); } -QString OrganizerCore::profilePath() const { - if (m_CurrentProfile != nullptr) { - return m_CurrentProfile->absolutePath(); - } else { - return ""; - } +QString OrganizerCore::profileName() const +{ + if (m_CurrentProfile != nullptr) { + return m_CurrentProfile->name(); + } else { + return ""; + } } -QString OrganizerCore::downloadsPath() const { return QDir::fromNativeSeparators(m_Settings.getDownloadDirectory()); } +QString OrganizerCore::profilePath() const +{ + if (m_CurrentProfile != nullptr) { + return m_CurrentProfile->absolutePath(); + } else { + return ""; + } +} -QString OrganizerCore::overwritePath() const { return QDir::fromNativeSeparators(m_Settings.getOverwriteDirectory()); } +QString OrganizerCore::downloadsPath() const +{ + return QDir::fromNativeSeparators(m_Settings.getDownloadDirectory()); +} -QString OrganizerCore::basePath() const { return QDir::fromNativeSeparators(m_Settings.getBaseDirectory()); } +QString OrganizerCore::overwritePath() const +{ + return QDir::fromNativeSeparators(m_Settings.getOverwriteDirectory()); +} -MOBase::VersionInfo OrganizerCore::appVersion() const { return m_Updater.getVersion(); } +QString OrganizerCore::basePath() const +{ + return QDir::fromNativeSeparators(m_Settings.getBaseDirectory()); +} -MOBase::IModInterface* OrganizerCore::getMod(const QString& name) const { - unsigned int index = ModInfo::getIndex(name); - return index == UINT_MAX ? nullptr : ModInfo::getByIndex(index).data(); +MOBase::VersionInfo OrganizerCore::appVersion() const +{ + return m_Updater.getVersion(); } -MOBase::IModInterface* OrganizerCore::createMod(GuessedValue& name) { - bool merge = false; - if (!m_InstallationManager.testOverwrite(name, &merge)) { - return nullptr; - } +MOBase::IModInterface *OrganizerCore::getMod(const QString &name) const +{ + unsigned int index = ModInfo::getIndex(name); + return index == UINT_MAX ? nullptr : ModInfo::getByIndex(index).data(); +} - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); +MOBase::IModInterface *OrganizerCore::createMod(GuessedValue &name) +{ + bool merge = false; + if (!m_InstallationManager.testOverwrite(name, &merge)) { + return nullptr; + } - QString targetDirectory = QDir::fromNativeSeparators(m_Settings.getModDirectory()).append("/").append(name); + m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - QSettings settingsFile(targetDirectory + "/meta.ini", QSettings::IniFormat); + QString targetDirectory + = QDir::fromNativeSeparators(m_Settings.getModDirectory()) + .append("/") + .append(name); - if (!merge) { - settingsFile.setValue("modid", 0); - settingsFile.setValue("version", ""); - settingsFile.setValue("newestVersion", ""); - settingsFile.setValue("category", 0); - settingsFile.setValue("installationFile", ""); + QSettings settingsFile(targetDirectory + "/meta.ini", QSettings::IniFormat); - settingsFile.beginWriteArray("installedFiles", 0); - settingsFile.endArray(); - } + if (!merge) { + settingsFile.setValue("modid", 0); + settingsFile.setValue("version", ""); + settingsFile.setValue("newestVersion", ""); + settingsFile.setValue("category", 0); + settingsFile.setValue("installationFile", ""); - return ModInfo::createFrom(QDir(targetDirectory), &m_DirectoryStructure).data(); -} + settingsFile.beginWriteArray("installedFiles", 0); + settingsFile.endArray(); + } -bool OrganizerCore::removeMod(MOBase::IModInterface* mod) { - unsigned int index = ModInfo::getIndex(mod->name()); - if (index == UINT_MAX) { - return mod->remove(); - } else { - return ModInfo::removeMod(index); - } + return ModInfo::createFrom(QDir(targetDirectory), &m_DirectoryStructure) + .data(); } -void OrganizerCore::modDataChanged(MOBase::IModInterface*) { refreshModList(false); } - -QVariant OrganizerCore::pluginSetting(const QString& pluginName, const QString& key) const { - return m_Settings.pluginSetting(pluginName, key); +bool OrganizerCore::removeMod(MOBase::IModInterface *mod) +{ + unsigned int index = ModInfo::getIndex(mod->name()); + if (index == UINT_MAX) { + return mod->remove(); + } else { + return ModInfo::removeMod(index); + } } -void OrganizerCore::setPluginSetting(const QString& pluginName, const QString& key, const QVariant& value) { - m_Settings.setPluginSetting(pluginName, key, value); +void OrganizerCore::modDataChanged(MOBase::IModInterface *) +{ + refreshModList(false); } -QVariant OrganizerCore::persistent(const QString& pluginName, const QString& key, const QVariant& def) const { - return m_Settings.pluginPersistent(pluginName, key, def); +QVariant OrganizerCore::pluginSetting(const QString &pluginName, + const QString &key) const +{ + return m_Settings.pluginSetting(pluginName, key); } -void OrganizerCore::setPersistent(const QString& pluginName, const QString& key, const QVariant& value, bool sync) { - m_Settings.setPluginPersistent(pluginName, key, value, sync); +void OrganizerCore::setPluginSetting(const QString &pluginName, + const QString &key, const QVariant &value) +{ + m_Settings.setPluginSetting(pluginName, key, value); } -QString OrganizerCore::pluginDataPath() const { - return qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()) + "/data"; +QVariant OrganizerCore::persistent(const QString &pluginName, + const QString &key, + const QVariant &def) const +{ + return m_Settings.pluginPersistent(pluginName, key, def); } -MOBase::IModInterface* OrganizerCore::installMod(const QString& fileName, const QString& initModName) { - if (m_CurrentProfile == nullptr) { - return nullptr; - } - - bool hasIniTweaks = false; - GuessedValue modName; - if (!initModName.isEmpty()) { - modName.update(initModName, GUESS_USER); - } - m_CurrentProfile->writeModlistNow(); - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { - MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow()); - refreshModList(); - - int modIndex = ModInfo::getIndex(modName); - if (modIndex != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - 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, ModInfoDialog::TAB_INIFILES); - } - m_ModInstalled(modName); - return modInfo.data(); - } else { - reportError(tr("mod \"%1\" not found").arg(modName)); - } - } else if (m_InstallationManager.wasCancelled()) { - QMessageBox::information(qApp->activeWindow(), tr("Installation cancelled"), - tr("The mod was not installed completely."), QMessageBox::Ok); - } - return nullptr; +void OrganizerCore::setPersistent(const QString &pluginName, const QString &key, + const QVariant &value, bool sync) +{ + m_Settings.setPluginPersistent(pluginName, key, value, sync); } -void OrganizerCore::installDownload(int index) { - try { - QString fileName = m_DownloadManager.getFilePath(index); - int modID = m_DownloadManager.getModID(index); - int fileID = m_DownloadManager.getFileInfo(index)->fileID; - GuessedValue modName; - - // see if there already are mods with the specified mod id - if (modID != 0) { - std::vector modInfo = ModInfo::getByModID(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()) { - modName.update((*iter)->name(), GUESS_PRESET); - (*iter)->saveMeta(); - } - } - } - - m_CurrentProfile->writeModlistNow(); - - bool hasIniTweaks = false; - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { - MessageDialog::showMessage(tr("Installation successful"), qApp->activeWindow()); - refreshModList(); - - int modIndex = ModInfo::getIndex(modName); - if (modIndex != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - modInfo->addInstalledFile(modID, fileID); - - 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, ModInfoDialog::TAB_INIFILES); - } - - m_ModInstalled(modName); - } else { - reportError(tr("mod \"%1\" not found").arg(modName)); - } - m_DownloadManager.markInstalled(index); - - emit modInstalled(modName); - } else if (m_InstallationManager.wasCancelled()) { - QMessageBox::information(qApp->activeWindow(), tr("Installation cancelled"), - tr("The mod was not installed completely."), QMessageBox::Ok); - } - } catch (const std::exception& e) { - reportError(e.what()); - } +QString OrganizerCore::pluginDataPath() const +{ + return qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()) + + "/data"; } -QString OrganizerCore::resolvePath(const QString& fileName) const { - if (m_DirectoryStructure == nullptr) { - return QString(); - } - const FileEntry::Ptr file = m_DirectoryStructure->searchFile(ToWString(fileName), nullptr); - if (file.get() != nullptr) { - return ToQString(file->getFullPath()); - } else { - return QString(); - } -} +MOBase::IModInterface *OrganizerCore::installMod(const QString &fileName, + const QString &initModName) +{ + if (m_CurrentProfile == nullptr) { + return nullptr; + } + + bool hasIniTweaks = false; + GuessedValue modName; + if (!initModName.isEmpty()) { + modName.update(initModName, GUESS_USER); + } + m_CurrentProfile->writeModlistNow(); + m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { + MessageDialog::showMessage(tr("Installation successful"), + qApp->activeWindow()); + refreshModList(); -QStringList OrganizerCore::listDirectories(const QString& directoryName) const { - QStringList result; - DirectoryEntry* dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(directoryName)); - if (dir != nullptr) { - std::vector::iterator current, end; - dir->getSubDirectories(current, end); - for (; current != end; ++current) { - result.append(ToQString((*current)->getName())); - } - } - return result; -} - -QStringList OrganizerCore::findFiles(const QString& path, const std::function& filter) const { - QStringList result; - DirectoryEntry* dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path)); - if (dir != nullptr) { - std::vector files = dir->getFiles(); - foreach (FileEntry::Ptr file, files) { - if (filter(ToQString(file->getFullPath()))) { - result.append(ToQString(file->getFullPath())); - } - } + int modIndex = ModInfo::getIndex(modName); + if (modIndex != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + 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, + ModInfoDialog::TAB_INIFILES); + } + m_ModInstalled(modName); + return modInfo.data(); } else { - qWarning("directory %s not found", qPrintable(path)); - } - return result; -} - -QStringList OrganizerCore::getFileOrigins(const QString& fileName) const { - QStringList result; - const FileEntry::Ptr file = m_DirectoryStructure->searchFile(ToWString(QFileInfo(fileName).fileName()), nullptr); + reportError(tr("mod \"%1\" not found").arg(modName)); + } + } else if (m_InstallationManager.wasCancelled()) { + QMessageBox::information(qApp->activeWindow(), tr("Installation cancelled"), + tr("The mod was not installed completely."), + QMessageBox::Ok); + } + return nullptr; +} + +void OrganizerCore::installDownload(int index) +{ + try { + QString fileName = m_DownloadManager.getFilePath(index); + int modID = m_DownloadManager.getModID(index); + int fileID = m_DownloadManager.getFileInfo(index)->fileID; + GuessedValue modName; - if (file.get() != nullptr) { - result.append(ToQString(m_DirectoryStructure->getOriginByID(file->getOrigin()).getName())); - foreach (auto i, file->getAlternatives()) { - result.append(ToQString(m_DirectoryStructure->getOriginByID(i.first).getName())); + // see if there already are mods with the specified mod id + if (modID != 0) { + std::vector modInfo = ModInfo::getByModID(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()) { + modName.update((*iter)->name(), GUESS_PRESET); + (*iter)->saveMeta(); } - } else { - qDebug("%s not found", qPrintable(fileName)); + } } - return result; -} - -QList -OrganizerCore::findFileInfos(const QString& path, - const std::function& filter) const { - QList result; - DirectoryEntry* dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path)); - if (dir != nullptr) { - std::vector files = dir->getFiles(); - foreach (FileEntry::Ptr file, files) { - IOrganizer::FileInfo info; - info.filePath = ToQString(file->getFullPath()); - bool fromArchive = false; - info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(file->getOrigin(fromArchive)).getName())); - info.archive = fromArchive ? ToQString(file->getArchive()) : ""; - foreach (auto idx, file->getAlternatives()) { - info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(idx.first).getName())); - } - - if (filter(info)) { - result.append(info); - } - } - } - return result; -} - -DownloadManager* OrganizerCore::downloadManager() { return &m_DownloadManager; } -PluginList* OrganizerCore::pluginList() { return &m_PluginList; } - -ModList* OrganizerCore::modList() { return &m_ModList; } + m_CurrentProfile->writeModlistNow(); -QStringList OrganizerCore::modsSortedByProfilePriority() const { - QStringList res; - for (unsigned int i = 0; i < currentProfile()->numRegularMods(); ++i) { - int modIndex = currentProfile()->modIndexByPriority(i); - res.push_back(ModInfo::getByIndex(modIndex)->name()); - } - return res; -} - -void OrganizerCore::spawnBinary(const QFileInfo& binary, const QString& arguments, const QDir& currentDirectory, - const QString& steamAppID, const QString& customOverwrite) { - DWORD processExitCode = 0; - HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, - customOverwrite, &processExitCode); - if (processHandle != INVALID_HANDLE_VALUE) { - refreshDirectoryStructure(); - // 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) { - qDebug("removing loadorder.txt"); - QFile::remove(m_CurrentProfile->getLoadOrderFileName()); + bool hasIniTweaks = false; + m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); + if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { + MessageDialog::showMessage(tr("Installation successful"), + qApp->activeWindow()); + refreshModList(); + + int modIndex = ModInfo::getIndex(modName); + if (modIndex != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); + modInfo->addInstalledFile(modID, fileID); + + 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, + ModInfoDialog::TAB_INIFILES); } - refreshDirectoryStructure(); - refreshESPList(); - savePluginList(); + m_ModInstalled(modName); + } else { + reportError(tr("mod \"%1\" not found").arg(modName)); + } + m_DownloadManager.markInstalled(index); - // These callbacks should not fiddle with directoy structure and ESPs. - m_FinishedRun(binary.absoluteFilePath(), processExitCode); + emit modInstalled(modName); + } else if (m_InstallationManager.wasCancelled()) { + QMessageBox::information( + qApp->activeWindow(), tr("Installation cancelled"), + tr("The mod was not installed completely."), QMessageBox::Ok); } + } catch (const std::exception &e) { + reportError(e.what()); + } } -HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo& binary, const QString& arguments, const QString& profileName, - const QDir& currentDirectory, const QString& steamAppID, - const QString& customOverwrite, LPDWORD exitCode) { - HANDLE processHandle = - spawnBinaryProcess(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite); - if (processHandle != INVALID_HANDLE_VALUE) { - std::unique_ptr dlg; - ILockedWaitingForProcess* uilock = nullptr; - - if (m_UserInterface != nullptr) { - uilock = m_UserInterface->lock(); - } else { - // i.e. when running command line shortcuts there is no m_UserInterface - dlg.reset(new LockedDialog); - dlg->show(); - dlg->setEnabled(true); - uilock = dlg.get(); - } - - ON_BLOCK_EXIT([&]() { - if (m_UserInterface != nullptr) { - m_UserInterface->unlock(); - } - }); - - DWORD ignoreExitCode; - waitForProcessCompletion(processHandle, exitCode ? exitCode : &ignoreExitCode, uilock); - cycleDiagnostics(); - } - - return processHandle; +QString OrganizerCore::resolvePath(const QString &fileName) const +{ + if (m_DirectoryStructure == nullptr) { + return QString(); + } + const FileEntry::Ptr file + = m_DirectoryStructure->searchFile(ToWString(fileName), nullptr); + if (file.get() != nullptr) { + return ToQString(file->getFullPath()); + } else { + return QString(); + } } -HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo& binary, const QString& arguments, const QString& profileName, - const QDir& currentDirectory, const QString& steamAppID, - const QString& customOverwrite) { - prepareStart(); - - if (!binary.exists()) { - reportError(tr("Executable \"%1\" not found").arg(binary.absoluteFilePath())); - return INVALID_HANDLE_VALUE; +QStringList OrganizerCore::listDirectories(const QString &directoryName) const +{ + QStringList result; + DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive( + ToWString(directoryName)); + if (dir != nullptr) { + std::vector::iterator current, end; + dir->getSubDirectories(current, end); + for (; current != end; ++current) { + result.append(ToQString((*current)->getName())); + } + } + return result; +} + +QStringList OrganizerCore::findFiles( + const QString &path, + const std::function &filter) const +{ + QStringList result; + DirectoryEntry *dir + = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path)); + if (dir != nullptr) { + std::vector files = dir->getFiles(); + foreach (FileEntry::Ptr file, files) { + if (filter(ToQString(file->getFullPath()))) { + result.append(ToQString(file->getFullPath())); + } + } + } else { + qWarning("directory %s not found", qPrintable(path)); + } + return result; +} + +QStringList OrganizerCore::getFileOrigins(const QString &fileName) const +{ + QStringList result; + const FileEntry::Ptr file = m_DirectoryStructure->searchFile( + ToWString(QFileInfo(fileName).fileName()), nullptr); + + if (file.get() != nullptr) { + result.append(ToQString( + m_DirectoryStructure->getOriginByID(file->getOrigin()).getName())); + foreach (auto i, file->getAlternatives()) { + result.append( + ToQString(m_DirectoryStructure->getOriginByID(i.first).getName())); + } + } else { + qDebug("%s not found", qPrintable(fileName)); + } + return result; +} + +QList OrganizerCore::findFileInfos( + const QString &path, + const std::function &filter) + const +{ + QList result; + DirectoryEntry *dir + = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path)); + if (dir != nullptr) { + std::vector files = dir->getFiles(); + foreach (FileEntry::Ptr file, files) { + IOrganizer::FileInfo info; + info.filePath = ToQString(file->getFullPath()); + bool fromArchive = false; + info.origins.append(ToQString( + m_DirectoryStructure->getOriginByID(file->getOrigin(fromArchive)) + .getName())); + info.archive = fromArchive ? ToQString(file->getArchive()) : ""; + foreach (auto idx, file->getAlternatives()) { + info.origins.append( + ToQString(m_DirectoryStructure->getOriginByID(idx.first).getName())); + } + + if (filter(info)) { + result.append(info); + } + } + } + return result; +} + +DownloadManager *OrganizerCore::downloadManager() +{ + return &m_DownloadManager; +} + +PluginList *OrganizerCore::pluginList() +{ + return &m_PluginList; +} + +ModList *OrganizerCore::modList() +{ + return &m_ModList; +} + +QStringList OrganizerCore::modsSortedByProfilePriority() const +{ + QStringList res; + for (unsigned int i = 0; i < currentProfile()->numRegularMods(); ++i) { + int modIndex = currentProfile()->modIndexByPriority(i); + res.push_back(ModInfo::getByIndex(modIndex)->name()); + } + return res; +} + +void OrganizerCore::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, const QString &steamAppID, const QString &customOverwrite) +{ + DWORD processExitCode = 0; + HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->name(), currentDirectory, steamAppID, customOverwrite, &processExitCode); + if (processHandle != INVALID_HANDLE_VALUE) { + refreshDirectoryStructure(); + // 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) { + qDebug("removing loadorder.txt"); + QFile::remove(m_CurrentProfile->getLoadOrderFileName()); } + refreshDirectoryStructure(); - if (!steamAppID.isEmpty()) { - ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); - } else { - ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(m_Settings.getSteamAppID()).c_str()); - } + refreshESPList(); + savePluginList(); + + //These callbacks should not fiddle with directoy structure and ESPs. + m_FinishedRun(binary.absoluteFilePath(), processExitCode); + } +} + +HANDLE OrganizerCore::spawnBinaryDirect(const QFileInfo &binary, + const QString &arguments, + const QString &profileName, + const QDir ¤tDirectory, + const QString &steamAppID, + const QString &customOverwrite, + LPDWORD exitCode) +{ + HANDLE processHandle = spawnBinaryProcess(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite); + if (processHandle != INVALID_HANDLE_VALUE) { + std::unique_ptr dlg; + ILockedWaitingForProcess* uilock = nullptr; - // This could possibly be extracted somewhere else but it's probably for when - // we have more than one provider of game registration. - if ((QFileInfo(managedGame()->gameDirectory().absoluteFilePath("steam_api.dll")).exists() || - QFileInfo(managedGame()->gameDirectory().absoluteFilePath("steam_api64.dll")).exists()) && - (m_Settings.getLoadMechanism() == LoadMechanism::LOAD_MODORGANIZER)) { - if (!testForSteam()) { - QWidget* window = qApp->activeWindow(); - if ((window != nullptr) && (!window->isVisible())) { - window = nullptr; - } - if (QuestionBoxMemory::query(window, "steamQuery", binary.fileName(), tr("Start Steam?"), - tr("Steam is required to be running already to correctly start the game. " - "Should MO try to start steam now?"), - QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::Yes) { - startSteam(qApp->activeWindow()); - } - } + if (m_UserInterface != nullptr) { + uilock = m_UserInterface->lock(); } - - while (m_DirectoryUpdate) { - ::Sleep(100); - QCoreApplication::processEvents(); + else { + // i.e. when running command line shortcuts there is no m_UserInterface + dlg.reset(new LockedDialog); + dlg->show(); + dlg->setEnabled(true); + uilock = dlg.get(); } - // need to make sure all data is saved before we start the application + ON_BLOCK_EXIT([&]() { + if (m_UserInterface != nullptr) { + m_UserInterface->unlock(); + } }); + + DWORD ignoreExitCode; + waitForProcessCompletion(processHandle, exitCode ? exitCode : &ignoreExitCode, uilock); + cycleDiagnostics(); + } + + return processHandle; +} + + +HANDLE OrganizerCore::spawnBinaryProcess(const QFileInfo &binary, + const QString &arguments, + const QString &profileName, + const QDir ¤tDirectory, + const QString &steamAppID, + const QString &customOverwrite) +{ + prepareStart(); + + if (!binary.exists()) { + reportError( + tr("Executable \"%1\" not found").arg(binary.absoluteFilePath())); + return INVALID_HANDLE_VALUE; + } + + if (!steamAppID.isEmpty()) { + ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); + } else { + ::SetEnvironmentVariableW(L"SteamAPPId", + ToWString(m_Settings.getSteamAppID()).c_str()); + } + + // This could possibly be extracted somewhere else but it's probably for when + // we have more than one provider of game registration. + if ((QFileInfo( + managedGame()->gameDirectory().absoluteFilePath("steam_api.dll")) + .exists() + || QFileInfo(managedGame()->gameDirectory().absoluteFilePath( + "steam_api64.dll")) + .exists()) + && (m_Settings.getLoadMechanism() == LoadMechanism::LOAD_MODORGANIZER)) { + if (!testForSteam()) { + QWidget *window = qApp->activeWindow(); + if ((window != nullptr) && (!window->isVisible())) { + window = nullptr; + } + if (QuestionBoxMemory::query(window, "steamQuery", binary.fileName(), + tr("Start Steam?"), + tr("Steam is required to be running already to correctly start the game. " + "Should MO try to start steam now?"), + QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::Yes) { + startSteam(qApp->activeWindow()); + } + } + } + + while (m_DirectoryUpdate) { + ::Sleep(100); + QCoreApplication::processEvents(); + } + + // need to make sure all data is saved before we start the application + if (m_CurrentProfile != nullptr) { + m_CurrentProfile->writeModlistNow(true); + } + + // TODO: should also pass arguments + if (m_AboutToRun(binary.absoluteFilePath())) { + try { + m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); + } catch (const std::exception &e) { + QMessageBox::warning(qApp->activeWindow(), tr("Error"), e.what()); + return INVALID_HANDLE_VALUE; + } + + QString modsPath = settings().getModDirectory(); + + // Check if this a request with either an executable or a working directory under our mods folder + // then will start the processs in a virtualized "environment" with the appropriate paths fixed: + // (i.e. mods\FNIS\path\exe => game\data\path\exe) + QString cwdPath = currentDirectory.absolutePath(); + bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); + QString binPath = binary.absoluteFilePath(); + bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); + if (virtualizedCwd || virtualizedBin) { + if (virtualizedCwd) { + int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); + cwdPath = m_GamePlugin->dataDirectory().absolutePath() + cwdPath.mid(cwdOffset, -1); + } + + if (virtualizedBin) { + int binOffset = binPath.indexOf('/', modsPath.length() + 1); + binPath = m_GamePlugin->dataDirectory().absolutePath() + binPath.mid(binOffset, -1); + } + + QString cmdline + = QString("launch \"%1\" \"%2\" %3") + .arg(QDir::toNativeSeparators(cwdPath), + QDir::toNativeSeparators(binPath), arguments); + + qDebug() << "Spawning proxyed process <" << cmdline << ">"; + + return startBinary(QFileInfo(QCoreApplication::applicationFilePath()), + cmdline, QCoreApplication::applicationDirPath(), true); + } else { + qDebug() << "Spawning direct process <" << binPath << "," << arguments << "," << cwdPath << ">"; + return startBinary(binary, arguments, currentDirectory, true); + } + } else { + qDebug("start of \"%s\" canceled by plugin", + qPrintable(binary.absoluteFilePath())); + return INVALID_HANDLE_VALUE; + } +} + +HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut) +{ + if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) + throw std::runtime_error( + QString("Refusing to run executable from different instance %1:%2") + .arg(shortcut.instance(),shortcut.executable()) + .toLocal8Bit().constData()); + + Executable& exe = m_ExecutablesList.find(shortcut.executable()); + + return spawnBinaryDirect( + exe.m_BinaryInfo, exe.m_Arguments, + m_CurrentProfile->name(), + exe.m_WorkingDirectory.length() != 0 + ? exe.m_WorkingDirectory + : exe.m_BinaryInfo.absolutePath(), + exe.m_SteamAppID, ""); +} + +HANDLE OrganizerCore::startApplication(const QString &executable, + const QStringList &args, + const QString &cwd, + const QString &profile) +{ + QFileInfo binary; + QString arguments = args.join(" "); + QString currentDirectory = cwd; + QString profileName = profile; + if (profile.length() == 0) { if (m_CurrentProfile != nullptr) { - m_CurrentProfile->writeModlistNow(true); - } - - // TODO: should also pass arguments - if (m_AboutToRun(binary.absoluteFilePath())) { - try { - m_USVFS.updateMapping(fileMapping(profileName, customOverwrite)); - } catch (const std::exception& e) { - QMessageBox::warning(qApp->activeWindow(), tr("Error"), e.what()); - return INVALID_HANDLE_VALUE; - } - - QString modsPath = settings().getModDirectory(); - - // Check if this a request with either an executable or a working directory under our mods folder - // then will start the processs in a virtualized "environment" with the appropriate paths fixed: - // (i.e. mods\FNIS\path\exe => game\data\path\exe) - QString cwdPath = currentDirectory.absolutePath(); - bool virtualizedCwd = cwdPath.startsWith(modsPath, Qt::CaseInsensitive); - QString binPath = binary.absoluteFilePath(); - bool virtualizedBin = binPath.startsWith(modsPath, Qt::CaseInsensitive); - if (virtualizedCwd || virtualizedBin) { - if (virtualizedCwd) { - int cwdOffset = cwdPath.indexOf('/', modsPath.length() + 1); - cwdPath = m_GamePlugin->dataDirectory().absolutePath() + cwdPath.mid(cwdOffset, -1); - } - - if (virtualizedBin) { - int binOffset = binPath.indexOf('/', modsPath.length() + 1); - binPath = m_GamePlugin->dataDirectory().absolutePath() + binPath.mid(binOffset, -1); - } - - QString cmdline = QString("launch \"%1\" \"%2\" %3") - .arg(QDir::toNativeSeparators(cwdPath), QDir::toNativeSeparators(binPath), arguments); - - qDebug() << "Spawning proxyed process <" << cmdline << ">"; - - return startBinary(QFileInfo(QCoreApplication::applicationFilePath()), cmdline, - QCoreApplication::applicationDirPath(), true); - } else { - qDebug() << "Spawning direct process <" << binPath << "," << arguments << "," << cwdPath << ">"; - return startBinary(binary, arguments, currentDirectory, true); - } + profileName = m_CurrentProfile->name(); } else { - qDebug("start of \"%s\" canceled by plugin", qPrintable(binary.absoluteFilePath())); - return INVALID_HANDLE_VALUE; + throw MyException(tr("No profile set")); } -} + } + QString steamAppID; + QString customOverwrite; + if (executable.contains('\\') || executable.contains('/')) { + // file path -HANDLE OrganizerCore::runShortcut(const MOShortcut& shortcut) { - if (shortcut.hasInstance() && shortcut.instance() != InstanceManager::instance().currentInstance()) - throw std::runtime_error(QString("Refusing to run executable from different instance %1:%2") - .arg(shortcut.instance(), shortcut.executable()) - .toLocal8Bit() - .constData()); - - Executable& exe = m_ExecutablesList.find(shortcut.executable()); - - return spawnBinaryDirect(exe.m_BinaryInfo, exe.m_Arguments, m_CurrentProfile->name(), - exe.m_WorkingDirectory.length() != 0 ? exe.m_WorkingDirectory - : exe.m_BinaryInfo.absolutePath(), - exe.m_SteamAppID, ""); -} - -HANDLE OrganizerCore::startApplication(const QString& executable, const QStringList& args, const QString& cwd, - const QString& profile) { - QFileInfo binary; - QString arguments = args.join(" "); - QString currentDirectory = cwd; - QString profileName = profile; - if (profile.length() == 0) { - if (m_CurrentProfile != nullptr) { - profileName = m_CurrentProfile->name(); - } else { - throw MyException(tr("No profile set")); - } + binary = QFileInfo(executable); + if (binary.isRelative()) { + // relative path, should be relative to game directory + binary = QFileInfo( + managedGame()->gameDirectory().absoluteFilePath(executable)); } - QString steamAppID; - QString customOverwrite; - if (executable.contains('\\') || executable.contains('/')) { - // file path - - binary = QFileInfo(executable); - if (binary.isRelative()) { - // relative path, should be relative to game directory - binary = QFileInfo(managedGame()->gameDirectory().absoluteFilePath(executable)); - } - if (cwd.length() == 0) { - currentDirectory = binary.absolutePath(); - } - try { - const Executable& exe = m_ExecutablesList.findByBinary(binary); - steamAppID = exe.m_SteamAppID; - customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.m_Title).toString(); - } catch (const std::runtime_error&) { - // nop - } - } else { - // only a file name, search executables list - try { - const Executable& exe = m_ExecutablesList.find(executable); - steamAppID = exe.m_SteamAppID; - customOverwrite = m_CurrentProfile->setting("custom_overwrites", exe.m_Title).toString(); - if (arguments == "") { - arguments = exe.m_Arguments; - } - binary = exe.m_BinaryInfo; - if (cwd.length() == 0) { - currentDirectory = exe.m_WorkingDirectory; - } - } catch (const std::runtime_error&) { - qWarning("\"%s\" not set up as executable", executable.toUtf8().constData()); - binary = QFileInfo(executable); - } + if (cwd.length() == 0) { + currentDirectory = binary.absolutePath(); } - - return spawnBinaryDirect(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite); -} - -bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) { - ILockedWaitingForProcess* uilock = nullptr; + try { + const Executable &exe = m_ExecutablesList.findByBinary(binary); + steamAppID = exe.m_SteamAppID; + customOverwrite + = m_CurrentProfile->setting("custom_overwrites", exe.m_Title) + .toString(); + } catch (const std::runtime_error &) { + // nop + } + } else { + // only a file name, search executables list + try { + const Executable &exe = m_ExecutablesList.find(executable); + steamAppID = exe.m_SteamAppID; + customOverwrite + = m_CurrentProfile->setting("custom_overwrites", exe.m_Title) + .toString(); + if (arguments == "") { + arguments = exe.m_Arguments; + } + binary = exe.m_BinaryInfo; + if (cwd.length() == 0) { + currentDirectory = exe.m_WorkingDirectory; + } + } catch (const std::runtime_error &) { + qWarning("\"%s\" not set up as executable", + executable.toUtf8().constData()); + binary = QFileInfo(executable); + } + } + + return spawnBinaryDirect(binary, arguments, profileName, currentDirectory, steamAppID, customOverwrite); +} + +bool OrganizerCore::waitForApplication(HANDLE handle, LPDWORD exitCode) +{ + ILockedWaitingForProcess* uilock = nullptr; + if (m_UserInterface != nullptr) { + uilock = m_UserInterface->lock(); + } + + ON_BLOCK_EXIT([&] () { if (m_UserInterface != nullptr) { - uilock = m_UserInterface->lock(); - } - - ON_BLOCK_EXIT([&]() { - if (m_UserInterface != nullptr) { - m_UserInterface->unlock(); - } - }); - return waitForProcessCompletion(handle, exitCode, uilock); -} - -bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) { - bool originalHandle = true; - bool newHandle = true; - bool uiunlocked = false; + m_UserInterface->unlock(); + } }); + return waitForProcessCompletion(handle, exitCode, uilock); +} + +bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock) +{ + bool originalHandle = true; + bool newHandle = true; + bool uiunlocked = false; + + DWORD currentPID = 0; + QString processName; + auto waitForChildUntil = GetTickCount64(); + if (handle != INVALID_HANDLE_VALUE) { + currentPID = GetProcessId(handle); + processName = QString::fromStdWString(getProcessName(handle)); + } + + // Certain process names we wish to "hide" for aesthetic reason: + bool waitingOnHidden = false; + std::vector hiddenList; + hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); + for (QString hide : hiddenList) + if (processName.contains(hide, Qt::CaseInsensitive)) + waitingOnHidden = true; + // The main reason for adding the hidden list is to hide the MO proxy we use to spawn virtualized processes. + // On the one hand we want to display the real executable without it feeling laggy, on the other we don't want + // to requery processes all the time if for some reason we are waiting on hidden processes and find no "unhidden" + // process. For this reason we use exponential backoff and also start with a delibrately low value to improve + // the responsiveness of the initial update + DWORD64 nextHiddenCheck = GetTickCount64(); + DWORD64 nextHiddenCheckDelay = 50; + + constexpr DWORD INPUT_EVENT = WAIT_OBJECT_0 + 1; + DWORD res = WAIT_TIMEOUT; + while (handle != INVALID_HANDLE_VALUE && (newHandle || res == WAIT_TIMEOUT || res == INPUT_EVENT)) + { + if (newHandle) { + processName += QString(" (%1)").arg(currentPID); + if (uilock) + uilock->setProcessName(processName); + qDebug() << "Waiting for" + << (originalHandle ? "spawned" : "usvfs") + << "process completion :" << processName.toUtf8().constData(); + newHandle = false; + } + + // Wait for a an event on the handle, a key press, mouse click or timeout + res = MsgWaitForMultipleObjects(1, &handle, FALSE, 200, QS_KEY | QS_MOUSEBUTTON); + if (res == WAIT_FAILED) { + qWarning() << "Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED" << GetLastError(); + break; + } + + // keep processing events so the app doesn't appear dead + QCoreApplication::sendPostedEvents(); + QCoreApplication::processEvents(); + + if (uilock && uilock->unlockForced()) { + uiunlocked = true; + break; + } + + if (res == WAIT_OBJECT_0) { + // process we were waiting on has completed + if (originalHandle && exitCode && !::GetExitCodeProcess(handle, exitCode)) + qWarning() << "Failed getting exit code of complete process :" << GetLastError(); + CloseHandle(handle); + handle = INVALID_HANDLE_VALUE; + originalHandle = false; + // if the previous process spawned a child process and immediately exits we may miss it if we check immediately + waitForChildUntil = GetTickCount64() + 800; + } + + // search for another process to wait on if either: + // 1. we just completed waiting for a process and need to find/wait for an inject child + // 2. we are currently waiting on a hidden process so periodically check if there is a non-hidden process to wait on + bool firstIteration = true; + while ((handle == INVALID_HANDLE_VALUE && GetTickCount64() <= waitForChildUntil) + || (waitingOnHidden && GetTickCount64() >= nextHiddenCheck)) + { + if (firstIteration) + firstIteration = false; + else { + QThread::msleep(200); + QCoreApplication::sendPostedEvents(); + QCoreApplication::processEvents(); + } - DWORD currentPID = 0; - QString processName; - auto waitForChildUntil = GetTickCount64(); - if (handle != INVALID_HANDLE_VALUE) { + // search if there is another usvfs process active + handle = findAndOpenAUSVFSProcess(hiddenList, currentPID); + waitingOnHidden = false; + newHandle = handle != INVALID_HANDLE_VALUE; + if (newHandle) { currentPID = GetProcessId(handle); processName = QString::fromStdWString(getProcessName(handle)); - } - - // Certain process names we wish to "hide" for aesthetic reason: - bool waitingOnHidden = false; - std::vector hiddenList; - hiddenList.push_back(QFileInfo(QCoreApplication::applicationFilePath()).fileName()); - for (QString hide : hiddenList) - if (processName.contains(hide, Qt::CaseInsensitive)) + for (QString hide : hiddenList) + if (processName.contains(hide, Qt::CaseInsensitive)) waitingOnHidden = true; - // The main reason for adding the hidden list is to hide the MO proxy we use to spawn virtualized processes. - // On the one hand we want to display the real executable without it feeling laggy, on the other we don't want - // to requery processes all the time if for some reason we are waiting on hidden processes and find no "unhidden" - // process. For this reason we use exponential backoff and also start with a delibrately low value to improve - // the responsiveness of the initial update - DWORD64 nextHiddenCheck = GetTickCount64(); - DWORD64 nextHiddenCheckDelay = 50; - - constexpr DWORD INPUT_EVENT = WAIT_OBJECT_0 + 1; - DWORD res = WAIT_TIMEOUT; - while (handle != INVALID_HANDLE_VALUE && (newHandle || res == WAIT_TIMEOUT || res == INPUT_EVENT)) { - if (newHandle) { - processName += QString(" (%1)").arg(currentPID); - if (uilock) - uilock->setProcessName(processName); - qDebug() << "Waiting for" << (originalHandle ? "spawned" : "usvfs") - << "process completion :" << processName.toUtf8().constData(); - newHandle = false; - } - - // Wait for a an event on the handle, a key press, mouse click or timeout - res = MsgWaitForMultipleObjects(1, &handle, FALSE, 200, QS_KEY | QS_MOUSEBUTTON); - if (res == WAIT_FAILED) { - qWarning() << "Failed waiting for process completion : MsgWaitForMultipleObjects WAIT_FAILED" - << GetLastError(); - break; - } - - // keep processing events so the app doesn't appear dead - QCoreApplication::sendPostedEvents(); - QCoreApplication::processEvents(); - - if (uilock && uilock->unlockForced()) { - uiunlocked = true; - break; - } - - if (res == WAIT_OBJECT_0) { - // process we were waiting on has completed - if (originalHandle && exitCode && !::GetExitCodeProcess(handle, exitCode)) - qWarning() << "Failed getting exit code of complete process :" << GetLastError(); - CloseHandle(handle); - handle = INVALID_HANDLE_VALUE; - originalHandle = false; - // if the previous process spawned a child process and immediately exits we may miss it if we check - // immediately - waitForChildUntil = GetTickCount64() + 800; - } - - // search for another process to wait on if either: - // 1. we just completed waiting for a process and need to find/wait for an inject child - // 2. we are currently waiting on a hidden process so periodically check if there is a non-hidden process to - // wait on - bool firstIteration = true; - while ((handle == INVALID_HANDLE_VALUE && GetTickCount64() <= waitForChildUntil) || - (waitingOnHidden && GetTickCount64() >= nextHiddenCheck)) { - if (firstIteration) - firstIteration = false; - else { - QThread::msleep(200); - QCoreApplication::sendPostedEvents(); - QCoreApplication::processEvents(); - } - - // search if there is another usvfs process active - handle = findAndOpenAUSVFSProcess(hiddenList, currentPID); - waitingOnHidden = false; - newHandle = handle != INVALID_HANDLE_VALUE; - if (newHandle) { - currentPID = GetProcessId(handle); - processName = QString::fromStdWString(getProcessName(handle)); - for (QString hide : hiddenList) - if (processName.contains(hide, Qt::CaseInsensitive)) - waitingOnHidden = true; - } - if (waitingOnHidden) { - nextHiddenCheck = GetTickCount64() + nextHiddenCheckDelay; - nextHiddenCheckDelay = std::min(nextHiddenCheckDelay * 2, (DWORD64)2000); - } else { - nextHiddenCheck = GetTickCount64(); - nextHiddenCheckDelay = 200; - } - } + } + if (waitingOnHidden) { + nextHiddenCheck = GetTickCount64() + nextHiddenCheckDelay; + nextHiddenCheckDelay = std::min(nextHiddenCheckDelay * 2, (DWORD64) 2000); + } + else { + nextHiddenCheck = GetTickCount64(); + nextHiddenCheckDelay = 200; + } } + } - if (res == WAIT_OBJECT_0) - qDebug() << "Waiting for process completion successfull"; - else if (uiunlocked) - qDebug() << "Waiting for process completion aborted by UI"; - else - qDebug() << "Waiting for process completion not successfull :" << res; + if (res == WAIT_OBJECT_0) + qDebug() << "Waiting for process completion successfull"; + else if (uiunlocked) + qDebug() << "Waiting for process completion aborted by UI"; + else + qDebug() << "Waiting for process completion not successfull :" << res; - if (handle != INVALID_HANDLE_VALUE) - ::CloseHandle(handle); + if (handle != INVALID_HANDLE_VALUE) + ::CloseHandle(handle); - return res == WAIT_OBJECT_0; + return res == WAIT_OBJECT_0; } HANDLE OrganizerCore::findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid) { - // for practical reasons a querySize of 1 is probably enough, we use a larger query as a heuristics - // to find a more "aesthetic injected processes (attempting to comply to hiddenList and preferedParentPid) - constexpr size_t querySize = 100; - DWORD pids[querySize]; - size_t found = querySize; - if (!::GetVFSProcessList(&found, pids)) { - qWarning() << "Failed seeking USVFS processes : GetVFSProcessList failed?!"; - return INVALID_HANDLE_VALUE; + // for practical reasons a querySize of 1 is probably enough, we use a larger query as a heuristics + // to find a more "aesthetic injected processes (attempting to comply to hiddenList and preferedParentPid) + constexpr size_t querySize = 100; + DWORD pids[querySize]; + size_t found = querySize; + if (!::GetVFSProcessList(&found, pids)) { + qWarning() << "Failed seeking USVFS processes : GetVFSProcessList failed?!"; + return INVALID_HANDLE_VALUE; + } + + HANDLE best_match = INVALID_HANDLE_VALUE; + bool best_match_hidden = true; + for (size_t i = 0; i < found; ++i) { + if (pids[i] == GetCurrentProcessId()) + continue; // obviously don't wait for MO process + + HANDLE handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pids[i]); + if (handle == INVALID_HANDLE_VALUE) { + qWarning() << "Failed openning USVFS process " << pids[i] << " : OpenProcess failed" << GetLastError(); + continue; + } + + QString pname = QString::fromStdWString(getProcessName(handle)); + bool phidden = false; + for (auto hide : hiddenList) + if (pname.contains(hide, Qt::CaseInsensitive)) + phidden = true; + + bool pprefered = preferedParentPid && getProcessParentID(pids[i]) == preferedParentPid; + + if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) { + if (best_match != INVALID_HANDLE_VALUE) + CloseHandle(best_match); + best_match = handle; + best_match_hidden = phidden; } + else + CloseHandle(handle); - HANDLE best_match = INVALID_HANDLE_VALUE; - bool best_match_hidden = true; - for (size_t i = 0; i < found; ++i) { - if (pids[i] == GetCurrentProcessId()) - continue; // obviously don't wait for MO process - - HANDLE handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pids[i]); - if (handle == INVALID_HANDLE_VALUE) { - qWarning() << "Failed openning USVFS process " << pids[i] << " : OpenProcess failed" << GetLastError(); - continue; - } - - QString pname = QString::fromStdWString(getProcessName(handle)); - bool phidden = false; - for (auto hide : hiddenList) - if (pname.contains(hide, Qt::CaseInsensitive)) - phidden = true; - - bool pprefered = preferedParentPid && getProcessParentID(pids[i]) == preferedParentPid; - - if (best_match == INVALID_HANDLE_VALUE || best_match_hidden || (!phidden && pprefered)) { - if (best_match != INVALID_HANDLE_VALUE) - CloseHandle(best_match); - best_match = handle; - best_match_hidden = phidden; - } else - CloseHandle(handle); - - if (!phidden && pprefered) - return best_match; - } + if (!phidden && pprefered) + return best_match; + } - return best_match; + return best_match; } -bool OrganizerCore::onAboutToRun(const std::function& func) { - auto conn = m_AboutToRun.connect(func); - return conn.connected(); +bool OrganizerCore::onAboutToRun( + const std::function &func) +{ + auto conn = m_AboutToRun.connect(func); + return conn.connected(); } -bool OrganizerCore::onFinishedRun(const std::function& func) { - auto conn = m_FinishedRun.connect(func); - return conn.connected(); +bool OrganizerCore::onFinishedRun( + const std::function &func) +{ + auto conn = m_FinishedRun.connect(func); + return conn.connected(); } -bool OrganizerCore::onModInstalled(const std::function& func) { - auto conn = m_ModInstalled.connect(func); - return conn.connected(); +bool OrganizerCore::onModInstalled( + const std::function &func) +{ + auto conn = m_ModInstalled.connect(func); + return conn.connected(); } -void OrganizerCore::refreshModList(bool saveChanges) { - // don't lose changes! - if (saveChanges) { - m_CurrentProfile->writeModlistNow(true); - } - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign(), - managedGame()); +void OrganizerCore::refreshModList(bool saveChanges) +{ + // don't lose changes! + if (saveChanges) { + m_CurrentProfile->writeModlistNow(true); + } + ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, + m_Settings.displayForeign(), managedGame()); - m_CurrentProfile->refreshModStatus(); + m_CurrentProfile->refreshModStatus(); - m_ModList.notifyChange(-1); + m_ModList.notifyChange(-1); - refreshDirectoryStructure(); + refreshDirectoryStructure(); } -void OrganizerCore::refreshESPList() { - if (m_DirectoryUpdate) { - // don't mess up the esp list if we're currently updating the directory - // structure - m_PostRefreshTasks.append([this]() { this->refreshESPList(); }); - return; - } - m_CurrentProfile->writeModlist(); +void OrganizerCore::refreshESPList() +{ + if (m_DirectoryUpdate) { + // don't mess up the esp list if we're currently updating the directory + // structure + m_PostRefreshTasks.append([this]() { + this->refreshESPList(); + }); + return; + } + m_CurrentProfile->writeModlist(); - // clear list - try { - m_PluginList.refresh(m_CurrentProfile->name(), *m_DirectoryStructure, - m_CurrentProfile->getLockedOrderFileName()); - } catch (const std::exception& e) { - reportError(tr("Failed to refresh list of esps: %1").arg(e.what())); - } + // clear list + try { + m_PluginList.refresh(m_CurrentProfile->name(), *m_DirectoryStructure, + m_CurrentProfile->getLockedOrderFileName()); + } catch (const std::exception &e) { + reportError(tr("Failed to refresh list of esps: %1").arg(e.what())); + } } -void OrganizerCore::refreshBSAList() { - DataArchives* archives = m_GamePlugin->feature(); - - if (archives != nullptr) { - m_ArchivesInit = false; - - // default archives are the ones enabled outside MO. if the list can't be - // found (which might - // happen if ini files are missing) use hard-coded defaults (preferrably the - // same the game would use) - m_DefaultArchives = archives->archives(m_CurrentProfile); - if (m_DefaultArchives.length() == 0) { - m_DefaultArchives = archives->vanillaArchives(); - } +void OrganizerCore::refreshBSAList() +{ + DataArchives *archives = m_GamePlugin->feature(); - m_ActiveArchives.clear(); + if (archives != nullptr) { + m_ArchivesInit = false; - auto iter = enabledArchives(); - m_ActiveArchives = toStringList(iter.begin(), iter.end()); - if (m_ActiveArchives.isEmpty()) { - m_ActiveArchives = m_DefaultArchives; - } - - if (m_UserInterface != nullptr) { - m_UserInterface->updateBSAList(m_DefaultArchives, m_ActiveArchives); - } - - m_ArchivesInit = true; + // default archives are the ones enabled outside MO. if the list can't be + // found (which might + // happen if ini files are missing) use hard-coded defaults (preferrably the + // same the game would use) + m_DefaultArchives = archives->archives(m_CurrentProfile); + if (m_DefaultArchives.length() == 0) { + m_DefaultArchives = archives->vanillaArchives(); } -} - -void OrganizerCore::refreshLists() { - if ((m_CurrentProfile != nullptr) && m_DirectoryStructure->isPopulated()) { - refreshESPList(); - refreshBSAList(); - } // no point in refreshing lists if no files have been added to the directory - // tree -} -void OrganizerCore::updateModActiveState(int index, bool active) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - QDir dir(modInfo->absolutePath()); - for (const QString& esm : dir.entryList(QStringList() << "*.esm", QDir::Files)) { - m_PluginList.enableESP(esm, active); - } - int enabled = 0; - for (const QString& esl : dir.entryList(QStringList() << "*.esl", QDir::Files)) { - const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esl)); - if (file.get() == nullptr) { - qWarning("failed to activate %s", qPrintable(esl)); - continue; - } + m_ActiveArchives.clear(); - if (active != m_PluginList.isEnabled(esl) && file->getAlternatives().empty()) { - m_PluginList.enableESP(esl, active); - ++enabled; - } + auto iter = enabledArchives(); + m_ActiveArchives = toStringList(iter.begin(), iter.end()); + if (m_ActiveArchives.isEmpty()) { + m_ActiveArchives = m_DefaultArchives; } - QStringList esps = dir.entryList(QStringList() << "*.esp", QDir::Files); - for (const QString& esp : esps) { - const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esp)); - if (file.get() == nullptr) { - qWarning("failed to activate %s", qPrintable(esp)); - continue; - } - - if (active != m_PluginList.isEnabled(esp) && file->getAlternatives().empty()) { - m_PluginList.enableESP(esp, active); - ++enabled; - } - } - if (active && (enabled > 1)) { - MessageDialog::showMessage(tr("Multiple esps/esls activated, please check that they don't conflict."), - qApp->activeWindow()); - } - m_PluginList.refreshLoadOrder(); - // immediately save affected lists - m_PluginListsWriter.writeImmediately(false); -} - -void OrganizerCore::updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo) { - // add files of the bsa to the directory structure - m_DirectoryRefresher.addModFilesToStructure(m_DirectoryStructure, modInfo->name(), - m_CurrentProfile->getModPriority(index), modInfo->absolutePath(), - modInfo->stealFiles()); - DirectoryRefresher::cleanStructure(m_DirectoryStructure); - // need to refresh plugin list now so we can activate esps - refreshESPList(); - // activate all esps of the specified mod so the bsas get activated along with - // it - updateModActiveState(index, true); - // now we need to refresh the bsa list and save it so there is no confusion - // about what archives are avaiable and active - refreshBSAList(); + if (m_UserInterface != nullptr) { - m_UserInterface->archivesWriter().writeImmediately(false); - } - - std::vector archives = enabledArchives(); - m_DirectoryRefresher.setMods(m_CurrentProfile->getActiveMods(), - std::set(archives.begin(), archives.end())); - - // finally also add files from bsas to the directory structure - m_DirectoryRefresher.addModBSAToStructure(m_DirectoryStructure, modInfo->name(), - m_CurrentProfile->getModPriority(index), modInfo->absolutePath(), - modInfo->archives()); -} - -void OrganizerCore::requestDownload(const QUrl& url, QNetworkReply* reply) { - if (m_PluginContainer != nullptr) { - for (IPluginModPage* modPage : m_PluginContainer->plugins()) { - ModRepositoryFileInfo* fileInfo = new ModRepositoryFileInfo(); - if (modPage->handlesDownload(url, reply->url(), *fileInfo)) { - fileInfo->repository = modPage->name(); - m_DownloadManager.addDownload(reply, fileInfo); - return; - } - } - } - - // no mod found that could handle the download. Is it a nexus mod? - if (url.host() == "www.nexusmods.com") { - int modID = 0; - int fileID = 0; - QRegExp modExp("mods/(\\d+)"); - if (modExp.indexIn(url.toString()) != -1) { - modID = modExp.cap(1).toInt(); - } - QRegExp fileExp("fid=(\\d+)"); - if (fileExp.indexIn(reply->url().toString()) != -1) { - fileID = fileExp.cap(1).toInt(); - } - m_DownloadManager.addDownload(reply, new ModRepositoryFileInfo(modID, fileID)); - } else { - if (QMessageBox::question(qApp->activeWindow(), tr("Download?"), - tr("A download has been started but no installed " - "page plugin recognizes it.\n" - "If you download anyway no information (i.e. " - "version) will be associated with the " - "download.\n" - "Continue?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_DownloadManager.addDownload(reply, new ModRepositoryFileInfo()); - } + m_UserInterface->updateBSAList(m_DefaultArchives, m_ActiveArchives); } -} - -ModListSortProxy* OrganizerCore::createModListProxyModel() { - ModListSortProxy* result = new ModListSortProxy(m_CurrentProfile, this); - result->setSourceModel(&m_ModList); - return result; -} - -PluginListSortProxy* OrganizerCore::createPluginListProxyModel() { - PluginListSortProxy* result = new PluginListSortProxy(this); - result->setSourceModel(&m_PluginList); - return result; -} - -IPluginGame const* OrganizerCore::managedGame() const { return m_GamePlugin; } - -std::vector OrganizerCore::enabledArchives() { - std::vector result; - QFile archiveFile(m_CurrentProfile->getArchivesFileName()); - if (archiveFile.open(QIODevice::ReadOnly)) { - while (!archiveFile.atEnd()) { - result.push_back(QString::fromUtf8(archiveFile.readLine()).trimmed()); - } - archiveFile.close(); - } - return result; -} - -void OrganizerCore::refreshDirectoryStructure() { - if (!m_DirectoryUpdate) { - m_CurrentProfile->writeModlistNow(true); - m_DirectoryUpdate = true; - std::vector> activeModList = m_CurrentProfile->getActiveMods(); - auto archives = enabledArchives(); - m_DirectoryRefresher.setMods(activeModList, std::set(archives.begin(), archives.end())); - - QTimer::singleShot(0, &m_DirectoryRefresher, SLOT(refresh())); - } + m_ArchivesInit = true; + } } -void OrganizerCore::directory_refreshed() { - DirectoryEntry* newStructure = m_DirectoryRefresher.getDirectoryStructure(); - Q_ASSERT(newStructure != m_DirectoryStructure); - if (newStructure != nullptr) { - std::swap(m_DirectoryStructure, newStructure); - delete newStructure; - } else { - // TODO: don't know why this happens, this slot seems to get called twice - // with only one emit +void OrganizerCore::refreshLists() +{ + if ((m_CurrentProfile != nullptr) && m_DirectoryStructure->isPopulated()) { + refreshESPList(); + refreshBSAList(); + } // no point in refreshing lists if no files have been added to the directory + // tree +} + +void OrganizerCore::updateModActiveState(int index, bool active) +{ + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + QDir dir(modInfo->absolutePath()); + for (const QString &esm : + dir.entryList(QStringList() << "*.esm", QDir::Files)) { + m_PluginList.enableESP(esm, active); + } + int enabled = 0; + for (const QString &esl : + dir.entryList(QStringList() << "*.esl", QDir::Files)) { + const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esl)); + if (file.get() == nullptr) { + qWarning("failed to activate %s", qPrintable(esl)); + continue; + } + + if (active != m_PluginList.isEnabled(esl) + && file->getAlternatives().empty()) { + m_PluginList.enableESP(esl, active); + ++enabled; + } + } + QStringList esps = dir.entryList(QStringList() << "*.esp", QDir::Files); + for (const QString &esp : esps) { + const FileEntry::Ptr file = m_DirectoryStructure->findFile(ToWString(esp)); + if (file.get() == nullptr) { + qWarning("failed to activate %s", qPrintable(esp)); + continue; + } + + if (active != m_PluginList.isEnabled(esp) + && file->getAlternatives().empty()) { + m_PluginList.enableESP(esp, active); + ++enabled; + } + } + if (active && (enabled > 1)) { + MessageDialog::showMessage( + tr("Multiple esps/esls activated, please check that they don't conflict."), + qApp->activeWindow()); + } + m_PluginList.refreshLoadOrder(); + // immediately save affected lists + m_PluginListsWriter.writeImmediately(false); +} + +void OrganizerCore::updateModInDirectoryStructure(unsigned int index, + ModInfo::Ptr modInfo) +{ + // add files of the bsa to the directory structure + m_DirectoryRefresher.addModFilesToStructure( + m_DirectoryStructure, modInfo->name(), + m_CurrentProfile->getModPriority(index), modInfo->absolutePath(), + modInfo->stealFiles()); + DirectoryRefresher::cleanStructure(m_DirectoryStructure); + // need to refresh plugin list now so we can activate esps + refreshESPList(); + // activate all esps of the specified mod so the bsas get activated along with + // it + updateModActiveState(index, true); + // now we need to refresh the bsa list and save it so there is no confusion + // about what archives are avaiable and active + refreshBSAList(); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().writeImmediately(false); + } + + std::vector archives = enabledArchives(); + m_DirectoryRefresher.setMods( + m_CurrentProfile->getActiveMods(), + std::set(archives.begin(), archives.end())); + + // finally also add files from bsas to the directory structure + m_DirectoryRefresher.addModBSAToStructure( + m_DirectoryStructure, modInfo->name(), + m_CurrentProfile->getModPriority(index), modInfo->absolutePath(), + modInfo->archives()); +} + +void OrganizerCore::requestDownload(const QUrl &url, QNetworkReply *reply) +{ + if (m_PluginContainer != nullptr) { + for (IPluginModPage *modPage : + m_PluginContainer->plugins()) { + ModRepositoryFileInfo *fileInfo = new ModRepositoryFileInfo(); + if (modPage->handlesDownload(url, reply->url(), *fileInfo)) { + fileInfo->repository = modPage->name(); + m_DownloadManager.addDownload(reply, fileInfo); return; - } - m_DirectoryUpdate = false; - - for (int i = 0; i < m_ModList.rowCount(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - modInfo->clearCaches(); - } - for (auto task : m_PostRefreshTasks) { - task(); - } - m_PostRefreshTasks.clear(); - - if (m_CurrentProfile != nullptr) { - refreshLists(); - } + } + } + } + + // no mod found that could handle the download. Is it a nexus mod? + if (url.host() == "www.nexusmods.com") { + int modID = 0; + int fileID = 0; + QRegExp modExp("mods/(\\d+)"); + if (modExp.indexIn(url.toString()) != -1) { + modID = modExp.cap(1).toInt(); + } + QRegExp fileExp("fid=(\\d+)"); + if (fileExp.indexIn(reply->url().toString()) != -1) { + fileID = fileExp.cap(1).toInt(); + } + m_DownloadManager.addDownload(reply, + new ModRepositoryFileInfo(modID, fileID)); + } else { + if (QMessageBox::question(qApp->activeWindow(), tr("Download?"), + tr("A download has been started but no installed " + "page plugin recognizes it.\n" + "If you download anyway no information (i.e. " + "version) will be associated with the " + "download.\n" + "Continue?"), + QMessageBox::Yes | QMessageBox::No) + == QMessageBox::Yes) { + m_DownloadManager.addDownload(reply, new ModRepositoryFileInfo()); + } + } +} + +ModListSortProxy *OrganizerCore::createModListProxyModel() +{ + ModListSortProxy *result = new ModListSortProxy(m_CurrentProfile, this); + result->setSourceModel(&m_ModList); + return result; +} + +PluginListSortProxy *OrganizerCore::createPluginListProxyModel() +{ + PluginListSortProxy *result = new PluginListSortProxy(this); + result->setSourceModel(&m_PluginList); + return result; +} + +IPluginGame const *OrganizerCore::managedGame() const +{ + return m_GamePlugin; +} + +std::vector OrganizerCore::enabledArchives() +{ + std::vector result; + QFile archiveFile(m_CurrentProfile->getArchivesFileName()); + if (archiveFile.open(QIODevice::ReadOnly)) { + while (!archiveFile.atEnd()) { + result.push_back(QString::fromUtf8(archiveFile.readLine()).trimmed()); + } + archiveFile.close(); + } + return result; +} + +void OrganizerCore::refreshDirectoryStructure() +{ + if (!m_DirectoryUpdate) { + m_CurrentProfile->writeModlistNow(true); + + m_DirectoryUpdate = true; + std::vector> activeModList + = m_CurrentProfile->getActiveMods(); + auto archives = enabledArchives(); + m_DirectoryRefresher.setMods( + activeModList, std::set(archives.begin(), archives.end())); + + QTimer::singleShot(0, &m_DirectoryRefresher, SLOT(refresh())); + } +} + +void OrganizerCore::directory_refreshed() +{ + DirectoryEntry *newStructure = m_DirectoryRefresher.getDirectoryStructure(); + Q_ASSERT(newStructure != m_DirectoryStructure); + if (newStructure != nullptr) { + std::swap(m_DirectoryStructure, newStructure); + delete newStructure; + } else { + // TODO: don't know why this happens, this slot seems to get called twice + // with only one emit + return; + } + m_DirectoryUpdate = false; + + for (int i = 0; i < m_ModList.rowCount(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(i); + modInfo->clearCaches(); + } + for (auto task : m_PostRefreshTasks) { + task(); + } + m_PostRefreshTasks.clear(); + + if (m_CurrentProfile != nullptr) { + refreshLists(); + } } -void OrganizerCore::profileRefresh() { - // have to refresh mods twice (again in refreshModList), otherwise the refresh - // isn't complete. Not sure why - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign(), - managedGame()); - m_CurrentProfile->refreshModStatus(); +void OrganizerCore::profileRefresh() +{ + // have to refresh mods twice (again in refreshModList), otherwise the refresh + // isn't complete. Not sure why + ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, + m_Settings.displayForeign(), managedGame()); + m_CurrentProfile->refreshModStatus(); - refreshModList(); + refreshModList(); } -void OrganizerCore::modStatusChanged(unsigned int index) { - try { - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - if (m_CurrentProfile->modEnabled(index)) { - updateModInDirectoryStructure(index, modInfo); - } else { - updateModActiveState(index, false); - refreshESPList(); - if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { - FilesOrigin& origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); - origin.enable(false); - } - if (m_UserInterface != nullptr) { - m_UserInterface->archivesWriter().write(); - } - } - modInfo->clearCaches(); - - for (unsigned int i = 0; i < m_CurrentProfile->numMods(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(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); - } - } - m_DirectoryStructure->getFileRegister()->sortOrigins(); - - refreshLists(); - } catch (const std::exception& e) { - reportError(tr("failed to update mod list: %1").arg(e.what())); - } -} +void OrganizerCore::modStatusChanged(unsigned int index) +{ + try { + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + if (m_CurrentProfile->modEnabled(index)) { + updateModInDirectoryStructure(index, modInfo); + } else { + updateModActiveState(index, false); + refreshESPList(); + if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { + FilesOrigin &origin + = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); + origin.enable(false); + } + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); + } + } + modInfo->clearCaches(); + + for (unsigned int i = 0; i < m_CurrentProfile->numMods(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(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); + } + } + m_DirectoryStructure->getFileRegister()->sortOrigins(); -void OrganizerCore::loginSuccessful(bool necessary) { - if (necessary) { - MessageDialog::showMessage(tr("login successful"), qApp->activeWindow()); - } + refreshLists(); + } catch (const std::exception &e) { + reportError(tr("failed to update mod list: %1").arg(e.what())); + } +} + +void OrganizerCore::loginSuccessful(bool necessary) +{ + if (necessary) { + MessageDialog::showMessage(tr("login successful"), qApp->activeWindow()); + } + for (QString url : m_PendingDownloads) { + downloadRequestedNXM(url); + } + m_PendingDownloads.clear(); + for (auto task : m_PostLoginTasks) { + task(); + } + + m_PostLoginTasks.clear(); + NexusInterface::instance()->loginCompleted(); +} + +void OrganizerCore::loginSuccessfulUpdate(bool necessary) +{ + if (necessary) { + MessageDialog::showMessage(tr("login successful"), qApp->activeWindow()); + } + m_Updater.startUpdate(); +} + +void OrganizerCore::loginFailed(const QString &message) +{ + if (QMessageBox::question(qApp->activeWindow(), tr("Login failed"), + tr("Login failed, try again?")) + == QMessageBox::Yes) { + if (nexusLogin(true)) { + return; + } + } + + if (!m_PendingDownloads.isEmpty()) { + MessageDialog::showMessage( + tr("login failed: %1. Download will not be associated with an account") + .arg(message), + qApp->activeWindow()); for (QString url : m_PendingDownloads) { - downloadRequestedNXM(url); + downloadRequestedNXM(url); } m_PendingDownloads.clear(); - for (auto task : m_PostLoginTasks) { - task(); - } - - m_PostLoginTasks.clear(); - NexusInterface::instance()->loginCompleted(); -} - -void OrganizerCore::loginSuccessfulUpdate(bool necessary) { - if (necessary) { - MessageDialog::showMessage(tr("login successful"), qApp->activeWindow()); - } - m_Updater.startUpdate(); -} - -void OrganizerCore::loginFailed(const QString& message) { - if (QMessageBox::question(qApp->activeWindow(), tr("Login failed"), tr("Login failed, try again?")) == - QMessageBox::Yes) { - if (nexusLogin(true)) { - return; - } - } - - if (!m_PendingDownloads.isEmpty()) { - MessageDialog::showMessage(tr("login failed: %1. Download will not be associated with an account").arg(message), - qApp->activeWindow()); - for (QString url : m_PendingDownloads) { - downloadRequestedNXM(url); - } - m_PendingDownloads.clear(); - } else { - MessageDialog::showMessage(tr("login failed: %1").arg(message), qApp->activeWindow()); - m_PostLoginTasks.clear(); - } - NexusInterface::instance()->loginCompleted(); -} - -void OrganizerCore::loginFailedUpdate(const QString& message) { - MessageDialog::showMessage(tr("login failed: %1. You need to log-in with Nexus to update MO.").arg(message), + } else { + MessageDialog::showMessage(tr("login failed: %1").arg(message), qApp->activeWindow()); -} - -void OrganizerCore::syncOverwrite() { - unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { - std::vector flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); - }); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(overwriteIndex); - SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure, qApp->activeWindow()); - if (syncDialog.exec() == QDialog::Accepted) { - syncDialog.apply(QDir::fromNativeSeparators(m_Settings.getModDirectory())); - modInfo->testValid(); - refreshDirectoryStructure(); - } -} - -QString OrganizerCore::oldMO1HookDll() const { - if (auto extender = managedGame()->feature()) { - QString hookdll = QDir::toNativeSeparators( - managedGame()->dataDirectory().absoluteFilePath(extender->PluginPath() + "/hook.dll")); - if (QFile(hookdll).exists()) - return hookdll; - } - return QString(); -} - -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 sevre 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. - qWarning("hook.dll found in game folder: %s", qPrintable(hookdll)); - problems.push_back(PROBLEM_MO1SCRIPTEXTENDERWORKAROUND); - } - return problems; -} - -QString OrganizerCore::shortDescription(unsigned int key) const { - switch (key) { + m_PostLoginTasks.clear(); + } + NexusInterface::instance()->loginCompleted(); +} + +void OrganizerCore::loginFailedUpdate(const QString &message) +{ + MessageDialog::showMessage( + tr("login failed: %1. You need to log-in with Nexus to update MO.") + .arg(message), + qApp->activeWindow()); +} + +void OrganizerCore::syncOverwrite() +{ + unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { + std::vector flags = mod->getFlags(); + return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) + != flags.end(); + }); + + ModInfo::Ptr modInfo = ModInfo::getByIndex(overwriteIndex); + SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure, + qApp->activeWindow()); + if (syncDialog.exec() == QDialog::Accepted) { + syncDialog.apply(QDir::fromNativeSeparators(m_Settings.getModDirectory())); + modInfo->testValid(); + refreshDirectoryStructure(); + } +} + +QString OrganizerCore::oldMO1HookDll() const +{ + if (auto extender = managedGame()->feature()) { + QString hookdll = QDir::toNativeSeparators( + managedGame()->dataDirectory().absoluteFilePath(extender->PluginPath() + "/hook.dll")); + if (QFile(hookdll).exists()) + return hookdll; + } + return QString(); +} + +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 sevre 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. + qWarning("hook.dll found in game folder: %s", qPrintable(hookdll)); + problems.push_back(PROBLEM_MO1SCRIPTEXTENDERWORKAROUND); + } + return problems; +} + +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"); + return tr("MO1 \"Script Extender\" load mechanism has left hook.dll in your game folder"); } break; - default: { return tr("Description missing"); } break; - } + default: { + return tr("Description missing"); + } break; + } } -QString OrganizerCore::fullDescription(unsigned int key) const { - switch (key) { +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; - } + 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; + } } -bool OrganizerCore::hasGuidedFix(unsigned int) const { return false; } +bool OrganizerCore::hasGuidedFix(unsigned int) const +{ + return false; +} -void OrganizerCore::startGuidedFix(unsigned int) const {} +void OrganizerCore::startGuidedFix(unsigned int) const +{ +} -bool OrganizerCore::saveCurrentLists() { - if (m_DirectoryUpdate) { - qWarning("not saving lists during directory update"); - return false; - } +bool OrganizerCore::saveCurrentLists() +{ + if (m_DirectoryUpdate) { + qWarning("not saving lists during directory update"); + return false; + } - try { - savePluginList(); - if (m_UserInterface != nullptr) { - m_UserInterface->archivesWriter().write(); - } - } catch (const std::exception& e) { - reportError(tr("failed to save load order: %1").arg(e.what())); + try { + savePluginList(); + if (m_UserInterface != nullptr) { + m_UserInterface->archivesWriter().write(); } + } catch (const std::exception &e) { + reportError(tr("failed to save load order: %1").arg(e.what())); + } - return true; + return true; } -void OrganizerCore::savePluginList() { - if (m_DirectoryUpdate) { - // delay save till after directory update - m_PostRefreshTasks.append([this]() { this->savePluginList(); }); - return; - } - m_PluginList.saveTo(m_CurrentProfile->getLockedOrderFileName(), m_CurrentProfile->getDeleterFileName(), - m_Settings.hideUncheckedPlugins()); - m_PluginList.saveLoadOrder(*m_DirectoryStructure); +void OrganizerCore::savePluginList() +{ + if (m_DirectoryUpdate) { + // delay save till after directory update + m_PostRefreshTasks.append([this]() { + this->savePluginList(); + }); + return; + } + m_PluginList.saveTo(m_CurrentProfile->getLockedOrderFileName(), + m_CurrentProfile->getDeleterFileName(), + m_Settings.hideUncheckedPlugins()); + m_PluginList.saveLoadOrder(*m_DirectoryStructure); } -void OrganizerCore::prepareStart() { - if (m_CurrentProfile == nullptr) { - return; - } - m_CurrentProfile->writeModlist(); - m_CurrentProfile->createTweakedIniFile(); - saveCurrentLists(); - m_Settings.setupLoadMechanism(); - storeSettings(); +void OrganizerCore::prepareStart() +{ + if (m_CurrentProfile == nullptr) { + return; + } + m_CurrentProfile->writeModlist(); + m_CurrentProfile->createTweakedIniFile(); + saveCurrentLists(); + m_Settings.setupLoadMechanism(); + storeSettings(); } -std::vector OrganizerCore::fileMapping(const QString& profileName, const QString& customOverwrite) { - // need to wait until directory structure - while (m_DirectoryUpdate) { - ::Sleep(100); - QCoreApplication::processEvents(); - } - - IPluginGame* game = qApp->property("managed_game").value(); - Profile profile(QDir(m_Settings.getProfileDirectory() + "/" + profileName), game); +std::vector OrganizerCore::fileMapping(const QString &profileName, + const QString &customOverwrite) +{ + // need to wait until directory structure + while (m_DirectoryUpdate) { + ::Sleep(100); + QCoreApplication::processEvents(); + } - MappingType result; + IPluginGame *game = qApp->property("managed_game").value(); + Profile profile(QDir(m_Settings.getProfileDirectory() + "/" + profileName), + game); - QString dataPath = QDir::toNativeSeparators(game->dataDirectory().absolutePath()); + MappingType result; - bool overwriteActive = false; + QString dataPath + = QDir::toNativeSeparators(game->dataDirectory().absolutePath()); - for (auto mod : profile.getActiveMods()) { - if (std::get<0>(mod).compare("overwrite", Qt::CaseInsensitive) == 0) { - continue; - } - - unsigned int modIndex = ModInfo::getIndex(std::get<0>(mod)); - ModInfo::Ptr modPtr = ModInfo::getByIndex(modIndex); - - bool createTarget = customOverwrite == std::get<0>(mod); - - overwriteActive |= createTarget; + bool overwriteActive = false; - if (modPtr->isRegular()) { - result.insert(result.end(), {QDir::toNativeSeparators(std::get<1>(mod)), dataPath, true, createTarget}); - } + for (auto mod : profile.getActiveMods()) { + if (std::get<0>(mod).compare("overwrite", Qt::CaseInsensitive) == 0) { + continue; } - if (!overwriteActive && !customOverwrite.isEmpty()) { - throw MyException(tr("The designated write target \"%1\" is not enabled.").arg(customOverwrite)); - } + unsigned int modIndex = ModInfo::getIndex(std::get<0>(mod)); + ModInfo::Ptr modPtr = ModInfo::getByIndex(modIndex); - if (m_CurrentProfile->localSavesEnabled()) { - LocalSavegames* localSaves = game->feature(); - if (localSaves != nullptr) { - MappingType saveMap = localSaves->mappings(currentProfile()->absolutePath() + "/saves"); - result.reserve(result.size() + saveMap.size()); - result.insert(result.end(), saveMap.begin(), saveMap.end()); - } else { - qWarning("local save games not supported by this game plugin"); - } - } + bool createTarget = customOverwrite == std::get<0>(mod); - result.insert(result.end(), {QDir::toNativeSeparators(m_Settings.getOverwriteDirectory()), dataPath, true, - customOverwrite.isEmpty()}); + overwriteActive |= createTarget; - for (MOBase::IPluginFileMapper* mapper : m_PluginContainer->plugins()) { - IPlugin* plugin = dynamic_cast(mapper); - if (plugin->isActive()) { - MappingType pluginMap = mapper->mappings(); - result.reserve(result.size() + pluginMap.size()); - result.insert(result.end(), pluginMap.begin(), pluginMap.end()); - } + if (modPtr->isRegular()) { + result.insert(result.end(), {QDir::toNativeSeparators(std::get<1>(mod)), + dataPath, true, createTarget}); } + } - return result; -} - -std::vector OrganizerCore::fileMapping(const QString& dataPath, const QString& relPath, - const DirectoryEntry* base, const DirectoryEntry* directoryEntry, - int createDestination) { - std::vector result; + if (!overwriteActive && !customOverwrite.isEmpty()) { + throw MyException(tr("The designated write target \"%1\" is not enabled.") + .arg(customOverwrite)); + } - for (FileEntry::Ptr current : directoryEntry->getFiles()) { - bool isArchive = false; - 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; - if (source != target) { - result.push_back({source, target, false, false}); - } + if (m_CurrentProfile->localSavesEnabled()) { + LocalSavegames *localSaves = game->feature(); + if (localSaves != nullptr) { + MappingType saveMap + = localSaves->mappings(currentProfile()->absolutePath() + "/saves"); + result.reserve(result.size() + saveMap.size()); + result.insert(result.end(), saveMap.begin(), saveMap.end()); + } else { + qWarning("local save games not supported by this game plugin"); } + } - // recurse into subdirectories - std::vector::const_iterator current, end; - directoryEntry->getSubDirectories(current, end); - for (; current != end; ++current) { - int origin = (*current)->anyOrigin(); - - QString originPath = QString::fromStdWString(base->getOriginByID(origin).getPath()); - QString dirName = QString::fromStdWString((*current)->getName()); - QString source = originPath + relPath + dirName; - QString target = dataPath + relPath + dirName; - - bool writeDestination = (base == directoryEntry) && (origin == createDestination); + result.insert(result.end(), { + QDir::toNativeSeparators(m_Settings.getOverwriteDirectory()), + dataPath, + true, + customOverwrite.isEmpty() + }); - result.push_back({source, target, true, writeDestination}); - std::vector subRes = - fileMapping(dataPath, relPath + dirName + "\\", base, *current, createDestination); - result.insert(result.end(), subRes.begin(), subRes.end()); - } - return result; + for (MOBase::IPluginFileMapper *mapper : + m_PluginContainer->plugins()) { + IPlugin *plugin = dynamic_cast(mapper); + if (plugin->isActive()) { + MappingType pluginMap = mapper->mappings(); + result.reserve(result.size() + pluginMap.size()); + result.insert(result.end(), pluginMap.begin(), pluginMap.end()); + } + } + + return result; +} + + +std::vector OrganizerCore::fileMapping( + const QString &dataPath, const QString &relPath, const DirectoryEntry *base, + const DirectoryEntry *directoryEntry, int createDestination) +{ + std::vector result; + + for (FileEntry::Ptr current : directoryEntry->getFiles()) { + bool isArchive = false; + 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; + if (source != target) { + result.push_back({source, target, false, false}); + } + } + + // recurse into subdirectories + std::vector::const_iterator current, end; + directoryEntry->getSubDirectories(current, end); + for (; current != end; ++current) { + int origin = (*current)->anyOrigin(); + + QString originPath + = QString::fromStdWString(base->getOriginByID(origin).getPath()); + QString dirName = QString::fromStdWString((*current)->getName()); + QString source = originPath + relPath + dirName; + QString target = dataPath + relPath + dirName; + + bool writeDestination + = (base == directoryEntry) && (origin == createDestination); + + result.push_back({source, target, true, writeDestination}); + std::vector subRes = fileMapping( + dataPath, relPath + dirName + "\\", base, *current, createDestination); + result.insert(result.end(), subRes.begin(), subRes.end()); + } + return result; } diff --git a/src/organizercore.h b/src/organizercore.h index ad6608f2..9e81f0c3 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -1,36 +1,34 @@ #ifndef ORGANIZERCORE_H #define ORGANIZERCORE_H -#include "directoryrefresher.h" -#include "downloadmanager.h" -#include "executableslist.h" -#include "installationmanager.h" + +#include "selfupdater.h" #include "iuserinterface.h" //should be class IUserInterface; -#include "modinfo.h" +#include "settings.h" #include "modlist.h" -#include "moshortcut.h" +#include "modinfo.h" #include "pluginlist.h" -#include "selfupdater.h" -#include "settings.h" +#include "directoryrefresher.h" +#include "installationmanager.h" +#include "downloadmanager.h" +#include "executableslist.h" #include "usvfsconnector.h" -#include -#include +#include "moshortcut.h" #include #include #include #include +#include +#include class ModListSortProxy; class PluginListSortProxy; class Profile; namespace MOBase { -template -class GuessedValue; -class IModInterface; -} // namespace MOBase -namespace MOShared { -class DirectoryEntry; + template class GuessedValue; + class IModInterface; } +namespace MOShared { class DirectoryEntry; } #include #include @@ -54,273 +52,293 @@ class QWidget; class PluginContainer; namespace MOBase { -class IPluginGame; + class IPluginGame; } -class OrganizerCore : public QObject, public MOBase::IPluginDiagnose { +class OrganizerCore : public QObject, public MOBase::IPluginDiagnose +{ - Q_OBJECT - Q_INTERFACES(MOBase::IPluginDiagnose) + Q_OBJECT + Q_INTERFACES(MOBase::IPluginDiagnose) private: - struct SignalCombinerAnd { - typedef bool result_type; - template - bool operator()(InputIterator first, InputIterator last) const { - while (first != last) { - if (!(*first)) { - return false; - } - ++first; - } - return true; + + struct SignalCombinerAnd + { + typedef bool result_type; + template + bool operator()(InputIterator first, InputIterator last) const + { + while (first != last) { + if (!(*first)) { + return false; } - }; + ++first; + } + return true; + } + }; private: - typedef boost::signals2::signal SignalAboutToRunApplication; - typedef boost::signals2::signal SignalFinishedRunApplication; - typedef boost::signals2::signal SignalModInstalled; + + typedef boost::signals2::signal SignalAboutToRunApplication; + typedef boost::signals2::signal SignalFinishedRunApplication; + typedef boost::signals2::signal SignalModInstalled; public: - static bool isNxmLink(const QString& link) { return link.startsWith("nxm://", Qt::CaseInsensitive); } + static bool isNxmLink(const QString &link) { return link.startsWith("nxm://", Qt::CaseInsensitive); } - OrganizerCore(const QSettings& initSettings); + OrganizerCore(const QSettings &initSettings); - ~OrganizerCore(); + ~OrganizerCore(); - void setUserInterface(IUserInterface* userInterface, QWidget* widget); - void connectPlugins(PluginContainer* container); - void disconnectPlugins(); + void setUserInterface(IUserInterface *userInterface, QWidget *widget); + void connectPlugins(PluginContainer *container); + void disconnectPlugins(); - void setManagedGame(MOBase::IPluginGame* game); + void setManagedGame(MOBase::IPluginGame *game); - void updateExecutablesList(QSettings& settings); + void updateExecutablesList(QSettings &settings); - void startMOUpdate(); + void startMOUpdate(); - Settings& settings(); - SelfUpdater* updater() { return &m_Updater; } - InstallationManager* installationManager(); - MOShared::DirectoryEntry* directoryStructure() { return m_DirectoryStructure; } - DirectoryRefresher* directoryRefresher() { return &m_DirectoryRefresher; } - ExecutablesList* executablesList() { return &m_ExecutablesList; } - void setExecutablesList(const ExecutablesList& executablesList) { m_ExecutablesList = executablesList; } + Settings &settings(); + SelfUpdater *updater() { return &m_Updater; } + InstallationManager *installationManager(); + MOShared::DirectoryEntry *directoryStructure() { return m_DirectoryStructure; } + DirectoryRefresher *directoryRefresher() { return &m_DirectoryRefresher; } + ExecutablesList *executablesList() { return &m_ExecutablesList; } + void setExecutablesList(const ExecutablesList &executablesList) { + m_ExecutablesList = executablesList; + } - Profile* currentProfile() const { return m_CurrentProfile; } - void setCurrentProfile(const QString& profileName); + Profile *currentProfile() const { return m_CurrentProfile; } + void setCurrentProfile(const QString &profileName); - std::vector enabledArchives(); + std::vector enabledArchives(); - MOBase::VersionInfo getVersion() const { return m_Updater.getVersion(); } + MOBase::VersionInfo getVersion() const { return m_Updater.getVersion(); } - ModListSortProxy* createModListProxyModel(); - PluginListSortProxy* createPluginListProxyModel(); + ModListSortProxy *createModListProxyModel(); + PluginListSortProxy *createPluginListProxyModel(); - MOBase::IPluginGame const* managedGame() const; + MOBase::IPluginGame const *managedGame() const; - bool isArchivesInit() const { return m_ArchivesInit; } + bool isArchivesInit() const { return m_ArchivesInit; } - bool saveCurrentLists(); + bool saveCurrentLists(); - void prepareStart(); + void prepareStart(); - void refreshESPList(); - void refreshBSAList(); + void refreshESPList(); + void refreshBSAList(); - void refreshDirectoryStructure(); - void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); + void refreshDirectoryStructure(); + void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); - void doAfterLogin(const std::function& function) { m_PostLoginTasks.append(function); } + void doAfterLogin(const std::function &function) { m_PostLoginTasks.append(function); } - void spawnBinary(const QFileInfo& binary, const QString& arguments = "", const QDir& currentDirectory = QDir(), - const QString& steamAppID = "", const QString& customOverwrite = ""); + void spawnBinary(const QFileInfo &binary, const QString &arguments = "", + const QDir ¤tDirectory = QDir(), + const QString &steamAppID = "", + const QString &customOverwrite = ""); - HANDLE spawnBinaryDirect(const QFileInfo& binary, const QString& arguments, const QString& profileName, - const QDir& currentDirectory, const QString& steamAppID, const QString& customOverwrite, - LPDWORD exitCode = nullptr); + HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, + const QString &profileName, + const QDir ¤tDirectory, + const QString &steamAppID, + const QString &customOverwrite, + LPDWORD exitCode = nullptr); - HANDLE spawnBinaryProcess(const QFileInfo& binary, const QString& arguments, const QString& profileName, - const QDir& currentDirectory, const QString& steamAppID, const QString& customOverwrite); + HANDLE spawnBinaryProcess(const QFileInfo &binary, const QString &arguments, + const QString &profileName, + const QDir ¤tDirectory, + const QString &steamAppID, + const QString &customOverwrite); - void loginSuccessfulUpdate(bool necessary); - void loginFailedUpdate(const QString& message); + void loginSuccessfulUpdate(bool necessary); + void loginFailedUpdate(const QString &message); - static bool createAndMakeWritable(const QString& path); - bool bootstrap(); - void createDefaultProfile(); + static bool createAndMakeWritable(const QString &path); + bool bootstrap(); + void createDefaultProfile(); - MOBase::DelayedFileWriter& pluginsWriter() { return m_PluginListsWriter; } + MOBase::DelayedFileWriter &pluginsWriter() { return m_PluginListsWriter; } - void prepareVFS(); + void prepareVFS(); - void updateVFSParams(int logLevel, int crashDumpsType); + void updateVFSParams(int logLevel, int crashDumpsType); - bool cycleDiagnostics(); + bool cycleDiagnostics(); - static CrashDumpsType getGlobalCrashDumpsType() { return m_globalCrashDumpsType; } - static void setGlobalCrashDumpsType(int crashDumpsType); - static std::wstring crashDumpsPath(); + static CrashDumpsType getGlobalCrashDumpsType() { return m_globalCrashDumpsType; } + static void setGlobalCrashDumpsType(int crashDumpsType); + static std::wstring crashDumpsPath(); public: - MOBase::IModRepositoryBridge* createNexusBridge() const; - QString profileName() const; - QString profilePath() const; - QString downloadsPath() const; - QString overwritePath() const; - QString basePath() const; - MOBase::VersionInfo appVersion() const; - MOBase::IModInterface* getMod(const QString& name) const; - MOBase::IModInterface* createMod(MOBase::GuessedValue& name); - bool removeMod(MOBase::IModInterface* mod); - 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); - QString pluginDataPath() const; - virtual MOBase::IModInterface* installMod(const QString& fileName, 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(); - HANDLE runShortcut(const MOShortcut& shortcut); - HANDLE startApplication(const QString& executable, const QStringList& args, const QString& cwd, - const QString& profile); - bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); - HANDLE findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid); - bool onModInstalled(const std::function& func); - bool onAboutToRun(const std::function& func); - bool onFinishedRun(const std::function& func); - void refreshModList(bool saveChanges = true); - QStringList modsSortedByProfilePriority() const; + MOBase::IModRepositoryBridge *createNexusBridge() const; + QString profileName() const; + QString profilePath() const; + QString downloadsPath() const; + QString overwritePath() const; + QString basePath() const; + MOBase::VersionInfo appVersion() const; + MOBase::IModInterface *getMod(const QString &name) const; + MOBase::IModInterface *createMod(MOBase::GuessedValue &name); + bool removeMod(MOBase::IModInterface *mod); + 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); + QString pluginDataPath() const; + virtual MOBase::IModInterface *installMod(const QString &fileName, 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(); + HANDLE runShortcut(const MOShortcut& shortcut); + HANDLE startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile); + bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); + HANDLE findAndOpenAUSVFSProcess(const std::vector& hiddenList, DWORD preferedParentPid); + bool onModInstalled(const std::function &func); + bool onAboutToRun(const std::function &func); + bool onFinishedRun(const std::function &func); + void refreshModList(bool saveChanges = true); + QStringList modsSortedByProfilePriority() 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; + + 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 externalMessage(const QString& message); + void profileRefresh(); + void externalMessage(const QString &message); - void syncOverwrite(); + void syncOverwrite(); - void savePluginList(); + void savePluginList(); - void refreshLists(); + void refreshLists(); - void installDownload(int downloadIndex); + void installDownload(int downloadIndex); - void modStatusChanged(unsigned int index); - void requestDownload(const QUrl& url, QNetworkReply* reply); - void downloadRequestedNXM(const QString& url); + void modStatusChanged(unsigned int index); + void requestDownload(const QUrl &url, QNetworkReply *reply); + void downloadRequestedNXM(const QString &url); - bool nexusLogin(bool retry = false); + bool nexusLogin(bool retry = false); signals: - /** - * @brief emitted after a mod has been installed - * @node this is currently only used for tutorials - */ - void modInstalled(const QString& modName); + /** + * @brief emitted after a mod has been installed + * @node this is currently only used for tutorials + */ + void modInstalled(const QString &modName); - void managedGameChanged(MOBase::IPluginGame const* gamePlugin); + void managedGameChanged(MOBase::IPluginGame const *gamePlugin); - void close(); + void close(); private: - void storeSettings(); - QSettings::Status storeSettings(const QString& fileName); + void storeSettings(); - QString commitSettings(const QString& iniFile); + QSettings::Status storeSettings(const QString &fileName); - bool queryLogin(QString& username, QString& password); + QString commitSettings(const QString &iniFile); - void updateModActiveState(int index, bool active); + bool queryLogin(QString &username, QString &password); - bool testForSteam(); + void updateModActiveState(int index, bool active); - bool createDirectory(const QString& path); + bool testForSteam(); - QString oldMO1HookDll() const; + bool createDirectory(const QString &path); - /** - * @brief return a descriptor of the mappings real file->virtual file - */ - std::vector fileMapping(const QString& profile, const QString& customOverwrite); + QString oldMO1HookDll() const; - std::vector fileMapping(const QString& dataPath, const QString& relPath, - const MOShared::DirectoryEntry* base, - const MOShared::DirectoryEntry* directoryEntry, int createDestination); + /** + * @brief return a descriptor of the mappings real file->virtual file + */ + std::vector fileMapping(const QString &profile, + const QString &customOverwrite); - bool waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); + std::vector + fileMapping(const QString &dataPath, const QString &relPath, + const MOShared::DirectoryEntry *base, + const MOShared::DirectoryEntry *directoryEntry, + int createDestination); + + bool waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, ILockedWaitingForProcess* uilock); private slots: - void directory_refreshed(); - void downloadRequested(QNetworkReply* reply, 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); + void directory_refreshed(); + void downloadRequested(QNetworkReply *reply, 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; + static const unsigned int PROBLEM_MO1SCRIPTEXTENDERWORKAROUND = 1; private: - IUserInterface* m_UserInterface; - PluginContainer* m_PluginContainer; - QString m_GameName; - MOBase::IPluginGame* m_GamePlugin; - Profile* m_CurrentProfile; + IUserInterface *m_UserInterface; + PluginContainer *m_PluginContainer; + QString m_GameName; + MOBase::IPluginGame *m_GamePlugin; + + Profile *m_CurrentProfile; + + Settings m_Settings; - Settings m_Settings; + SelfUpdater m_Updater; - SelfUpdater m_Updater; + SignalAboutToRunApplication m_AboutToRun; + SignalFinishedRunApplication m_FinishedRun; + SignalModInstalled m_ModInstalled; - SignalAboutToRunApplication m_AboutToRun; - SignalFinishedRunApplication m_FinishedRun; - SignalModInstalled m_ModInstalled; + ModList m_ModList; + PluginList m_PluginList; - ModList m_ModList; - PluginList m_PluginList; - QList> m_PostLoginTasks; - QList> m_PostRefreshTasks; + QList> m_PostLoginTasks; + QList> m_PostRefreshTasks; - ExecutablesList m_ExecutablesList; - QStringList m_PendingDownloads; - QStringList m_DefaultArchives; - QStringList m_ActiveArchives; + ExecutablesList m_ExecutablesList; + QStringList m_PendingDownloads; + QStringList m_DefaultArchives; + QStringList m_ActiveArchives; - DirectoryRefresher m_DirectoryRefresher; - MOShared::DirectoryEntry* m_DirectoryStructure; + DirectoryRefresher m_DirectoryRefresher; + MOShared::DirectoryEntry *m_DirectoryStructure; - DownloadManager m_DownloadManager; - InstallationManager m_InstallationManager; + DownloadManager m_DownloadManager; + InstallationManager m_InstallationManager; - QThread m_RefresherThread; + QThread m_RefresherThread; - bool m_AskForNexusPW; - bool m_DirectoryUpdate; - bool m_ArchivesInit; + bool m_AskForNexusPW; + bool m_DirectoryUpdate; + bool m_ArchivesInit; - MOBase::DelayedFileWriter m_PluginListsWriter; - UsvfsConnector m_USVFS; + MOBase::DelayedFileWriter m_PluginListsWriter; + UsvfsConnector m_USVFS; - static CrashDumpsType m_globalCrashDumpsType; + static CrashDumpsType m_globalCrashDumpsType; }; #endif // ORGANIZERCORE_H diff --git a/src/organizerproxy.cpp b/src/organizerproxy.cpp index bfb712af..cf2e29e3 100644 --- a/src/organizerproxy.cpp +++ b/src/organizerproxy.cpp @@ -8,109 +8,182 @@ using namespace MOBase; using namespace MOShared; -OrganizerProxy::OrganizerProxy(OrganizerCore* organizer, const QString& pluginName) - : m_Proxied(organizer), m_PluginName(pluginName) {} -IModRepositoryBridge* OrganizerProxy::createNexusBridge() const { return new NexusBridge(m_PluginName); } +OrganizerProxy::OrganizerProxy(OrganizerCore *organizer, const QString &pluginName) + : m_Proxied(organizer) + , m_PluginName(pluginName) +{ +} -QString OrganizerProxy::profileName() const { return m_Proxied->profileName(); } +IModRepositoryBridge *OrganizerProxy::createNexusBridge() const +{ + return new NexusBridge(m_PluginName); +} -QString OrganizerProxy::profilePath() const { return m_Proxied->profilePath(); } +QString OrganizerProxy::profileName() const +{ + return m_Proxied->profileName(); +} -QString OrganizerProxy::downloadsPath() const { return m_Proxied->downloadsPath(); } +QString OrganizerProxy::profilePath() const +{ + return m_Proxied->profilePath(); +} -QString OrganizerProxy::overwritePath() const { - /*return QDir::fromNativeSeparators(qApp->property("dataPath").toString()) - + "/" - + ToQString(AppConfig::overwritePath());*/ - return m_Proxied->overwritePath(); +QString OrganizerProxy::downloadsPath() const +{ + return m_Proxied->downloadsPath(); } -QString OrganizerProxy::basePath() const { return m_Proxied->basePath(); } +QString OrganizerProxy::overwritePath() const +{ + /*return QDir::fromNativeSeparators(qApp->property("dataPath").toString()) + + "/" + + ToQString(AppConfig::overwritePath());*/ + return m_Proxied->overwritePath(); +} -VersionInfo OrganizerProxy::appVersion() const { return m_Proxied->appVersion(); } +QString OrganizerProxy::basePath() const +{ + return m_Proxied->basePath(); +} -IModInterface* OrganizerProxy::getMod(const QString& name) const { return m_Proxied->getMod(name); } +VersionInfo OrganizerProxy::appVersion() const +{ + return m_Proxied->appVersion(); +} -IModInterface* OrganizerProxy::createMod(MOBase::GuessedValue& name) { return m_Proxied->createMod(name); } +IModInterface *OrganizerProxy::getMod(const QString &name) const +{ + return m_Proxied->getMod(name); +} -bool OrganizerProxy::removeMod(IModInterface* mod) { return m_Proxied->removeMod(mod); } +IModInterface *OrganizerProxy::createMod(MOBase::GuessedValue &name) +{ + return m_Proxied->createMod(name); +} -void OrganizerProxy::modDataChanged(IModInterface* mod) { m_Proxied->modDataChanged(mod); } +bool OrganizerProxy::removeMod(IModInterface *mod) +{ + return m_Proxied->removeMod(mod); +} -QVariant OrganizerProxy::pluginSetting(const QString& pluginName, const QString& key) const { - return m_Proxied->pluginSetting(pluginName, key); +void OrganizerProxy::modDataChanged(IModInterface *mod) +{ + m_Proxied->modDataChanged(mod); } -void OrganizerProxy::setPluginSetting(const QString& pluginName, const QString& key, const QVariant& value) { - m_Proxied->setPluginSetting(pluginName, key, value); +QVariant OrganizerProxy::pluginSetting(const QString &pluginName, const QString &key) const +{ + return m_Proxied->pluginSetting(pluginName, key); } -QVariant OrganizerProxy::persistent(const QString& pluginName, const QString& key, const QVariant& def) const { - return m_Proxied->persistent(pluginName, key, def); +void OrganizerProxy::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) +{ + m_Proxied->setPluginSetting(pluginName, key, value); } -void OrganizerProxy::setPersistent(const QString& pluginName, const QString& key, const QVariant& value, bool sync) { - m_Proxied->setPersistent(pluginName, key, value, sync); +QVariant OrganizerProxy::persistent(const QString &pluginName, const QString &key, const QVariant &def) const +{ + return m_Proxied->persistent(pluginName, key, def); } -QString OrganizerProxy::pluginDataPath() const { return m_Proxied->pluginDataPath(); } +void OrganizerProxy::setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) +{ + m_Proxied->setPersistent(pluginName, key, value, sync); +} -HANDLE OrganizerProxy::startApplication(const QString& executable, const QStringList& args, const QString& cwd, - const QString& profile) { - return m_Proxied->startApplication(executable, args, cwd, profile); +QString OrganizerProxy::pluginDataPath() const +{ + return m_Proxied->pluginDataPath(); } -bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const { - return m_Proxied->waitForApplication(handle, exitCode); +HANDLE OrganizerProxy::startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile) +{ + return m_Proxied->startApplication(executable, args, cwd, profile); } -bool OrganizerProxy::onAboutToRun(const std::function& func) { - return m_Proxied->onAboutToRun(func); +bool OrganizerProxy::waitForApplication(HANDLE handle, LPDWORD exitCode) const +{ + return m_Proxied->waitForApplication(handle, exitCode); } -bool OrganizerProxy::onFinishedRun(const std::function& func) { - return m_Proxied->onFinishedRun(func); +bool OrganizerProxy::onAboutToRun(const std::function &func) +{ + return m_Proxied->onAboutToRun(func); } -bool OrganizerProxy::onModInstalled(const std::function& func) { - return m_Proxied->onModInstalled(func); +bool OrganizerProxy::onFinishedRun(const std::function &func) +{ + return m_Proxied->onFinishedRun(func); } -void OrganizerProxy::refreshModList(bool saveChanges) { m_Proxied->refreshModList(saveChanges); } +bool OrganizerProxy::onModInstalled(const std::function &func) +{ + return m_Proxied->onModInstalled(func); +} -IModInterface* OrganizerProxy::installMod(const QString& fileName, const QString& nameSuggestion) { - return m_Proxied->installMod(fileName, nameSuggestion); +void OrganizerProxy::refreshModList(bool saveChanges) +{ + m_Proxied->refreshModList(saveChanges); } -QString OrganizerProxy::resolvePath(const QString& fileName) const { return m_Proxied->resolvePath(fileName); } +IModInterface *OrganizerProxy::installMod(const QString &fileName, const QString &nameSuggestion) +{ + return m_Proxied->installMod(fileName, nameSuggestion); +} -QStringList OrganizerProxy::listDirectories(const QString& directoryName) const { - return m_Proxied->listDirectories(directoryName); +QString OrganizerProxy::resolvePath(const QString &fileName) const +{ + return m_Proxied->resolvePath(fileName); } -QStringList OrganizerProxy::findFiles(const QString& path, const std::function& filter) const { - return m_Proxied->findFiles(path, filter); +QStringList OrganizerProxy::listDirectories(const QString &directoryName) const +{ + return m_Proxied->listDirectories(directoryName); } -QStringList OrganizerProxy::getFileOrigins(const QString& fileName) const { - return m_Proxied->getFileOrigins(fileName); +QStringList OrganizerProxy::findFiles(const QString &path, const std::function &filter) const +{ + return m_Proxied->findFiles(path, filter); } -QList -OrganizerProxy::findFileInfos(const QString& path, - const std::function& filter) const { - return m_Proxied->findFileInfos(path, filter); +QStringList OrganizerProxy::getFileOrigins(const QString &fileName) const +{ + return m_Proxied->getFileOrigins(fileName); } -MOBase::IDownloadManager* OrganizerProxy::downloadManager() const { return m_Proxied->downloadManager(); } +QList OrganizerProxy::findFileInfos(const QString &path, const std::function &filter) const +{ + return m_Proxied->findFileInfos(path, filter); +} + +MOBase::IDownloadManager *OrganizerProxy::downloadManager() const +{ + return m_Proxied->downloadManager(); +} -MOBase::IPluginList* OrganizerProxy::pluginList() const { return m_Proxied->pluginList(); } +MOBase::IPluginList *OrganizerProxy::pluginList() const +{ + return m_Proxied->pluginList(); +} -MOBase::IModList* OrganizerProxy::modList() const { return m_Proxied->modList(); } +MOBase::IModList *OrganizerProxy::modList() const +{ + return m_Proxied->modList(); +} -MOBase::IProfile* OrganizerProxy::profile() const { return m_Proxied->currentProfile(); } +MOBase::IProfile *OrganizerProxy::profile() const +{ + return m_Proxied->currentProfile(); +} -MOBase::IPluginGame const* OrganizerProxy::managedGame() const { return m_Proxied->managedGame(); } +MOBase::IPluginGame const *OrganizerProxy::managedGame() const +{ + return m_Proxied->managedGame(); +} -QStringList OrganizerProxy::modsSortedByProfilePriority() const { return m_Proxied->modsSortedByProfilePriority(); } +QStringList OrganizerProxy::modsSortedByProfilePriority() const +{ + return m_Proxied->modsSortedByProfilePriority(); +} diff --git a/src/organizerproxy.h b/src/organizerproxy.h index cb14a838..7004db14 100644 --- a/src/organizerproxy.h +++ b/src/organizerproxy.h @@ -1,60 +1,63 @@ #ifndef ORGANIZERPROXY_H #define ORGANIZERPROXY_H + #include class OrganizerCore; -class OrganizerProxy : public MOBase::IOrganizer { +class OrganizerProxy : public MOBase::IOrganizer +{ public: - OrganizerProxy(OrganizerCore* organizer, const QString& pluginName); - - 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 MOBase::VersionInfo appVersion() const; - virtual MOBase::IModInterface* getMod(const QString& name) const; - virtual MOBase::IModInterface* createMod(MOBase::GuessedValue& name); - virtual bool removeMod(MOBase::IModInterface* mod); - virtual void modDataChanged(MOBase::IModInterface* mod); - virtual QVariant pluginSetting(const QString& pluginName, const QString& key) const; - virtual void setPluginSetting(const QString& pluginName, const QString& key, const QVariant& value); - virtual QVariant persistent(const QString& pluginName, const QString& key, const QVariant& def = QVariant()) const; - virtual void setPersistent(const QString& pluginName, const QString& key, const QVariant& value, bool sync = true); - virtual QString pluginDataPath() const; - virtual 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; - virtual QStringList getFileOrigins(const QString& fileName) const; - virtual QList findFileInfos(const QString& path, - const std::function& filter) const; - - 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 = ""); - virtual bool waitForApplication(HANDLE handle, LPDWORD exitCode = nullptr) const; - virtual void refreshModList(bool saveChanges); - - virtual bool onAboutToRun(const std::function& func); - virtual bool onFinishedRun(const std::function& func); - virtual bool onModInstalled(const std::function& func); - - virtual MOBase::IPluginGame const* managedGame() const; - - virtual QStringList modsSortedByProfilePriority() const; + + OrganizerProxy(OrganizerCore *organizer, const QString &pluginName); + + 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 MOBase::VersionInfo appVersion() const; + virtual MOBase::IModInterface *getMod(const QString &name) const; + virtual MOBase::IModInterface *createMod(MOBase::GuessedValue &name); + virtual bool removeMod(MOBase::IModInterface *mod); + virtual void modDataChanged(MOBase::IModInterface *mod); + virtual QVariant pluginSetting(const QString &pluginName, const QString &key) const; + virtual void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); + virtual QVariant persistent(const QString &pluginName, const QString &key, const QVariant &def = QVariant()) const; + virtual void setPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync = true); + virtual QString pluginDataPath() const; + virtual 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; + virtual QStringList getFileOrigins(const QString &fileName) const; + virtual QList findFileInfos(const QString &path, const std::function &filter) const; + + 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 = ""); + virtual bool waitForApplication(HANDLE handle, LPDWORD exitCode = nullptr) const; + virtual void refreshModList(bool saveChanges); + + virtual bool onAboutToRun(const std::function &func); + virtual bool onFinishedRun(const std::function &func); + virtual bool onModInstalled(const std::function &func); + + virtual MOBase::IPluginGame const *managedGame() const; + + virtual QStringList modsSortedByProfilePriority() const; private: - OrganizerCore* m_Proxied; - const QString& m_PluginName; + OrganizerCore *m_Proxied; + + const QString &m_PluginName; + }; #endif // ORGANIZERPROXY_H diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index b2b1710b..e956b025 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -18,213 +18,243 @@ along with Mod Organizer. If not, see . */ #include "overwriteinfodialog.h" -#include "report.h" #include "ui_overwriteinfodialog.h" +#include "report.h" #include "utility.h" -#include #include +#include #include + using namespace MOBase; + class MyFileSystemModel : public QFileSystemModel { public: - MyFileSystemModel(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); - } + MyFileSystemModel(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); - } + } + + 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; + mutable int m_RegularColumnCount; }; -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 MyFileSystemModel(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); - 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(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 MyFileSystemModel(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); + 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; } +OverwriteInfoDialog::~OverwriteInfoDialog() +{ + delete ui; +} -void OverwriteInfoDialog::setModInfo(ModInfo::Ptr modInfo) { - m_ModInfo = modInfo; - if (QDir(modInfo->absolutePath()).exists()) { - m_FileSystemModel->setRootPath(modInfo->absolutePath()); - } else { - throw MyException(tr("%1 not found").arg(modInfo->absolutePath())); - } +void OverwriteInfoDialog::setModInfo(ModInfo::Ptr modInfo) +{ + m_ModInfo = modInfo; + if (QDir(modInfo->absolutePath()).exists()) { + m_FileSystemModel->setRootPath(modInfo->absolutePath()); + } else { + throw MyException(tr("%1 not found").arg(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)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); - return false; - } - } else { - if (!m_FileSystemModel->remove(childIndex)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); - return false; - } - } - } - if (!m_FileSystemModel->remove(index)) { - qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); +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)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); return false; + } + } else { + if (!m_FileSystemModel->remove(childIndex)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(childIndex).toUtf8().constData()); + return false; + } } - return true; + } + if (!m_FileSystemModel->remove(index)) { + qCritical("failed to delete %s", m_FileSystemModel->fileName(index).toUtf8().constData()); + 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::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::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 sure you want to delete \"%1\"?").arg(fileName), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } - } else { - if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), - QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { - return; - } + +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 sure you want to delete \"%1\"?").arg(fileName), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; } + } else { + if (QMessageBox::question(this, tr("Confirm"), tr("Are sure you want to delete the selected files?"), + QMessageBox::Yes | QMessageBox::No) != QMessageBox::Yes) { + return; + } + } - foreach (QModelIndex index, m_FileSelection) { deleteFile(index); } + 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::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) { - QString fileName = m_FileSystemModel->filePath(index); - HINSTANCE res = ::ShellExecuteW(nullptr, L"open", ToWString(fileName).c_str(), nullptr, nullptr, SW_SHOW); - if ((int)res <= 32) { - qCritical("failed to invoke %s: %d", fileName.toUtf8().constData(), res); - } +void OverwriteInfoDialog::openFile(const QModelIndex &index) +{ + QString fileName = m_FileSystemModel->filePath(index); + + HINSTANCE res = ::ShellExecuteW(nullptr, L"open", ToWString(fileName).c_str(), nullptr, nullptr, SW_SHOW); + if ((int)res <= 32) { + qCritical("failed to invoke %s: %d", fileName.toUtf8().constData(), res); + } } -void OverwriteInfoDialog::openTriggered() { - foreach (QModelIndex idx, m_FileSelection) { openFile(idx); } + +void OverwriteInfoDialog::openTriggered() +{ + foreach(QModelIndex idx, m_FileSelection) { + openFile(idx); + } } -void OverwriteInfoDialog::createDirectoryTriggered() { - QModelIndex selection = m_FileSelection.at(0); +void OverwriteInfoDialog::createDirectoryTriggered() +{ + QModelIndex selection = m_FileSelection.at(0); - QModelIndex index = m_FileSystemModel->isDir(selection) ? selection : selection.parent(); - index = index.sibling(index.row(), 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("/"); + 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 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; - } + 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); + ui->filesView->setCurrentIndex(newIndex); + ui->filesView->edit(newIndex); } -void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint& pos) { - QItemSelectionModel* selectionModel = ui->filesView->selectionModel(); - m_FileSelection = selectionModel->selectedRows(0); - QMenu menu(ui->filesView); +void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint &pos) +{ + QItemSelectionModel *selectionModel = ui->filesView->selectionModel(); + m_FileSelection = selectionModel->selectedRows(0); - menu.addAction(m_NewFolderAction); + QMenu menu(ui->filesView); - bool hasFiles = false; + menu.addAction(m_NewFolderAction); - foreach (QModelIndex idx, m_FileSelection) { - if (m_FileSystemModel->fileInfo(idx).isFile()) { - hasFiles = true; - break; - } + 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)); + if (selectionModel->hasSelection()) { + if (hasFiles) { + menu.addAction(m_OpenAction); } - menu.exec(ui->filesView->mapToGlobal(pos)); + 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->mapToGlobal(pos)); } diff --git a/src/overwriteinfodialog.h b/src/overwriteinfodialog.h index 110601dc..eb182292 100644 --- a/src/overwriteinfodialog.h +++ b/src/overwriteinfodialog.h @@ -28,41 +28,46 @@ namespace Ui { class OverwriteInfoDialog; } -class OverwriteInfoDialog : public QDialog { - Q_OBJECT - +class OverwriteInfoDialog : public QDialog +{ + Q_OBJECT + public: - explicit OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget* parent = 0); - ~OverwriteInfoDialog(); - ModInfo::Ptr modInfo() const { return m_ModInfo; } + explicit OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent = 0); + ~OverwriteInfoDialog(); + + ModInfo::Ptr modInfo() const { return m_ModInfo; } - void setModInfo(ModInfo::Ptr modInfo); + void setModInfo(ModInfo::Ptr modInfo); private: - void openFile(const QModelIndex& index); - bool recursiveDelete(const QModelIndex& index); - void deleteFile(const QModelIndex& index); + + void openFile(const QModelIndex &index); + bool recursiveDelete(const QModelIndex &index); + void deleteFile(const QModelIndex &index); private slots: - void deleteTriggered(); - void renameTriggered(); - void openTriggered(); - void createDirectoryTriggered(); + void deleteTriggered(); + void renameTriggered(); + void openTriggered(); + void createDirectoryTriggered(); - void on_filesView_customContextMenuRequested(const QPoint& pos); + 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; + + 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/persistentcookiejar.cpp b/src/persistentcookiejar.cpp index 0e17d5b7..a6eb78fa 100644 --- a/src/persistentcookiejar.cpp +++ b/src/persistentcookiejar.cpp @@ -1,70 +1,72 @@ #include "persistentcookiejar.h" +#include #include #include -#include -PersistentCookieJar::PersistentCookieJar(const QString& fileName, QObject* parent) - : QNetworkCookieJar(parent), m_FileName(fileName) { - restore(); + +PersistentCookieJar::PersistentCookieJar(const QString &fileName, QObject *parent) +: QNetworkCookieJar(parent), m_FileName(fileName) +{ + restore(); } PersistentCookieJar::~PersistentCookieJar() { - qDebug("save %s", qPrintable(m_FileName)); - save(); + qDebug("save %s", qPrintable(m_FileName)); + save(); } void PersistentCookieJar::clear() { - for (const QNetworkCookie& cookie : allCookies()) { - deleteCookie(cookie); - } + for (const QNetworkCookie &cookie : allCookies()) { + deleteCookie(cookie); + } } void PersistentCookieJar::save() { - QTemporaryFile file; - if (!file.open()) { - qCritical("failed to save cookies: couldn't create temporary file"); - return; - } - QDataStream data(&file); + QTemporaryFile file; + if (!file.open()) { + qCritical("failed to save cookies: couldn't create temporary file"); + return; + } + QDataStream data(&file); - QList cookies = allCookies(); - data << static_cast(cookies.size()); + QList cookies = allCookies(); + data << static_cast(cookies.size()); - for (const QNetworkCookie& cookie : allCookies()) { - data << cookie.toRawForm(); - } + for (const QNetworkCookie &cookie : allCookies()) { + data << cookie.toRawForm(); + } - { - QFile oldCookies(m_FileName); - if (oldCookies.exists()) { - if (!oldCookies.remove()) { - qCritical("failed to save cookies: failed to remove %s", qPrintable(m_FileName)); - return; - } - } // if it doesn't exists that's fine - } + { + QFile oldCookies(m_FileName); + if (oldCookies.exists()) { + if (!oldCookies.remove()) { + qCritical("failed to save cookies: failed to remove %s", qPrintable(m_FileName)); + return; + } + } // if it doesn't exists that's fine + } - if (!file.copy(m_FileName)) { - qCritical("failed to save cookies: failed to write %s", qPrintable(m_FileName)); - } + if (!file.copy(m_FileName)) { + qCritical("failed to save cookies: failed to write %s", qPrintable(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; - } + QFile file(m_FileName); + if (!file.open(QIODevice::ReadOnly)) { + // not necessarily a problem, the file may just not exist (yet) + return; + } - QList allCookies; + 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); + 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 82426ef9..0ff747ee 100644 --- a/src/persistentcookiejar.h +++ b/src/persistentcookiejar.h @@ -3,23 +3,28 @@ #include + class PersistentCookieJar : public QNetworkCookieJar { - Q_OBJECT + Q_OBJECT public: - PersistentCookieJar(const QString& fileName, QObject* parent = 0); - virtual ~PersistentCookieJar(); + PersistentCookieJar(const QString &fileName, QObject *parent = 0); + virtual ~PersistentCookieJar(); - void clear(); + void clear(); private: - void save(); - void restore(); + void save(); + + void restore(); private: - QString m_FileName; + + QString m_FileName; + }; + #endif // PERSISTENTCOOKIEJAR_H diff --git a/src/plugincontainer.cpp b/src/plugincontainer.cpp index 39c49deb..c980c0a1 100644 --- a/src/plugincontainer.cpp +++ b/src/plugincontainer.cpp @@ -1,312 +1,348 @@ #include "plugincontainer.h" #include "organizerproxy.h" #include "report.h" +#include +#include +#include #include +#include #include -#include #include -#include -#include -#include +#include +#include #include +#include #include -#include -#include -#include using namespace MOBase; using namespace MOShared; namespace bf = boost::fusion; -PluginContainer::PluginContainer(OrganizerCore* organizer) : m_Organizer(organizer), m_UserInterface(nullptr) {} -void PluginContainer::setUserInterface(IUserInterface* userInterface, QWidget* widget) { - for (IPluginProxy* proxy : bf::at_key(m_Plugins)) { - proxy->setParentWidget(widget); - } +PluginContainer::PluginContainer(OrganizerCore *organizer) + : m_Organizer(organizer) + , m_UserInterface(nullptr) +{ +} - if (userInterface != nullptr) { - for (IPluginModPage* modPage : bf::at_key(m_Plugins)) { - userInterface->registerModPage(modPage); - } - for (IPluginTool* tool : bf::at_key(m_Plugins)) { - userInterface->registerPluginTool(tool); - } +void PluginContainer::setUserInterface(IUserInterface *userInterface, QWidget *widget) +{ + for (IPluginProxy *proxy : bf::at_key(m_Plugins)) { + proxy->setParentWidget(widget); + } + + if (userInterface != nullptr) { + for (IPluginModPage *modPage : bf::at_key(m_Plugins)) { + userInterface->registerModPage(modPage); + } + + for (IPluginTool *tool : bf::at_key(m_Plugins)) { + userInterface->registerPluginTool(tool); } + } - m_UserInterface = userInterface; + m_UserInterface = userInterface; } -QStringList PluginContainer::pluginFileNames() const { - QStringList result; - for (QPluginLoader* loader : m_PluginLoaders) { - result.append(loader->fileName()); - } - return result; + +QStringList PluginContainer::pluginFileNames() const +{ + QStringList result; + for (QPluginLoader *loader : m_PluginLoaders) { + result.append(loader->fileName()); + } + return result; } -bool PluginContainer::verifyPlugin(IPlugin* plugin) { - if (plugin == nullptr) { - return false; - } else if (!plugin->init(new OrganizerProxy(m_Organizer, plugin->name()))) { - qWarning("plugin failed to initialize"); - return false; - } - return true; + +bool PluginContainer::verifyPlugin(IPlugin *plugin) +{ + if (plugin == nullptr) { + return false; + } else if (!plugin->init(new OrganizerProxy(m_Organizer, plugin->name()))) { + qWarning("plugin failed to initialize"); + return false; + } + return true; } -void PluginContainer::registerGame(IPluginGame* game) { m_SupportedGames.insert({game->gameName(), game}); } -bool PluginContainer::registerPlugin(QObject* plugin, const QString& fileName) { - { // generic treatment for all plugins - IPlugin* pluginObj = qobject_cast(plugin); - if (pluginObj == nullptr) { - qDebug("not an IPlugin"); - return false; - } - plugin->setProperty("filename", fileName); - m_Organizer->settings().registerPlugin(pluginObj); - } +void PluginContainer::registerGame(IPluginGame *game) +{ + m_SupportedGames.insert({ game->gameName(), game }); +} - { // diagnosis plugin - IPluginDiagnose* diagnose = qobject_cast(plugin); - if (diagnose != nullptr) { - bf::at_key(m_Plugins).push_back(diagnose); - m_DiagnosisConnections.push_back(diagnose->onInvalidated([&]() { emit diagnosisUpdate(); })); - } + +bool PluginContainer::registerPlugin(QObject *plugin, const QString &fileName) +{ + { // generic treatment for all plugins + IPlugin *pluginObj = qobject_cast(plugin); + if (pluginObj == nullptr) { + qDebug("not an IPlugin"); + return false; } - { // file mapper plugin - IPluginFileMapper* mapper = qobject_cast(plugin); - if (mapper != nullptr) { - bf::at_key(m_Plugins).push_back(mapper); - } + plugin->setProperty("filename", fileName); + m_Organizer->settings().registerPlugin(pluginObj); + } + + { // diagnosis plugin + IPluginDiagnose *diagnose = qobject_cast(plugin); + if (diagnose != nullptr) { + bf::at_key(m_Plugins).push_back(diagnose); + m_DiagnosisConnections.push_back( + diagnose->onInvalidated([&] () { emit diagnosisUpdate(); }) + ); } - { // mod page plugin - IPluginModPage* modPage = qobject_cast(plugin); - if (verifyPlugin(modPage)) { - bf::at_key(m_Plugins).push_back(modPage); - return true; - } + } + { // file mapper plugin + IPluginFileMapper *mapper = qobject_cast(plugin); + if (mapper != nullptr) { + bf::at_key(m_Plugins).push_back(mapper); } - { // game plugin - IPluginGame* game = qobject_cast(plugin); - if (verifyPlugin(game)) { - bf::at_key(m_Plugins).push_back(game); - registerGame(game); - return true; - } + } + { // mod page plugin + IPluginModPage *modPage = qobject_cast(plugin); + if (verifyPlugin(modPage)) { + bf::at_key(m_Plugins).push_back(modPage); + return true; } - { // tool plugins - IPluginTool* tool = qobject_cast(plugin); - if (verifyPlugin(tool)) { - bf::at_key(m_Plugins).push_back(tool); - return true; - } + } + { // game plugin + IPluginGame *game = qobject_cast(plugin); + if (verifyPlugin(game)) { + bf::at_key(m_Plugins).push_back(game); + registerGame(game); + return true; } - { // installer plugins - IPluginInstaller* installer = qobject_cast(plugin); - if (verifyPlugin(installer)) { - bf::at_key(m_Plugins).push_back(installer); - m_Organizer->installationManager()->registerInstaller(installer); - return true; - } + } + { // tool plugins + IPluginTool *tool = qobject_cast(plugin); + if (verifyPlugin(tool)) { + bf::at_key(m_Plugins).push_back(tool); + return true; } - { // preview plugins - IPluginPreview* preview = qobject_cast(plugin); - if (verifyPlugin(preview)) { - bf::at_key(m_Plugins).push_back(preview); - m_PreviewGenerator.registerPlugin(preview); - return true; - } + } + { // installer plugins + IPluginInstaller *installer = qobject_cast(plugin); + if (verifyPlugin(installer)) { + bf::at_key(m_Plugins).push_back(installer); + m_Organizer->installationManager()->registerInstaller(installer); + return true; + } + } + { // preview plugins + IPluginPreview *preview = qobject_cast(plugin); + if (verifyPlugin(preview)) { + bf::at_key(m_Plugins).push_back(preview); + m_PreviewGenerator.registerPlugin(preview); + return true; } - { // proxy plugins - IPluginProxy* proxy = qobject_cast(plugin); - if (verifyPlugin(proxy)) { - bf::at_key(m_Plugins).push_back(proxy); - QStringList pluginNames = - proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())); - for (const QString& pluginName : pluginNames) { - try { - QObject* proxiedPlugin = proxy->instantiate(pluginName); - if (proxiedPlugin != nullptr) { - if (registerPlugin(proxiedPlugin, pluginName)) { - qDebug("loaded plugin \"%s\"", qPrintable(QFileInfo(pluginName).fileName())); - } else { - qWarning("plugin \"%s\" 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.", - qPrintable(pluginName)); - } - } - } catch (const std::exception& e) { - reportError(QObject::tr("failed to initialize plugin %1: %2").arg(pluginName).arg(e.what())); - } + } + { // proxy plugins + IPluginProxy *proxy = qobject_cast(plugin); + if (verifyPlugin(proxy)) { + bf::at_key(m_Plugins).push_back(proxy); + QStringList pluginNames = proxy->pluginList( + QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())); + for (const QString &pluginName : pluginNames) { + try { + QObject *proxiedPlugin = proxy->instantiate(pluginName); + if (proxiedPlugin != nullptr) { + if (registerPlugin(proxiedPlugin, pluginName)) { + qDebug("loaded plugin \"%s\"", qPrintable(QFileInfo(pluginName).fileName())); + } else { + qWarning("plugin \"%s\" 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.", + qPrintable(pluginName)); } - return true; + } + } catch (const std::exception &e) { + reportError(QObject::tr("failed to initialize plugin %1: %2").arg(pluginName).arg(e.what())); } + } + return true; } - - { // dummy plugins - // only initialize these, no processing otherwise - IPlugin* dummy = qobject_cast(plugin); - if (verifyPlugin(dummy)) { - bf::at_key(m_Plugins).push_back(dummy); - return true; - } + } + + { // dummy plugins + // only initialize these, no processing otherwise + IPlugin *dummy = qobject_cast(plugin); + if (verifyPlugin(dummy)) { + bf::at_key(m_Plugins).push_back(dummy); + return true; } + } - qDebug("no matching plugin interface"); + qDebug("no matching plugin interface"); - return false; + return false; } -struct clearPlugins { - template - void operator()(T& t) const { - t.second.clear(); +struct clearPlugins +{ + template + void operator()(T& t) const + { + t.second.clear(); } }; -void PluginContainer::unloadPlugins() { - if (m_UserInterface != nullptr) { - m_UserInterface->disconnectPlugins(); - } +void PluginContainer::unloadPlugins() +{ + if (m_UserInterface != nullptr) { + m_UserInterface->disconnectPlugins(); + } - // disconnect all slots before unloading plugins so plugins don't have to take care of that - m_Organizer->disconnectPlugins(); + // disconnect all slots before unloading plugins so plugins don't have to take care of that + m_Organizer->disconnectPlugins(); - bf::for_each(m_Plugins, clearPlugins()); + bf::for_each(m_Plugins, clearPlugins()); - for (const boost::signals2::connection& connection : m_DiagnosisConnections) { - connection.disconnect(); - } - m_DiagnosisConnections.clear(); + for (const boost::signals2::connection &connection : m_DiagnosisConnections) { + connection.disconnect(); + } + m_DiagnosisConnections.clear(); - while (!m_PluginLoaders.empty()) { - QPluginLoader* loader = m_PluginLoaders.back(); - m_PluginLoaders.pop_back(); - if ((loader != nullptr) && !loader->unload()) { - qDebug("failed to unload %s: %s", qPrintable(loader->fileName()), qPrintable(loader->errorString())); - } - delete loader; + while (!m_PluginLoaders.empty()) { + QPluginLoader *loader = m_PluginLoaders.back(); + m_PluginLoaders.pop_back(); + if ((loader != nullptr) && !loader->unload()) { + qDebug("failed to unload %s: %s", qPrintable(loader->fileName()), qPrintable(loader->errorString())); } + delete loader; + } } -IPluginGame* PluginContainer::managedGame(const QString& name) const { - auto iter = m_SupportedGames.find(name); - if (iter != m_SupportedGames.end()) { - return iter->second; - } else { - return nullptr; - } +IPluginGame *PluginContainer::managedGame(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; } +const PreviewGenerator &PluginContainer::previewGenerator() const +{ + return m_PreviewGenerator; +} -void PluginContainer::loadPlugins() { - unloadPlugins(); +void PluginContainer::loadPlugins() +{ + unloadPlugins(); - for (QObject* plugin : QPluginLoader::staticInstances()) { - registerPlugin(plugin, ""); - } + for (QObject *plugin : QPluginLoader::staticInstances()) { + registerPlugin(plugin, ""); + } - QFile loadCheck(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(); - } - if (QMessageBox::question(nullptr, QObject::tr("Plugin error"), - QObject::tr("It appears the plugin \"%1\" failed to load last startup and caused MO " - "to crash. Do you want to disable it?\n" - "(Please note: If this is the first time you see this message for this " - "plugin you may want to give it another try. " - "The plugin may be able to recover from the problem)") - .arg(fileName), - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { - m_Organizer->settings().addBlacklistPlugin(fileName); - } - loadCheck.close(); + QFile loadCheck(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(); } + if (QMessageBox::question(nullptr, QObject::tr("Plugin error"), + QObject::tr("It appears the plugin \"%1\" failed to load last startup and caused MO to crash. Do you want to disable it?\n" + "(Please note: If this is the first time you see this message for this plugin you may want to give it another try. " + "The plugin may be able to recover from the problem)").arg(fileName), + QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { + m_Organizer->settings().addBlacklistPlugin(fileName); + } + loadCheck.close(); + } - loadCheck.open(QIODevice::WriteOnly); + loadCheck.open(QIODevice::WriteOnly); - QString pluginPath = qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); - qDebug("looking for plugins in %s", QDir::toNativeSeparators(pluginPath).toUtf8().constData()); - QDirIterator iter(pluginPath, QDir::Files | QDir::NoDotAndDotDot); + QString pluginPath = qApp->applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()); + qDebug("looking for plugins in %s", QDir::toNativeSeparators(pluginPath).toUtf8().constData()); + QDirIterator iter(pluginPath, QDir::Files | QDir::NoDotAndDotDot); - while (iter.hasNext()) { - iter.next(); - if (m_Organizer->settings().pluginBlacklisted(iter.fileName())) { - qDebug("plugin \"%s\" blacklisted", qPrintable(iter.fileName())); - continue; - } - loadCheck.write(iter.fileName().toUtf8()); - loadCheck.write("\n"); - loadCheck.flush(); - QString pluginName = iter.filePath(); - if (QLibrary::isLibrary(pluginName)) { - std::unique_ptr pluginLoader(new QPluginLoader(pluginName, this)); - if (pluginLoader->instance() == nullptr) { - m_FailedPlugins.push_back(pluginName); - qCritical("failed to load plugin %s: %s", qPrintable(pluginName), - qPrintable(pluginLoader->errorString())); - } else { - if (registerPlugin(pluginLoader->instance(), pluginName)) { - qDebug("loaded plugin \"%s\"", qPrintable(QFileInfo(pluginName).fileName())); - m_PluginLoaders.push_back(pluginLoader.release()); - } else { - m_FailedPlugins.push_back(pluginName); - qWarning("plugin \"%s\" failed to load (may be outdated)", qPrintable(pluginName)); - } - } + while (iter.hasNext()) { + iter.next(); + if (m_Organizer->settings().pluginBlacklisted(iter.fileName())) { + qDebug("plugin \"%s\" blacklisted", qPrintable(iter.fileName())); + continue; + } + loadCheck.write(iter.fileName().toUtf8()); + loadCheck.write("\n"); + loadCheck.flush(); + QString pluginName = iter.filePath(); + if (QLibrary::isLibrary(pluginName)) { + std::unique_ptr pluginLoader(new QPluginLoader(pluginName, this)); + if (pluginLoader->instance() == nullptr) { + m_FailedPlugins.push_back(pluginName); + qCritical("failed to load plugin %s: %s", + qPrintable(pluginName), qPrintable(pluginLoader->errorString())); + } else { + if (registerPlugin(pluginLoader->instance(), pluginName)) { + qDebug("loaded plugin \"%s\"", qPrintable(QFileInfo(pluginName).fileName())); + m_PluginLoaders.push_back(pluginLoader.release()); + } else { + m_FailedPlugins.push_back(pluginName); + qWarning("plugin \"%s\" failed to load (may be outdated)", qPrintable(pluginName)); } + } } + } - // remove the load check file on success - loadCheck.remove(); + // remove the load check file on success + loadCheck.remove(); - bf::at_key(m_Plugins).push_back(m_Organizer); - bf::at_key(m_Plugins).push_back(this); + bf::at_key(m_Plugins).push_back(m_Organizer); + bf::at_key(m_Plugins).push_back(this); - m_Organizer->connectPlugins(this); + m_Organizer->connectPlugins(this); } -std::vector PluginContainer::activeProblems() const { - std::vector problems; - if (m_FailedPlugins.size()) { - problems.push_back(PROBLEM_PLUGINSNOTLOADED); - } - return problems; + +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) { +QString PluginContainer::shortDescription(unsigned int key) const +{ + switch (key) { case PROBLEM_PLUGINSNOTLOADED: { - return tr("Some plugins could not be loaded"); + return tr("Some plugins could not be loaded"); } break; - default: { return tr("Description missing"); } break; - } + default: { + return tr("Description missing"); + } break; + } } -QString PluginContainer::fullDescription(unsigned int key) const { - switch (key) { +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; + 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; - } + default: { + return tr("Description missing"); + } break; + } } -bool PluginContainer::hasGuidedFix(unsigned int) const { return false; } +bool PluginContainer::hasGuidedFix(unsigned int) const +{ + return false; +} -void PluginContainer::startGuidedFix(unsigned int) const {} +void PluginContainer::startGuidedFix(unsigned int) const +{ +} diff --git a/src/plugincontainer.h b/src/plugincontainer.h index e431142e..70392d0b 100644 --- a/src/plugincontainer.h +++ b/src/plugincontainer.h @@ -1,94 +1,103 @@ #ifndef PLUGINCONTAINER_H #define PLUGINCONTAINER_H + #include "organizercore.h" #include "previewgenerator.h" -#include -#include -#include #include -#include -#include -#include #include -#include +#include #include +#include +#include +#include +#include +#include +#include #ifndef Q_MOC_RUN #include #include #endif // Q_MOC_RUN #include -class PluginContainer : public QObject, public MOBase::IPluginDiagnose { - Q_OBJECT - Q_INTERFACES(MOBase::IPluginDiagnose) +class PluginContainer : public QObject, public MOBase::IPluginDiagnose +{ + + Q_OBJECT + Q_INTERFACES(MOBase::IPluginDiagnose) private: - typedef 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>> - PluginMap; - - static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1; + + typedef 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> + > PluginMap; + + static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1; public: - PluginContainer(OrganizerCore* organizer); - void setUserInterface(IUserInterface* userInterface, QWidget* widget); + PluginContainer(OrganizerCore *organizer); + + void setUserInterface(IUserInterface *userInterface, QWidget *widget); - void loadPlugins(); - void unloadPlugins(); + void loadPlugins(); + void unloadPlugins(); - MOBase::IPluginGame* managedGame(const QString& name) const; + MOBase::IPluginGame *managedGame(const QString &name) const; - template - const std::vector& plugins() const { - typename boost::fusion::result_of::at_key::type temp = boost::fusion::at_key(m_Plugins); - return temp; - } + template + const std::vector &plugins() const { + typename boost::fusion::result_of::at_key::type temp = boost::fusion::at_key(m_Plugins); + return temp; + } - const PreviewGenerator& previewGenerator() const; + const PreviewGenerator &previewGenerator() const; - QStringList pluginFileNames() const; + 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; + + 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: - void diagnosisUpdate(); + void diagnosisUpdate(); private: - bool verifyPlugin(MOBase::IPlugin* plugin); - void registerGame(MOBase::IPluginGame* game); - bool registerPlugin(QObject* pluginObj, const QString& fileName); - bool unregisterPlugin(QObject* pluginObj, const QString& fileName); - OrganizerCore* m_Organizer; + bool verifyPlugin(MOBase::IPlugin *plugin); + void registerGame(MOBase::IPluginGame *game); + bool registerPlugin(QObject *pluginObj, const QString &fileName); + bool unregisterPlugin(QObject *pluginObj, const QString &fileName); - IUserInterface* m_UserInterface; + OrganizerCore *m_Organizer; - PluginMap m_Plugins; + IUserInterface *m_UserInterface; - std::map m_SupportedGames; - std::vector m_DiagnosisConnections; - QStringList m_FailedPlugins; - std::vector m_PluginLoaders; + PluginMap m_Plugins; - PreviewGenerator m_PreviewGenerator; + std::map m_SupportedGames; + std::vector m_DiagnosisConnections; + QStringList m_FailedPlugins; + std::vector m_PluginLoaders; - QFile m_PluginsCheck; + PreviewGenerator m_PreviewGenerator; + + QFile m_PluginsCheck; }; + #endif // PLUGINCONTAINER_H diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index f1ebac39..775086fd 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -18,1129 +18,1205 @@ along with Mod Organizer. If not, see . */ #include "pluginlist.h" -#include "modinfo.h" -#include "scopeguard.h" #include "settings.h" +#include "scopeguard.h" +#include "modinfo.h" #include "viewmarkingscrollbar.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 -#include #include +#include #include + using namespace MOBase; using namespace MOShared; + static bool ByName(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { - return LHS.m_Name.toUpper() < RHS.m_Name.toUpper(); + return LHS.m_Name.toUpper() < RHS.m_Name.toUpper(); } static bool ByPriority(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { - if (LHS.m_IsMaster && !RHS.m_IsMaster) { - return true; - } else if (!LHS.m_IsMaster && RHS.m_IsMaster) { - return false; - } else { - return LHS.m_Priority < RHS.m_Priority; - } + if (LHS.m_IsMaster && !RHS.m_IsMaster) { + return true; + } else if (!LHS.m_IsMaster && RHS.m_IsMaster) { + return false; + } else { + return LHS.m_Priority < RHS.m_Priority; + } } static bool ByDate(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { - return QFileInfo(LHS.m_FullPath).lastModified() < QFileInfo(RHS.m_FullPath).lastModified(); + return QFileInfo(LHS.m_FullPath).lastModified() < QFileInfo(RHS.m_FullPath).lastModified(); } -PluginList::PluginList(QObject* parent) : QAbstractItemModel(parent), m_FontMetrics(QFont()) {} +PluginList::PluginList(QObject *parent) + : QAbstractItemModel(parent) + , m_FontMetrics(QFont()) +{ +} -PluginList::~PluginList() { - m_Refreshed.disconnect_all_slots(); - m_PluginMoved.disconnect_all_slots(); - m_PluginStateChanged.disconnect_all_slots(); +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::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 your mods"); - case COL_PRIORITY: - return tr("Load priority of your mod. The higher, the more \"important\" it is and thus " - "overwrites data from plugins with lower priority."); - case COL_MODINDEX: - return tr("The modindex determines the formids of objects originating from this mods."); - default: - return tr("unknown"); - } + +QString PluginList::getColumnToolTip(int column) +{ + switch (column) { + case COL_NAME: return tr("Name of your mods"); + case COL_PRIORITY: return tr("Load priority of your mod. The higher, the more \"important\" it is and thus " + "overwrites data from plugins with lower priority."); + case COL_MODINDEX: return tr("The modindex determines the formids of objects originating from this mods."); + default: return tr("unknown"); + } } -void PluginList::highlightPlugins(const QItemSelection& selected, const MOShared::DirectoryEntry& directoryEntry, - const Profile& profile) { - for (auto& esp : m_ESPs) { - esp.m_ModSelected = false; - } - for (QModelIndex idx : selected.indexes()) { - ModInfo::Ptr selectedMod = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); - if (!selectedMod.isNull() && profile.modEnabled(idx.data(Qt::UserRole + 1).toInt())) { - QDir dir(selectedMod->absolutePath()); - QStringList plugins = dir.entryList(QStringList() << "*.esp" - << "*.esm" - << "*.esl"); - MOShared::FilesOrigin origin = directoryEntry.getOriginByName(selectedMod->internalName().toStdWString()); - if (plugins.size() > 0) { - for (auto plugin : plugins) { - MOShared::FileEntry::Ptr file = directoryEntry.findFile(plugin.toStdWString()); - if (file->getOrigin() != origin.getID()) { - const std::vector> alternatives = file->getAlternatives(); - if (std::find_if(alternatives.begin(), alternatives.end(), - [&](const std::pair& element) { - return element.first == origin.getID(); - }) == alternatives.end()) - continue; - } - std::map::iterator iter = m_ESPsByName.find(plugin.toLower()); - if (iter != m_ESPsByName.end()) { - m_ESPs[iter->second].m_ModSelected = true; - } - } - } +void PluginList::highlightPlugins(const QItemSelection &selected, const MOShared::DirectoryEntry &directoryEntry, const Profile &profile) +{ + for (auto &esp : m_ESPs) { + esp.m_ModSelected = false; + } + for (QModelIndex idx : selected.indexes()) { + ModInfo::Ptr selectedMod = ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt()); + if (!selectedMod.isNull() && profile.modEnabled(idx.data(Qt::UserRole + 1).toInt())) { + QDir dir(selectedMod->absolutePath()); + QStringList plugins = dir.entryList(QStringList() << "*.esp" << "*.esm" << "*.esl"); + MOShared::FilesOrigin origin = directoryEntry.getOriginByName(selectedMod->internalName().toStdWString()); + if (plugins.size() > 0) { + for (auto plugin : plugins) { + MOShared::FileEntry::Ptr file = directoryEntry.findFile(plugin.toStdWString()); + if (file->getOrigin() != origin.getID()) { + const std::vector> alternatives = file->getAlternatives(); + if (std::find_if(alternatives.begin(), alternatives.end(), [&](const std::pair& element) { return element.first == origin.getID(); }) == alternatives.end()) + continue; + } + std::map::iterator iter = m_ESPsByName.find(plugin.toLower()); + if (iter != m_ESPsByName.end()) { + m_ESPs[iter->second].m_ModSelected = true; + } } + } } - emit dataChanged(this->index(0, 0), this->index(m_ESPs.size() - 1, this->columnCount() - 1)); + } + emit dataChanged(this->index(0, 0), this->index(m_ESPs.size() - 1, this->columnCount() - 1)); } -void PluginList::refresh(const QString& profileName, const DirectoryEntry& baseDirectory, - const QString& lockedOrderFile) { - ChangeBracket layoutChange(this); +void PluginList::refresh(const QString &profileName + , const DirectoryEntry &baseDirectory + , const QString &lockedOrderFile) +{ + ChangeBracket layoutChange(this); - QStringList primaryPlugins = m_GamePlugin->primaryPlugins(); + QStringList primaryPlugins = m_GamePlugin->primaryPlugins(); - m_CurrentProfile = profileName; + m_CurrentProfile = profileName; - QStringList availablePlugins; + QStringList availablePlugins; - std::vector files = baseDirectory.getFiles(); - for (FileEntry::Ptr current : files) { - if (current.get() == nullptr) { - continue; - } - QString filename = ToQString(current->getName()); + std::vector files = baseDirectory.getFiles(); + for (FileEntry::Ptr current : files) { + if (current.get() == nullptr) { + continue; + } + QString filename = ToQString(current->getName()); - availablePlugins.append(filename.toLower()); + availablePlugins.append(filename.toLower()); - if (m_ESPsByName.find(filename.toLower()) != m_ESPsByName.end()) { - continue; - } + if (m_ESPsByName.find(filename.toLower()) != m_ESPsByName.end()) { + continue; + } + + QString extension = filename.right(3).toLower(); + + if ((extension == "esp") || (extension == "esm") || (extension == "esl")) { + bool forceEnabled = Settings::instance().forceEnableCoreFiles() && + std::find(primaryPlugins.begin(), primaryPlugins.end(), filename.toLower()) != primaryPlugins.end(); + + bool archive = false; + try { + FilesOrigin &origin = baseDirectory.getOriginByID(current->getOrigin(archive)); - QString extension = filename.right(3).toLower(); - - if ((extension == "esp") || (extension == "esm") || (extension == "esl")) { - bool forceEnabled = - Settings::instance().forceEnableCoreFiles() && - std::find(primaryPlugins.begin(), primaryPlugins.end(), filename.toLower()) != primaryPlugins.end(); - - bool archive = false; - try { - FilesOrigin& origin = baseDirectory.getOriginByID(current->getOrigin(archive)); - - QString iniPath = QFileInfo(filename).baseName() + ".ini"; - bool hasIni = baseDirectory.findFile(ToWString(iniPath)).get() != nullptr; - - 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)); - m_ESPs.rbegin()->m_Priority = -1; - } catch (const std::exception& e) { - reportError(tr("failed to update esp info for file %1 (source id: %2), error: %3") - .arg(filename) - .arg(current->getOrigin(archive)) - .arg(e.what())); - } + QString iniPath = QFileInfo(filename).baseName() + ".ini"; + bool hasIni = baseDirectory.findFile(ToWString(iniPath)).get() != nullptr; + + 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)); + m_ESPs.rbegin()->m_Priority = -1; + } catch (const std::exception &e) { + reportError(tr("failed to update esp info for file %1 (source id: %2), error: %3").arg(filename).arg(current->getOrigin(archive)).arg(e.what())); + } } + } - for (const auto& espName : m_ESPsByName) { - if (!availablePlugins.contains(espName.first)) { - m_ESPs[espName.second].m_Name = ""; - } + for (const auto &espName : m_ESPsByName) { + if (!availablePlugins.contains(espName.first)) { + m_ESPs[espName.second].m_Name = ""; } + } - m_ESPs.erase( - std::remove_if(m_ESPs.begin(), m_ESPs.end(), [](const ESPInfo& info) -> bool { return info.m_Name.isEmpty(); }), - m_ESPs.end()); + m_ESPs.erase(std::remove_if(m_ESPs.begin(), m_ESPs.end(), + [](const ESPInfo &info) -> bool { + return info.m_Name.isEmpty(); + }), + m_ESPs.end()); - fixPriorities(); + fixPriorities(); - // functions in GamePlugins will use the IPluginList interface of this, so - // indices need to work. priority will be off however - updateIndices(); + // functions in GamePlugins will use the IPluginList interface of this, so + // indices need to work. priority will be off however + updateIndices(); - GamePlugins* gamePlugins = m_GamePlugin->feature(); - gamePlugins->readPluginLists(this); + GamePlugins *gamePlugins = m_GamePlugin->feature(); + gamePlugins->readPluginLists(this); - testMasters(); + testMasters(); - updateIndices(); + updateIndices(); - readLockedOrderFrom(lockedOrderFile); + readLockedOrderFrom(lockedOrderFile); - layoutChange.finish(); + layoutChange.finish(); - refreshLoadOrder(); - emit dataChanged(this->index(0, 0), this->index(static_cast(m_ESPs.size()), columnCount())); + refreshLoadOrder(); + emit dataChanged(this->index(0, 0), + this->index(static_cast(m_ESPs.size()), columnCount())); - m_Refreshed(); + m_Refreshed(); } -void PluginList::fixPriorities() { - std::vector> espPrios; +void PluginList::fixPriorities() +{ + std::vector> espPrios; - for (int i = 0; i < m_ESPs.size(); ++i) { - int prio = m_ESPs[i].m_Priority; - if (prio == -1) { - prio = INT_MAX; - } - espPrios.push_back(std::make_pair(prio, i)); + for (int i = 0; i < m_ESPs.size(); ++i) { + int prio = m_ESPs[i].m_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; }); + 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].m_Priority = i; - } + for (int i = 0; i < espPrios.size(); ++i) { + m_ESPs[espPrios[i].second].m_Priority = i; + } } -void PluginList::enableESP(const QString& name, bool enable) { - std::map::iterator iter = m_ESPsByName.find(name.toLower()); +void PluginList::enableESP(const QString &name, bool enable) +{ + std::map::iterator iter = m_ESPsByName.find(name.toLower()); - if (iter != m_ESPsByName.end()) { - m_ESPs[iter->second].m_Enabled = enable | m_ESPs[iter->second].m_ForceEnabled; + if (iter != m_ESPsByName.end()) { + m_ESPs[iter->second].m_Enabled = + enable | m_ESPs[iter->second].m_ForceEnabled; - emit writePluginsList(); - } else { - reportError(tr("esp not found: %1").arg(name)); - } + emit writePluginsList(); + } else { + reportError(tr("esp not found: %1").arg(name)); + } } -void PluginList::enableAll() { - if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really enable all plugins?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - for (ESPInfo& info : m_ESPs) { - info.m_Enabled = true; - } - emit writePluginsList(); - } -} -void PluginList::disableAll() { - if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really disable all plugins?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - for (ESPInfo& info : m_ESPs) { - if (!info.m_ForceEnabled) { - info.m_Enabled = false; - } - } - emit writePluginsList(); +void PluginList::enableAll() +{ + if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really enable all plugins?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + for (ESPInfo &info : m_ESPs) { + info.m_Enabled = true; } + emit writePluginsList(); + } } -bool PluginList::isEnabled(const QString& name) { - std::map::iterator iter = m_ESPsByName.find(name.toLower()); - if (iter != m_ESPsByName.end()) { - return m_ESPs[iter->second].m_Enabled; - } else { - return false; +void PluginList::disableAll() +{ + if (QMessageBox::question(nullptr, tr("Confirm"), tr("Really disable all plugins?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + for (ESPInfo &info : m_ESPs) { + if (!info.m_ForceEnabled) { + info.m_Enabled = false; + } } + emit writePluginsList(); + } } -void PluginList::clearInformation(const QString& name) { - std::map::iterator iter = m_ESPsByName.find(name.toLower()); - if (iter != m_ESPsByName.end()) { - m_AdditionalInfo[name.toLower()].m_Messages.clear(); - } +bool PluginList::isEnabled(const QString &name) +{ + std::map::iterator iter = m_ESPsByName.find(name.toLower()); + + if (iter != m_ESPsByName.end()) { + return m_ESPs[iter->second].m_Enabled; + } else { + return false; + } } -void PluginList::clearAdditionalInformation() { m_AdditionalInfo.clear(); } +void PluginList::clearInformation(const QString &name) +{ + std::map::iterator iter = m_ESPsByName.find(name.toLower()); -void PluginList::addInformation(const QString& name, const QString& message) { - std::map::iterator iter = m_ESPsByName.find(name.toLower()); + if (iter != m_ESPsByName.end()) { + m_AdditionalInfo[name.toLower()].m_Messages.clear(); + } +} - if (iter != m_ESPsByName.end()) { - m_AdditionalInfo[name.toLower()].m_Messages.append(message); - } else { - qWarning("failed to associate message for \"%s\"", qPrintable(name)); - } +void PluginList::clearAdditionalInformation() +{ + m_AdditionalInfo.clear(); } -bool PluginList::isEnabled(int index) { return m_ESPs.at(index).m_Enabled; } +void PluginList::addInformation(const QString &name, const QString &message) +{ + std::map::iterator iter = m_ESPsByName.find(name.toLower()); -void PluginList::readLockedOrderFrom(const QString& fileName) { - m_LockedOrder.clear(); + if (iter != m_ESPsByName.end()) { + m_AdditionalInfo[name.toLower()].m_Messages.append(message); + } else { + qWarning("failed to associate message for \"%s\"", qPrintable(name)); + } +} - QFile file(fileName); - if (!file.exists()) { - // no locked load order, that's ok - return; - } +bool PluginList::isEnabled(int index) +{ + return m_ESPs.at(index).m_Enabled; +} - file.open(QIODevice::ReadOnly); - while (!file.atEnd()) { - QByteArray line = file.readLine(); - if ((line.size() > 0) && (line.at(0) != '#')) { - QList fields = line.split('|'); - if (fields.count() == 2) { - int priority = fields.at(1).trimmed().toInt(); - QString name = QString::fromUtf8(fields.at(0)); - // Avoid locking a force-enabled plugin - if (!m_ESPs[m_ESPsByName.at(name)].m_ForceEnabled) { - // Is this an open and unclaimed priority? - if (m_ESPs[m_ESPsByPriority.at(priority)].m_ForceEnabled || - std::find_if(m_LockedOrder.begin(), m_LockedOrder.end(), [&](const std::pair& a) { - return a.second == priority; - }) != m_LockedOrder.end()) { - // Attempt to find a priority but step over force-enabled plugins and already-set locks - int calcPriority = priority; - do { - ++calcPriority; - } while (calcPriority < m_ESPsByPriority.size() || - (m_ESPs[m_ESPsByPriority.at(calcPriority)].m_ForceEnabled && - std::find_if(m_LockedOrder.begin(), m_LockedOrder.end(), - [&](const std::pair& a) { - return a.second == calcPriority; - }) != m_LockedOrder.end())); - // If we have a match, we can reassign the priority... - if (calcPriority < m_ESPsByPriority.size()) - m_LockedOrder[name] = calcPriority; - } else { - m_LockedOrder[name] = priority; - } - } - } else { - reportError(tr("The file containing locked plugin indices is broken")); - break; - } +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); + while (!file.atEnd()) { + QByteArray line = file.readLine(); + if ((line.size() > 0) && (line.at(0) != '#')) { + QList fields = line.split('|'); + if (fields.count() == 2) { + int priority = fields.at(1).trimmed().toInt(); + QString name = QString::fromUtf8(fields.at(0)); + // Avoid locking a force-enabled plugin + if (!m_ESPs[m_ESPsByName.at(name)].m_ForceEnabled) { + // Is this an open and unclaimed priority? + if (m_ESPs[m_ESPsByPriority.at(priority)].m_ForceEnabled || + std::find_if(m_LockedOrder.begin(), m_LockedOrder.end(), [&](const std::pair &a) { return a.second == priority; }) != m_LockedOrder.end()) { + // Attempt to find a priority but step over force-enabled plugins and already-set locks + int calcPriority = priority; + do { + ++calcPriority; + } while (calcPriority < m_ESPsByPriority.size() || (m_ESPs[m_ESPsByPriority.at(calcPriority)].m_ForceEnabled && + std::find_if(m_LockedOrder.begin(), m_LockedOrder.end(), [&](const std::pair &a) { return a.second == calcPriority; }) != m_LockedOrder.end())); + // If we have a match, we can reassign the priority... + if (calcPriority < m_ESPsByPriority.size()) + m_LockedOrder[name] = calcPriority; + } else { + m_LockedOrder[name] = priority; + } } + } else { + reportError(tr("The file containing locked plugin indices is broken")); + break; + } } - file.close(); + } + 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(); - qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); +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(); + qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); } -void PluginList::saveTo(const QString& lockedOrderFileName, const QString& deleterFileName, bool hideUnchecked) const { - GamePlugins* gamePlugins = m_GamePlugin->feature(); - gamePlugins->writePluginLists(this); - writeLockedOrder(lockedOrderFileName); +void PluginList::saveTo(const QString &lockedOrderFileName + , const QString& deleterFileName + , bool hideUnchecked) const +{ + GamePlugins *gamePlugins = m_GamePlugin->feature(); + gamePlugins->writePluginLists(this); - if (hideUnchecked) { - SafeWriteFile deleterFile(deleterFileName); - deleterFile->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); + writeLockedOrder(lockedOrderFileName); - for (size_t i = 0; i < m_ESPs.size(); ++i) { - int priority = m_ESPsByPriority[i]; - if (!m_ESPs[priority].m_Enabled) { - deleterFile->write(m_ESPs[priority].m_Name.toUtf8()); - deleterFile->write("\r\n"); - } - } - if (deleterFile.commitIfDifferent(m_LastSaveHash[deleterFileName])) { - qDebug("%s saved", qPrintable(QDir::toNativeSeparators(deleterFileName))); - } - } else if (QFile::exists(deleterFileName)) { - shellDelete(QStringList() << deleterFileName); + if (hideUnchecked) { + SafeWriteFile deleterFile(deleterFileName); + deleterFile->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); + + for (size_t i = 0; i < m_ESPs.size(); ++i) { + int priority = m_ESPsByPriority[i]; + if (!m_ESPs[priority].m_Enabled) { + deleterFile->write(m_ESPs[priority].m_Name.toUtf8()); + deleterFile->write("\r\n"); + } + } + if (deleterFile.commitIfDifferent(m_LastSaveHash[deleterFileName])) { + qDebug("%s saved", qPrintable(QDir::toNativeSeparators(deleterFileName))); } + } else if (QFile::exists(deleterFileName)) { + shellDelete(QStringList() << deleterFileName); + } } -bool PluginList::saveLoadOrder(DirectoryEntry& directoryStructure) { - if (m_GamePlugin->loadOrderMechanism() != IPluginGame::LoadOrderMechanism::FileTime) { - // nothing to do - return true; - } - qDebug("setting file times on esps"); - - for (ESPInfo& esp : m_ESPs) { - std::wstring espName = ToWString(esp.m_Name); - const FileEntry::Ptr 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.m_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.m_Priority) * 24 * 60 * 60 * 10000000ULL; - - FILETIME newWriteTime; - - newWriteTime.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF); - newWriteTime.dwHighDateTime = (DWORD)(temp >> 32); - esp.m_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); +bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure) +{ + if (m_GamePlugin->loadOrderMechanism() != IPluginGame::LoadOrderMechanism::FileTime) { + // nothing to do + return true; + } + + qDebug("setting file times on esps"); + + for (ESPInfo &esp : m_ESPs) { + std::wstring espName = ToWString(esp.m_Name); + const FileEntry::Ptr 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.m_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.m_Priority) * 24 * 60 * 60 * 10000000ULL; + + FILETIME newWriteTime; + + newWriteTime.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF); + newWriteTime.dwHighDateTime = (DWORD)(temp >> 32); + esp.m_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; + } + return true; } -int PluginList::enabledCount() const { - int enabled = 0; - for (const auto& info : m_ESPs) { - if (info.m_Enabled) { - ++enabled; - } +int PluginList::enabledCount() const +{ + int enabled = 0; + for (const auto &info : m_ESPs) { + if (info.m_Enabled) { + ++enabled; } - return enabled; + } + return enabled; } -bool PluginList::isESPLocked(int index) const { - return m_LockedOrder.find(m_ESPs.at(index).m_Name.toLower()) != m_LockedOrder.end(); +bool PluginList::isESPLocked(int index) const +{ + return m_LockedOrder.find(m_ESPs.at(index).m_Name.toLower()) != m_LockedOrder.end(); } -void PluginList::lockESPIndex(int index, bool lock) { - if (lock) { - if (!m_ESPs.at(index).m_ForceEnabled) - m_LockedOrder[getName(index).toLower()] = m_ESPs.at(index).m_LoadOrder; - else - return; - } else { - auto iter = m_LockedOrder.find(getName(index).toLower()); - if (iter != m_LockedOrder.end()) { - m_LockedOrder.erase(iter); - } +void PluginList::lockESPIndex(int index, bool lock) +{ + if (lock) { + if (!m_ESPs.at(index).m_ForceEnabled) + m_LockedOrder[getName(index).toLower()] = m_ESPs.at(index).m_LoadOrder; + else + return; + } else { + auto iter = m_LockedOrder.find(getName(index).toLower()); + if (iter != m_LockedOrder.end()) { + m_LockedOrder.erase(iter); } - emit writePluginsList(); + } + emit writePluginsList(); } -void PluginList::syncLoadOrder() { - int loadOrder = 0; - for (unsigned int i = 0; i < m_ESPs.size(); ++i) { - int index = m_ESPsByPriority[i]; +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].m_Enabled) { - m_ESPs[index].m_LoadOrder = loadOrder++; - } else { - m_ESPs[index].m_LoadOrder = -1; - } + if (m_ESPs[index].m_Enabled) { + m_ESPs[index].m_LoadOrder = loadOrder++; + } else { + m_ESPs[index].m_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.toLower()); - 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]].m_LoadOrder < iter->first)) { - ++targetPrio; - } - - if (static_cast(targetPrio) >= m_ESPs.size()) { - continue; - } - - int temp = targetPrio; - int index = nameIter->second; - if (m_ESPs[index].m_Priority != temp) { - setPluginPriority(index, temp); - m_ESPs[index].m_LoadOrder = iter->first; - syncLoadOrder(); - savePluginsList = true; - } - } - } - if (savePluginsList) { - emit writePluginsList(); +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.toLower()); + 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]].m_LoadOrder < iter->first)) { + ++targetPrio; + } + + if (static_cast(targetPrio) >= m_ESPs.size()) { + continue; + } + + int temp = targetPrio; + int index = nameIter->second; + if (m_ESPs[index].m_Priority != temp) { + setPluginPriority(index, temp); + m_ESPs[index].m_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(); + m_PluginMoved.disconnect_all_slots(); + m_Refreshed.disconnect_all_slots(); + m_PluginStateChanged.disconnect_all_slots(); } -QStringList PluginList::pluginNames() const { - QStringList result; +QStringList PluginList::pluginNames() const +{ + QStringList result; - for (const ESPInfo& info : m_ESPs) { - result.append(info.m_Name); - } + for (const ESPInfo &info : m_ESPs) { + result.append(info.m_Name); + } - return result; + return result; } -IPluginList::PluginStates PluginList::state(const QString& name) const { - auto iter = m_ESPsByName.find(name.toLower()); - if (iter == m_ESPsByName.end()) { - return IPluginList::STATE_MISSING; - } else { - return m_ESPs[iter->second].m_Enabled ? IPluginList::STATE_ACTIVE : IPluginList::STATE_INACTIVE; - } +IPluginList::PluginStates PluginList::state(const QString &name) const +{ + auto iter = m_ESPsByName.find(name.toLower()); + if (iter == m_ESPsByName.end()) { + return IPluginList::STATE_MISSING; + } else { + return m_ESPs[iter->second].m_Enabled ? IPluginList::STATE_ACTIVE : IPluginList::STATE_INACTIVE; + } } -void PluginList::setState(const QString& name, PluginStates state) { - auto iter = m_ESPsByName.find(name.toLower()); - if (iter != m_ESPsByName.end()) { - m_ESPs[iter->second].m_Enabled = (state == IPluginList::STATE_ACTIVE) || m_ESPs[iter->second].m_ForceEnabled; - } else { - qWarning("plugin %s not found", qPrintable(name)); - } +void PluginList::setState(const QString &name, PluginStates state) { + auto iter = m_ESPsByName.find(name.toLower()); + if (iter != m_ESPsByName.end()) { + m_ESPs[iter->second].m_Enabled = (state == IPluginList::STATE_ACTIVE) || + m_ESPs[iter->second].m_ForceEnabled; + } else { + qWarning("plugin %s not found", qPrintable(name)); + } } -void PluginList::setLoadOrder(const QStringList& pluginList) { - for (ESPInfo& info : m_ESPs) { - info.m_Priority = -1; - } - int maxPriority = 0; - for (const QString& plugin : pluginList) { - auto iter = m_ESPsByName.find(plugin.toLower()); - if (iter != m_ESPsByName.end()) { - m_ESPs[iter->second].m_Priority = maxPriority++; - } +void PluginList::setLoadOrder(const QStringList &pluginList) +{ + for (ESPInfo &info : m_ESPs) { + info.m_Priority = -1; + } + int maxPriority = 0; + for (const QString &plugin : pluginList) { + auto iter = m_ESPsByName.find(plugin.toLower()); + if (iter !=m_ESPsByName.end()) { + m_ESPs[iter->second].m_Priority = maxPriority++; } + } - // use old priorities - for (ESPInfo& info : m_ESPs) { - if (info.m_Priority == -1) { - info.m_Priority = maxPriority++; - } + // use old priorities + for (ESPInfo &info : m_ESPs) { + if (info.m_Priority == -1) { + info.m_Priority = maxPriority++; } - updateIndices(); + } + updateIndices(); } -int PluginList::priority(const QString& name) const { - auto iter = m_ESPsByName.find(name.toLower()); - if (iter == m_ESPsByName.end()) { - return -1; - } else { - return m_ESPs[iter->second].m_Priority; - } +int PluginList::priority(const QString &name) const +{ + auto iter = m_ESPsByName.find(name.toLower()); + if (iter == m_ESPsByName.end()) { + return -1; + } else { + return m_ESPs[iter->second].m_Priority; + } } -int PluginList::loadOrder(const QString& name) const { - auto iter = m_ESPsByName.find(name.toLower()); - if (iter == m_ESPsByName.end()) { - return -1; - } else { - return m_ESPs[iter->second].m_LoadOrder; - } +int PluginList::loadOrder(const QString &name) const +{ + auto iter = m_ESPsByName.find(name.toLower()); + if (iter == m_ESPsByName.end()) { + return -1; + } else { + return m_ESPs[iter->second].m_LoadOrder; + } } -bool PluginList::isMaster(const QString& name) const { - auto iter = m_ESPsByName.find(name.toLower()); - if (iter == m_ESPsByName.end()) { - return false; - } else { - return m_ESPs[iter->second].m_IsMaster; - } +bool PluginList::isMaster(const QString &name) const +{ + auto iter = m_ESPsByName.find(name.toLower()); + if (iter == m_ESPsByName.end()) { + return false; + } else { + return m_ESPs[iter->second].m_IsMaster; + } } -bool PluginList::isLight(const QString& name) const { - auto iter = m_ESPsByName.find(name.toLower()); - if (iter == m_ESPsByName.end()) { - return false; - } else { - return m_ESPs[iter->second].m_IsLight; - } +bool PluginList::isLight(const QString &name) const +{ + auto iter = m_ESPsByName.find(name.toLower()); + if (iter == m_ESPsByName.end()) { + return false; + } else { + return m_ESPs[iter->second].m_IsLight; + } } -QStringList PluginList::masters(const QString& name) const { - auto iter = m_ESPsByName.find(name.toLower()); - if (iter == m_ESPsByName.end()) { - return QStringList(); - } else { - QStringList result; - for (const QString& master : m_ESPs[iter->second].m_Masters) { - result.append(master); - } - return result; +QStringList PluginList::masters(const QString &name) const +{ + auto iter = m_ESPsByName.find(name.toLower()); + if (iter == m_ESPsByName.end()) { + return QStringList(); + } else { + QStringList result; + for (const QString &master : m_ESPs[iter->second].m_Masters) { + result.append(master); } + return result; + } } -QString PluginList::origin(const QString& name) const { - auto iter = m_ESPsByName.find(name.toLower()); - if (iter == m_ESPsByName.end()) { - return QString(); - } else { - return m_ESPs[iter->second].m_OriginName; - } +QString PluginList::origin(const QString &name) const +{ + auto iter = m_ESPsByName.find(name.toLower()); + if (iter == m_ESPsByName.end()) { + return QString(); + } else { + return m_ESPs[iter->second].m_OriginName; + } } -bool PluginList::onPluginStateChanged(const std::function& func) { - auto conn = m_PluginStateChanged.connect(func); - return conn.connected(); +bool PluginList::onPluginStateChanged(const std::function &func) +{ + auto conn = m_PluginStateChanged.connect(func); + return conn.connected(); } -bool PluginList::onRefreshed(const std::function& callback) { - auto conn = m_Refreshed.connect(callback); - return conn.connected(); +bool PluginList::onRefreshed(const std::function &callback) +{ + auto conn = m_Refreshed.connect(callback); + return conn.connected(); } -bool PluginList::onPluginMoved(const std::function& func) { - auto conn = m_PluginMoved.connect(func); - return conn.connected(); + +bool PluginList::onPluginMoved(const std::function &func) +{ + auto conn = m_PluginMoved.connect(func); + return conn.connected(); } -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].m_Priority < 0) { - continue; - } - if (m_ESPs[i].m_Priority >= static_cast(m_ESPs.size())) { - qCritical("invalid priority %d", m_ESPs[i].m_Priority); - continue; - } - m_ESPsByName[m_ESPs[i].m_Name.toLower()] = i; - m_ESPsByPriority.at(static_cast(m_ESPs[i].m_Priority)) = i; + +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].m_Priority < 0) { + continue; + } + if (m_ESPs[i].m_Priority >= static_cast(m_ESPs.size())) { + qCritical("invalid priority %d", m_ESPs[i].m_Priority); + continue; } + m_ESPsByName[m_ESPs[i].m_Name.toLower()] = i; + m_ESPsByPriority.at(static_cast(m_ESPs[i].m_Priority)) = i; + } } -int PluginList::rowCount(const QModelIndex& parent) const { - if (!parent.isValid()) { - return static_cast(m_ESPs.size()); - } else { - return 0; - } + +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; } +int PluginList::columnCount(const QModelIndex &) const +{ + return COL_LASTCOLUMN + 1; +} -void PluginList::testMasters() { - // emit layoutAboutToBeChanged(); - std::set enabledMasters; - for (const auto& iter : m_ESPs) { - if (iter.m_Enabled) { - enabledMasters.insert(iter.m_Name.toLower()); - } - } +void PluginList::testMasters() +{ +// emit layoutAboutToBeChanged(); - for (auto& iter : m_ESPs) { - iter.m_MasterUnset.clear(); - if (iter.m_Enabled) { - for (const auto& master : iter.m_Masters) { - if (enabledMasters.find(master.toLower()) == enabledMasters.end()) { - iter.m_MasterUnset.insert(master); - } - } + std::set enabledMasters; + for (const auto& iter: m_ESPs) { + if (iter.m_Enabled) { + enabledMasters.insert(iter.m_Name.toLower()); + } + } + + for (auto& iter: m_ESPs) { + iter.m_MasterUnset.clear(); + if (iter.m_Enabled) { + for (const auto& master: iter.m_Masters) { + if (enabledMasters.find(master.toLower()) == enabledMasters.end()) { + iter.m_MasterUnset.insert(master); } + } } + } #pragma message("emitting this seems to cause a crash!") - // emit layoutChanged(); +// emit layoutChanged(); } -QVariant PluginList::data(const QModelIndex& modelIndex, int role) const { - int index = modelIndex.row(); - if ((role == Qt::DisplayRole) || (role == Qt::EditRole)) { - switch (modelIndex.column()) { - case COL_NAME: { - return m_ESPs[index].m_Name; - } break; - case COL_PRIORITY: { - return m_ESPs[index].m_Priority; - } break; - case COL_MODINDEX: { - if (m_ESPs[index].m_LoadOrder == -1) { - return QString(); - } else { - int numESLs = 0; - std::vector sortESPs(m_ESPs); - std::sort(sortESPs.begin(), sortESPs.end()); - for (auto sortedESP : sortESPs) { - if (sortedESP.m_LoadOrder == m_ESPs[index].m_LoadOrder) - break; - if (sortedESP.m_IsLight && sortedESP.m_LoadOrder != -1) - ++numESLs; - } - if (m_ESPs[index].m_IsLight) { - int ESLpos = 254 + ((numESLs + 1) / 4096); - return QString("%1:%2").arg(ESLpos, 2, 16, QChar('0')).arg((numESLs) % 4096).toUpper(); - } else { - return QString("%1").arg(m_ESPs[index].m_LoadOrder - numESLs, 2, 16, QChar('0')).toUpper(); - } - } - } break; - default: { return QVariant(); } break; - } - } else if ((role == Qt::CheckStateRole) && (modelIndex.column() == 0)) { - if (m_ESPs[index].m_ForceEnabled) { - return QVariant(); - } else { - return m_ESPs[index].m_Enabled ? Qt::Checked : Qt::Unchecked; - } - } else if (role == Qt::ForegroundRole) { - if ((modelIndex.column() == COL_NAME) && m_ESPs[index].m_ForceEnabled) { - return QBrush(Qt::gray); - } - } else if (role == Qt::BackgroundRole || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { - if (m_ESPs[index].m_ModSelected) { - return QColor(0, 0, 255, 32); - } else { - return QVariant(); - } - } else if (role == Qt::FontRole) { - QFont result; - if (m_ESPs[index].m_IsMaster) { - result.setItalic(true); - result.setWeight(QFont::Bold); - } else if (m_ESPs[index].m_IsLight) { - result.setItalic(true); - } - return result; - } else if (role == Qt::TextAlignmentRole) { - if (modelIndex.column() == 0) { - return QVariant(Qt::AlignLeft | Qt::AlignVCenter); +QVariant PluginList::data(const QModelIndex &modelIndex, int role) const +{ + int index = modelIndex.row(); + if ((role == Qt::DisplayRole) + || (role == Qt::EditRole)) { + switch (modelIndex.column()) { + case COL_NAME: { + return m_ESPs[index].m_Name; + } break; + case COL_PRIORITY: { + return m_ESPs[index].m_Priority; + } break; + case COL_MODINDEX: { + if (m_ESPs[index].m_LoadOrder == -1) { + return QString(); } else { - return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); + int numESLs = 0; + std::vector sortESPs(m_ESPs); + std::sort(sortESPs.begin(), sortESPs.end()); + for (auto sortedESP: sortESPs) { + if (sortedESP.m_LoadOrder == m_ESPs[index].m_LoadOrder) + break; + if (sortedESP.m_IsLight && sortedESP.m_LoadOrder != -1) + ++numESLs; + } + if (m_ESPs[index].m_IsLight) { + int ESLpos = 254 + ((numESLs+1) / 4096); + return QString("%1:%2").arg(ESLpos, 2, 16, QChar('0')).arg((numESLs)%4096).toUpper(); + } else { + return QString("%1").arg(m_ESPs[index].m_LoadOrder - numESLs, 2, 16, QChar('0')).toUpper(); + } } - } else if (role == Qt::ToolTipRole) { - QString name = m_ESPs[index].m_Name.toLower(); - auto addInfoIter = m_AdditionalInfo.find(name); - QString toolTip; - if (addInfoIter != m_AdditionalInfo.end()) { - if (!addInfoIter->second.m_Messages.isEmpty()) { - toolTip += addInfoIter->second.m_Messages.join("
            ") + "

            "; - } - } - if (m_ESPs[index].m_ForceEnabled) { - QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName); - text += tr("
            This plugin can't be disabled (enforced by the game)."); - toolTip += text; - } else { - QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName); - if (m_ESPs[index].m_Author.size() > 0) { - text += "
            " + tr("Author") + ": " + m_ESPs[index].m_Author; - } - if (m_ESPs[index].m_Description.size() > 0) { - text += "
            " + tr("Description") + ": " + m_ESPs[index].m_Description; - } - if (m_ESPs[index].m_MasterUnset.size() > 0) { - text += "
            " + tr("Missing Masters") + ": " + SetJoin(m_ESPs[index].m_MasterUnset, ", ") + - ""; - } - std::set enabledMasters; - std::set_difference(m_ESPs[index].m_Masters.begin(), m_ESPs[index].m_Masters.end(), - m_ESPs[index].m_MasterUnset.begin(), m_ESPs[index].m_MasterUnset.end(), - std::inserter(enabledMasters, enabledMasters.end())); - if (!enabledMasters.empty()) { - text += "
            " + tr("Enabled Masters") + ": " + SetJoin(enabledMasters, ", "); - } - if (m_ESPs[index].m_HasIni) { - text += "
            There is an ini file connected to this esp. Its settings will be added to your game " - "settings, overwriting " - "in case of conflicts."; - } - toolTip += text; - } - return toolTip; - } else if (role == Qt::UserRole + 1) { - QVariantList result; - QString nameLower = m_ESPs[index].m_Name.toLower(); - if (m_ESPs[index].m_MasterUnset.size() > 0) { - result.append(":/MO/gui/warning"); - } - if (m_LockedOrder.find(nameLower) != m_LockedOrder.end()) { - result.append(":/MO/gui/locked"); - } - auto bossInfoIter = m_AdditionalInfo.find(nameLower); - if (bossInfoIter != m_AdditionalInfo.end()) { - if (!bossInfoIter->second.m_Messages.isEmpty()) { - result.append(":/MO/gui/information"); - } - } - if (m_ESPs[index].m_HasIni) { - result.append(":/MO/gui/attachment"); - } - return result; + } break; + default: { + return QVariant(); + } break; + } + } else if ((role == Qt::CheckStateRole) && (modelIndex.column() == 0)) { + if (m_ESPs[index].m_ForceEnabled) { + return QVariant(); + } else { + return m_ESPs[index].m_Enabled ? Qt::Checked : Qt::Unchecked; + } + } else if (role == Qt::ForegroundRole) { + if ((modelIndex.column() == COL_NAME) && + m_ESPs[index].m_ForceEnabled) { + return QBrush(Qt::gray); + } + } else if (role == Qt::BackgroundRole + || (role == ViewMarkingScrollBar::DEFAULT_ROLE)) { + if (m_ESPs[index].m_ModSelected) { + return QColor(0, 0, 255, 32); + } else { + return QVariant(); + } + } else if (role == Qt::FontRole) { + QFont result; + if (m_ESPs[index].m_IsMaster) { + result.setItalic(true); + result.setWeight(QFont::Bold); + } else if (m_ESPs[index].m_IsLight) { + result.setItalic(true); } - return QVariant(); + return result; + } else if (role == Qt::TextAlignmentRole) { + if (modelIndex.column() == 0) { + return QVariant(Qt::AlignLeft | Qt::AlignVCenter); + } else { + return QVariant(Qt::AlignHCenter | Qt::AlignVCenter); + } + } else if (role == Qt::ToolTipRole) { + QString name = m_ESPs[index].m_Name.toLower(); + auto addInfoIter = m_AdditionalInfo.find(name); + QString toolTip; + if (addInfoIter != m_AdditionalInfo.end()) { + if (!addInfoIter->second.m_Messages.isEmpty()) { + toolTip += addInfoIter->second.m_Messages.join("
            ") + "

            "; + } + } + if (m_ESPs[index].m_ForceEnabled) { + QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName); + text += tr("
            This plugin can't be disabled (enforced by the game)."); + toolTip += text; + } else { + QString text = tr("Origin: %1").arg(m_ESPs[index].m_OriginName); + if (m_ESPs[index].m_Author.size() > 0) { + text += "
            " + tr("Author") + ": " + m_ESPs[index].m_Author; + } + if (m_ESPs[index].m_Description.size() > 0) { + text += "
            " + tr("Description") + ": " + m_ESPs[index].m_Description; + } + if (m_ESPs[index].m_MasterUnset.size() > 0) { + text += "
            " + tr("Missing Masters") + ": " + SetJoin(m_ESPs[index].m_MasterUnset, ", ") + ""; + } + std::set enabledMasters; + std::set_difference(m_ESPs[index].m_Masters.begin(), m_ESPs[index].m_Masters.end(), + m_ESPs[index].m_MasterUnset.begin(), m_ESPs[index].m_MasterUnset.end(), + std::inserter(enabledMasters, enabledMasters.end())); + if (!enabledMasters.empty()) { + text += "
            " + tr("Enabled Masters") + ": " + SetJoin(enabledMasters, ", "); + } + if (m_ESPs[index].m_HasIni) { + text += "
            There is an ini file connected to this esp. Its settings will be added to your game settings, overwriting " + "in case of conflicts."; + } + toolTip += text; + } + return toolTip; + } else if (role == Qt::UserRole + 1) { + QVariantList result; + QString nameLower = m_ESPs[index].m_Name.toLower(); + if (m_ESPs[index].m_MasterUnset.size() > 0) { + result.append(":/MO/gui/warning"); + } + if (m_LockedOrder.find(nameLower) != m_LockedOrder.end()) { + result.append(":/MO/gui/locked"); + } + auto bossInfoIter = m_AdditionalInfo.find(nameLower); + if (bossInfoIter != m_AdditionalInfo.end()) { + if (!bossInfoIter->second.m_Messages.isEmpty()) { + result.append(":/MO/gui/information"); + } + } + if (m_ESPs[index].m_HasIni) { + result.append(":/MO/gui/attachment"); + } + return result; + } + return QVariant(); } -bool PluginList::setData(const QModelIndex& modIndex, const QVariant& value, int role) { - QString modName = modIndex.data().toString(); - IPluginList::PluginStates oldState = state(modName); - bool result = false; +bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int role) +{ + QString modName = modIndex.data().toString(); + IPluginList::PluginStates oldState = state(modName); - if (role == Qt::CheckStateRole) { - m_ESPs[modIndex.row()].m_Enabled = value.toInt() == Qt::Checked || m_ESPs[modIndex.row()].m_ForceEnabled; - emit dataChanged(modIndex, modIndex); + bool result = false; - refreshLoadOrder(); - emit writePluginsList(); + if (role == Qt::CheckStateRole) { + m_ESPs[modIndex.row()].m_Enabled = + value.toInt() == Qt::Checked || m_ESPs[modIndex.row()].m_ForceEnabled; + 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; - } 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(); - } + } + refreshLoadOrder(); } + } - IPluginList::PluginStates newState = state(modName); - if (oldState != newState) { - try { - m_PluginStateChanged(modName, newState); - testMasters(); - emit dataChanged(this->index(0, 0), this->index(static_cast(m_ESPs.size()), columnCount())); - } catch (const std::exception& e) { - qCritical("failed to invoke state changed notification: %s", e.what()); - } catch (...) { - qCritical("failed to invoke state changed notification: unknown exception"); - } + IPluginList::PluginStates newState = state(modName); + if (oldState != newState) { + try { + m_PluginStateChanged(modName, newState); + testMasters(); + emit dataChanged( + this->index(0, 0), + this->index(static_cast(m_ESPs.size()), columnCount())); + } catch (const std::exception &e) { + qCritical("failed to invoke state changed notification: %s", e.what()); + } catch (...) { + qCritical("failed to invoke state changed notification: unknown exception"); } + } - return result; + 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); - } else if (role == Qt::SizeHintRole) { - QSize temp = m_FontMetrics.size(Qt::TextSingleLine, getColumnName(section)); - temp.rwidth() += 25; - temp.rheight() += 12; - return temp; - } + +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); + } else if (role == Qt::SizeHintRole) { + QSize temp = m_FontMetrics.size(Qt::TextSingleLine, getColumnName(section)); + temp.rwidth() += 25; + temp.rheight() += 12; + return temp; } - return QAbstractItemModel::headerData(section, orientation, role); + } + 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].m_ForceEnabled) { - result |= Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; - } - if (modelIndex.column() == COL_PRIORITY) { - result |= Qt::ItemIsEditable; - } - result &= ~Qt::ItemIsDropEnabled; - } else { - result |= Qt::ItemIsDropEnabled; +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].m_ForceEnabled) { + result |= Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; + } + if (modelIndex.column() == COL_PRIORITY) { + result |= Qt::ItemIsEditable; } + result &= ~Qt::ItemIsDropEnabled; + } else { + result |= Qt::ItemIsDropEnabled; + } - return result; + return result; } -void PluginList::setPluginPriority(int row, int& newPriority) { - int newPriorityTemp = newPriority; - if (!m_ESPs[row].m_IsMaster && !m_ESPs[row].m_IsLight) { - // don't allow esps to be moved above esms - while ((newPriorityTemp < static_cast(m_ESPsByPriority.size() - 1)) && - (m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster || - m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsLight)) { - ++newPriorityTemp; - } +void PluginList::setPluginPriority(int row, int &newPriority) +{ + int newPriorityTemp = newPriority; + + if (!m_ESPs[row].m_IsMaster && !m_ESPs[row].m_IsLight) { + // don't allow esps to be moved above esms + while ((newPriorityTemp < static_cast(m_ESPsByPriority.size() - 1)) && + (m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster || + m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsLight)) { + ++newPriorityTemp; + } + } else { + // don't allow esms to be moved below esps + while ((newPriorityTemp > 0) && + !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster && + !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsLight) { + --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)).m_ForceEnabled)) { + ++newPriorityTemp; + } + } + + // enforce valid range + if (newPriorityTemp < 0) + newPriorityTemp = 0; + else if (newPriorityTemp >= static_cast(m_ESPsByPriority.size())) + newPriorityTemp = static_cast(m_ESPsByPriority.size()) - 1; + + try { + int oldPriority = m_ESPs.at(row).m_Priority; + if (newPriorityTemp > oldPriority) { + // priority is higher than the old, so the gap we left is in lower priorities + for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) { + --m_ESPs.at(m_ESPsByPriority.at(i)).m_Priority; + } + emit dataChanged(index(oldPriority + 1, 0), index(newPriorityTemp, columnCount())); } else { - // don't allow esms to be moved below esps - while ((newPriorityTemp > 0) && !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster && - !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsLight) { - --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)).m_ForceEnabled)) { - ++newPriorityTemp; - } + for (int i = newPriorityTemp; i < oldPriority; ++i) { + ++m_ESPs.at(m_ESPsByPriority.at(i)).m_Priority; + } + emit dataChanged(index(newPriorityTemp, 0), index(oldPriority - 1, columnCount())); + ++newPriority; } - // enforce valid range - if (newPriorityTemp < 0) - newPriorityTemp = 0; - else if (newPriorityTemp >= static_cast(m_ESPsByPriority.size())) - newPriorityTemp = static_cast(m_ESPsByPriority.size()) - 1; + m_ESPs.at(row).m_Priority = newPriorityTemp; + emit dataChanged(index(row, 0), index(row, columnCount())); + m_PluginMoved(m_ESPs[row].m_Name, oldPriority, newPriorityTemp); + } catch (const std::out_of_range&) { + reportError(tr("failed to restore load order for %1").arg(m_ESPs[row].m_Name)); + } - try { - int oldPriority = m_ESPs.at(row).m_Priority; - if (newPriorityTemp > oldPriority) { - // priority is higher than the old, so the gap we left is in lower priorities - for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) { - --m_ESPs.at(m_ESPsByPriority.at(i)).m_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)).m_Priority; - } - emit dataChanged(index(newPriorityTemp, 0), index(oldPriority - 1, columnCount())); - ++newPriority; - } - - m_ESPs.at(row).m_Priority = newPriorityTemp; - emit dataChanged(index(row, 0), index(row, columnCount())); - m_PluginMoved(m_ESPs[row].m_Name, oldPriority, newPriorityTemp); - } catch (const std::out_of_range&) { - reportError(tr("failed to restore load order for %1").arg(m_ESPs[row].m_Name)); - } - - updateIndices(); + updateIndices(); } -void PluginList::changePluginPriority(std::vector rows, int newPriority) { - ChangeBracket layoutChange(this); - // sort rows to insert by their old priority (ascending) and insert them move them in that order - const std::vector& esp = m_ESPs; - std::sort(rows.begin(), rows.end(), - [&esp](const int& LHS, const int& RHS) { return esp[LHS].m_Priority < esp[RHS].m_Priority; }); - - // odd stuff: if any of the dragged sources has priority lower than the destination then the - // target idx is that of the row BELOW the dropped location, otherwise it's the one above. why? - for (std::vector::const_iterator iter = rows.begin(); iter != rows.end(); ++iter) { - if (m_ESPs[*iter].m_Priority < newPriority) { - --newPriority; - break; - } - } - for (std::vector::const_iterator iter = rows.begin(); iter != rows.end(); ++iter) { - setPluginPriority(*iter, newPriority); +void PluginList::changePluginPriority(std::vector rows, int newPriority) +{ + ChangeBracket layoutChange(this); + // sort rows to insert by their old priority (ascending) and insert them move them in that order + const std::vector &esp = m_ESPs; + std::sort(rows.begin(), rows.end(), + [&esp](const int &LHS, const int &RHS) { + return esp[LHS].m_Priority < esp[RHS].m_Priority; + }); + + // odd stuff: if any of the dragged sources has priority lower than the destination then the + // target idx is that of the row BELOW the dropped location, otherwise it's the one above. why? + for (std::vector::const_iterator iter = rows.begin(); + iter != rows.end(); ++iter) { + if (m_ESPs[*iter].m_Priority < newPriority) { + --newPriority; + break; } + } - layoutChange.finish(); - refreshLoadOrder(); - emit writePluginsList(); + 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; - } +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); + QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); + QDataStream stream(&encoded, QIODevice::ReadOnly); - std::vector sourceRows; + 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); - } + 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(); - } + if (row == -1) { + row = parent.row(); + } - int newPriority; + int newPriority; - if ((row < 0) || (row >= static_cast(m_ESPs.size()))) { - newPriority = static_cast(m_ESPs.size()); - } else { - newPriority = m_ESPs[row].m_Priority; - } - changePluginPriority(sourceRows, newPriority); + if ((row < 0) || + (row >= static_cast(m_ESPs.size()))) { + newPriority = static_cast(m_ESPs.size()); + } else { + newPriority = m_ESPs[row].m_Priority; + } + changePluginPriority(sourceRows, newPriority); - return false; + 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::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(); } +QModelIndex PluginList::parent(const QModelIndex&) const +{ + return QModelIndex(); +} -bool PluginList::eventFilter(QObject* obj, QEvent* event) { - if (event->type() == QEvent::KeyPress) { - QAbstractItemView* itemView = qobject_cast(obj); - if (itemView == nullptr) { - return QAbstractItemModel::eventFilter(obj, event); - } +bool PluginList::eventFilter(QObject *obj, QEvent *event) +{ + if (event->type() == QEvent::KeyPress) { + QAbstractItemView *itemView = qobject_cast(obj); - QKeyEvent* keyEvent = static_cast(event); - // ctrl+up and ctrl+down -> increase or decrease priority of selected plugins - if ((keyEvent->modifiers() == Qt::ControlModifier) && - ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) { - QItemSelectionModel* selectionModel = itemView->selectionModel(); - const QSortFilterProxyModel* proxyModel = - qobject_cast(selectionModel->model()); - if (proxyModel != nullptr) { - int diff = -1; - if (((keyEvent->key() == Qt::Key_Up) && (proxyModel->sortOrder() == Qt::DescendingOrder)) || - ((keyEvent->key() == Qt::Key_Down) && (proxyModel->sortOrder() == Qt::AscendingOrder))) { - diff = 1; - } - QModelIndexList rows = selectionModel->selectedRows(); - // remove elements that aren't supposed to be movable - QMutableListIterator iter(rows); - while (iter.hasNext()) { - if ((iter.next().flags() & Qt::ItemIsDragEnabled) == 0) { - iter.remove(); - } - } - if (keyEvent->key() == Qt::Key_Down) { - for (int i = 0; i < rows.size() / 2; ++i) { - rows.swap(i, rows.size() - i - 1); - } - } - for (QModelIndex idx : rows) { - idx = proxyModel->mapToSource(idx); - int newPriority = m_ESPs[idx.row()].m_Priority + diff; - if ((newPriority >= 0) && (newPriority < rowCount())) { - setPluginPriority(idx.row(), newPriority); - } - } - refreshLoadOrder(); - } - return true; - } else if (keyEvent->key() == Qt::Key_Space) { - QItemSelectionModel* selectionModel = itemView->selectionModel(); - const QSortFilterProxyModel* proxyModel = - qobject_cast(selectionModel->model()); - QList indices; - for (QModelIndex idx : selectionModel->selectedRows()) { - indices.append(idx); - } - - QModelIndex minRow, maxRow; - for (QModelIndex idx : indices) { - if (proxyModel != nullptr) { - idx = proxyModel->mapToSource(idx); - } - 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); - - return true; + if (itemView == nullptr) { + return QAbstractItemModel::eventFilter(obj, event); + } + + QKeyEvent *keyEvent = static_cast(event); + // ctrl+up and ctrl+down -> increase or decrease priority of selected plugins + if ((keyEvent->modifiers() == Qt::ControlModifier) && + ((keyEvent->key() == Qt::Key_Up) || (keyEvent->key() == Qt::Key_Down))) { + QItemSelectionModel *selectionModel = itemView->selectionModel(); + const QSortFilterProxyModel *proxyModel = qobject_cast(selectionModel->model()); + if (proxyModel != nullptr) { + int diff = -1; + if (((keyEvent->key() == Qt::Key_Up) && (proxyModel->sortOrder() == Qt::DescendingOrder)) || + ((keyEvent->key() == Qt::Key_Down) && (proxyModel->sortOrder() == Qt::AscendingOrder))) { + diff = 1; + } + QModelIndexList rows = selectionModel->selectedRows(); + // remove elements that aren't supposed to be movable + QMutableListIterator iter(rows); + while (iter.hasNext()) { + if ((iter.next().flags() & Qt::ItemIsDragEnabled) == 0) { + iter.remove(); + } + } + if (keyEvent->key() == Qt::Key_Down) { + for (int i = 0; i < rows.size() / 2; ++i) { + rows.swap(i, rows.size() - i - 1); + } + } + for (QModelIndex idx : rows) { + idx = proxyModel->mapToSource(idx); + int newPriority = m_ESPs[idx.row()].m_Priority + diff; + if ((newPriority >= 0) && (newPriority < rowCount())) { + setPluginPriority(idx.row(), newPriority); + } + } + refreshLoadOrder(); + } + return true; + } else if (keyEvent->key() == Qt::Key_Space) { + QItemSelectionModel *selectionModel = itemView->selectionModel(); + const QSortFilterProxyModel *proxyModel = qobject_cast(selectionModel->model()); + QList indices; + for (QModelIndex idx : selectionModel->selectedRows()) { + indices.append(idx); + } + + QModelIndex minRow, maxRow; + for (QModelIndex idx : indices) { + if (proxyModel != nullptr) { + idx = proxyModel->mapToSource(idx); } + 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); + + return true; } - return QAbstractItemModel::eventFilter(obj, event); + } + return QAbstractItemModel::eventFilter(obj, event); } -PluginList::ESPInfo::ESPInfo(const QString& name, bool enabled, const QString& originName, const QString& fullPath, + +PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, + const QString &originName, const QString &fullPath, bool hasIni) - : m_Name(name), m_FullPath(fullPath), m_Enabled(enabled), m_ForceEnabled(enabled), m_Priority(0), m_LoadOrder(-1), - m_OriginName(originName), m_HasIni(hasIni), m_ModSelected(false) { - try { - ESP::File file(ToWString(fullPath)); - m_IsMaster = file.isMaster(); - auto extension = name.right(3).toLower(); - m_IsLight = (extension == "esl"); // The .isLight() header is apparently meaningless. - - m_Author = QString::fromLatin1(file.author().c_str()); - m_Description = QString::fromLatin1(file.description().c_str()); - std::set masters = file.masters(); - for (auto iter = masters.begin(); iter != masters.end(); ++iter) { - m_Masters.insert(QString(iter->c_str())); - } - } catch (const std::exception& e) { - qCritical("failed to parse plugin file %s: %s", qPrintable(fullPath), e.what()); - m_IsMaster = false; - m_IsLight = false; + : m_Name(name), m_FullPath(fullPath), m_Enabled(enabled), m_ForceEnabled(enabled), + m_Priority(0), m_LoadOrder(-1), m_OriginName(originName), m_HasIni(hasIni), m_ModSelected(false) +{ + try { + ESP::File file(ToWString(fullPath)); + m_IsMaster = file.isMaster(); + auto extension = name.right(3).toLower(); + m_IsLight = (extension == "esl"); // The .isLight() header is apparently meaningless. + + m_Author = QString::fromLatin1(file.author().c_str()); + m_Description = QString::fromLatin1(file.description().c_str()); + std::set masters = file.masters(); + for (auto iter = masters.begin(); iter != masters.end(); ++iter) { + m_Masters.insert(QString(iter->c_str())); } + } catch (const std::exception &e) { + qCritical("failed to parse plugin file %s: %s", qPrintable(fullPath), e.what()); + m_IsMaster = false; + m_IsLight = false; + } } -void PluginList::managedGameChanged(const IPluginGame* gamePlugin) { m_GamePlugin = gamePlugin; } +void PluginList::managedGameChanged(const IPluginGame *gamePlugin) +{ + m_GamePlugin = gamePlugin; +} diff --git a/src/pluginlist.h b/src/pluginlist.h index 762c4a69..8d1a5491 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -20,309 +20,324 @@ along with Mod Organizer. If not, see . #ifndef PLUGINLIST_H #define PLUGINLIST_H -#include "profile.h" #include #include -namespace MOBase { -class IPluginGame; -} +#include "profile.h" +namespace MOBase { class IPluginGame; } -#include #include -#include +#include #include +#include #pragma warning(push) -#pragma warning(disable : 4100) +#pragma warning(disable: 4100) #ifndef Q_MOC_RUN -#include #include +#include #endif -#include #include +#include + 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(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; - } + } + ~ChangeBracket() { + finish(); + } + + void finish() { + if (m_Model != nullptr) { + m_Model->layoutChanged(); + m_Model->setProperty("__aboutToChange", false); + m_Model = nullptr; } + } private: - C* m_Model; + C *m_Model; }; + + /** * @brief model representing the plugins (.esp/.esm) in the current virtual data folder **/ -class PluginList : public QAbstractItemModel, public MOBase::IPluginList { - Q_OBJECT - friend class ChangeBracket; - +class PluginList : public QAbstractItemModel, public MOBase::IPluginList +{ + Q_OBJECT + friend class ChangeBracket; public: - enum EColumn { - COL_NAME, - COL_FLAGS, - COL_PRIORITY, - COL_MODINDEX, - COL_LASTCOLUMN = COL_MODINDEX - }; + enum EColumn { + COL_NAME, + COL_FLAGS, + COL_PRIORITY, + COL_MODINDEX, + + COL_LASTCOLUMN = COL_MODINDEX + }; - typedef boost::signals2::signal SignalRefreshed; - typedef boost::signals2::signal SignalPluginMoved; - typedef boost::signals2::signal SignalPluginStateChanged; + typedef boost::signals2::signal SignalRefreshed; + typedef boost::signals2::signal SignalPluginMoved; + typedef boost::signals2::signal SignalPluginStateChanged; public: - /** - * @brief constructor - * - * @param parent parent object - **/ - PluginList(QObject* parent = nullptr); - - ~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); - - /** - * @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); - - /** - * @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 - * @param deleterFileName file to receive a list of files to hide from the virtual data tree. This is used to hide - *unchecked plugins if "hideUnchecked" is true - * @param hideUnchecked if true, plugins that aren't enabled will be hidden from the virtual data directory - **/ - void saveTo(const QString& lockedOrderFileName, const QString& deleterFileName, bool hideUnchecked) 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; - - QString getName(int index) const { return m_ESPs.at(index).m_Name; } - int getPriority(int index) const { return m_ESPs.at(index).m_Priority; } - bool isESPLocked(int index) const; - void lockESPIndex(int index, bool lock); - - bool eventFilter(QObject* obj, QEvent* event); - - static QString getColumnName(int column); - static QString getColumnToolTip(int column); - - void highlightPlugins(const QItemSelection& selected, const MOShared::DirectoryEntry& directoryEntry, - const Profile& profile); - - void refreshLoadOrder(); - - void disconnectSlots(); + + /** + * @brief constructor + * + * @param parent parent object + **/ + PluginList(QObject *parent = nullptr); + + ~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); + + /** + * @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); + + /** + * @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 + * @param deleterFileName file to receive a list of files to hide from the virtual data tree. This is used to hide unchecked plugins if "hideUnchecked" is true + * @param hideUnchecked if true, plugins that aren't enabled will be hidden from the virtual data directory + **/ + void saveTo(const QString &lockedOrderFileName + , const QString &deleterFileName + , bool hideUnchecked) 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; + + QString getName(int index) const { return m_ESPs.at(index).m_Name; } + int getPriority(int index) const { return m_ESPs.at(index).m_Priority; } + bool isESPLocked(int index) const; + void lockESPIndex(int index, bool lock); + + bool eventFilter(QObject *obj, QEvent *event); + + static QString getColumnName(int column); + static QString getColumnToolTip(int column); + + void highlightPlugins(const QItemSelection &selected, const MOShared::DirectoryEntry &directoryEntry, const Profile &profile); + + void refreshLoadOrder(); + + void disconnectSlots(); public: - virtual QStringList pluginNames() const override; - virtual PluginStates state(const QString& name) const; - virtual void setState(const QString& name, PluginStates state) override; - virtual int priority(const QString& name) const; - virtual int loadOrder(const QString& name) const; - virtual bool onRefreshed(const std::function& callback); - virtual bool isMaster(const QString& name) const; - virtual bool isLight(const QString& name) const; - virtual QStringList masters(const QString& name) const; - virtual QString origin(const QString& name) const; - virtual void setLoadOrder(const QStringList& pluginList) override; - virtual bool onPluginMoved(const std::function& func); - virtual bool onPluginStateChanged(const std::function& func) override; + + virtual QStringList pluginNames() const override; + virtual PluginStates state(const QString &name) const; + virtual void setState(const QString &name, PluginStates state) override; + virtual int priority(const QString &name) const; + virtual int loadOrder(const QString &name) const; + virtual bool onRefreshed(const std::function &callback); + virtual bool isMaster(const QString &name) const; + virtual bool isLight(const QString &name) const; + virtual QStringList masters(const QString &name) const; + virtual QString origin(const QString &name) const; + virtual void setLoadOrder(const QStringList &pluginList) override; + virtual bool onPluginMoved(const std::function &func); + virtual bool onPluginStateChanged(const std::function &func) override; 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; + + 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: - /** - * @brief enables ALL plugins - **/ - void enableAll(); + /** + * @brief enables ALL plugins + **/ + void enableAll(); - /** - * @brief disables ALL plugins - **/ - void disableAll(); + /** + * @brief disables ALL plugins + **/ + void disableAll(); - /** - * @brief The currently managed game has changed - * @param gamePlugin - */ - void managedGameChanged(MOBase::IPluginGame const* gamePlugin); + /** + * @brief The currently managed game has changed + * @param gamePlugin + */ + void managedGameChanged(MOBase::IPluginGame const *gamePlugin); 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(); + /** + * @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(); + void writePluginsList(); private: - struct ESPInfo { - - ESPInfo(const QString& name, bool enabled, const QString& originName, const QString& fullPath, bool hasIni); - QString m_Name; - QString m_FullPath; - bool m_Enabled; - bool m_ForceEnabled; - int m_Priority; - int m_LoadOrder; - FILETIME m_Time; - QString m_OriginName; - bool m_IsMaster; - bool m_IsLight; - bool m_ModSelected; - QString m_Author; - QString m_Description; - bool m_HasIni; - std::set m_Masters; - mutable std::set m_MasterUnset; - bool operator<(const ESPInfo& str) const { return (m_LoadOrder < str.m_LoadOrder); } - }; - - struct AdditionalInfo { - QStringList m_Messages; - }; - - friend bool ByName(const ESPInfo& LHS, const ESPInfo& RHS); - friend bool ByDate(const ESPInfo& LHS, const ESPInfo& RHS); - friend bool ByPriority(const ESPInfo& LHS, const ESPInfo& RHS); + + struct ESPInfo { + + ESPInfo(const QString &name, bool enabled, const QString &originName, const QString &fullPath, bool hasIni); + QString m_Name; + QString m_FullPath; + bool m_Enabled; + bool m_ForceEnabled; + int m_Priority; + int m_LoadOrder; + FILETIME m_Time; + QString m_OriginName; + bool m_IsMaster; + bool m_IsLight; + bool m_ModSelected; + QString m_Author; + QString m_Description; + bool m_HasIni; + std::set m_Masters; + mutable std::set m_MasterUnset; + bool operator < (const ESPInfo& str) const + { + return (m_LoadOrder < str.m_LoadOrder); + } + }; + + struct AdditionalInfo { + QStringList m_Messages; + }; + + friend bool ByName(const ESPInfo& LHS, const ESPInfo& RHS); + friend bool ByDate(const ESPInfo& LHS, const ESPInfo& RHS); + friend bool ByPriority(const ESPInfo& LHS, const ESPInfo& RHS); private: - void syncLoadOrder(); - void updateIndices(); - void writeLockedOrder(const QString& fileName) const; + void syncLoadOrder(); + void updateIndices(); - void readLockedOrderFrom(const QString& fileName); - void setPluginPriority(int row, int& newPriority); - void changePluginPriority(std::vector rows, int newPriority); + void writeLockedOrder(const QString &fileName) const; - void testMasters(); + void readLockedOrderFrom(const QString &fileName); + void setPluginPriority(int row, int &newPriority); + void changePluginPriority(std::vector rows, int newPriority); - void fixPriorities(); + void testMasters(); + + void fixPriorities(); private: - std::vector m_ESPs; - mutable std::map m_LastSaveHash; - std::map m_ESPsByName; - std::vector m_ESPsByPriority; + std::vector m_ESPs; + mutable std::map m_LastSaveHash; + + std::map m_ESPsByName; + std::vector m_ESPsByPriority; + + std::map m_LockedOrder; - std::map m_LockedOrder; + std::map m_AdditionalInfo; // maps esp names to boss information - std::map m_AdditionalInfo; // maps esp names to boss information + QString m_CurrentProfile; + QFontMetrics m_FontMetrics; - QString m_CurrentProfile; - QFontMetrics m_FontMetrics; + SignalRefreshed m_Refreshed; + SignalPluginMoved m_PluginMoved; + SignalPluginStateChanged m_PluginStateChanged; - SignalRefreshed m_Refreshed; - SignalPluginMoved m_PluginMoved; - SignalPluginStateChanged m_PluginStateChanged; + QTemporaryFile m_TempFile; - QTemporaryFile m_TempFile; + const MOBase::IPluginGame *m_GamePlugin; - const MOBase::IPluginGame* m_GamePlugin; }; #pragma warning(pop) diff --git a/src/pluginlistsortproxy.cpp b/src/pluginlistsortproxy.cpp index e6e8f6df..342b3744 100644 --- a/src/pluginlistsortproxy.cpp +++ b/src/pluginlistsortproxy.cpp @@ -19,94 +19,116 @@ along with Mod Organizer. If not, see . #include "pluginlistsortproxy.h" #include "messagedialog.h" -#include -#include #include -#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); + +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::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(); } -Qt::ItemFlags PluginListSortProxy::flags(const QModelIndex& modelIndex) const { - /* Qt::ItemFlags flags; - QModelIndex index = mapToSource(modelIndex); - if (index.isValid()) { - flags = sourceModel()->flags(index); - } - return flags;*/ - return sourceModel()->flags(mapToSource(modelIndex)); +Qt::ItemFlags PluginListSortProxy::flags(const QModelIndex &modelIndex) const +{ +/* Qt::ItemFlags flags; + QModelIndex index = mapToSource(modelIndex); + if (index.isValid()) { + flags = sourceModel()->flags(index); + } + return flags;*/ + + return sourceModel()->flags(mapToSource(modelIndex)); } -void PluginListSortProxy::updateFilter(const QString& filter) { - m_CurrentFilter = filter; - invalidateFilter(); + +void PluginListSortProxy::updateFilter(const QString &filter) +{ + m_CurrentFilter = filter; + invalidateFilter(); } -bool PluginListSortProxy::filterAcceptsRow(int row, const QModelIndex&) const { - return m_CurrentFilter.isEmpty() || - sourceModel()->data(sourceModel()->index(row, 0)).toString().contains(m_CurrentFilter, Qt::CaseInsensitive); + +bool PluginListSortProxy::filterAcceptsRow(int row, const QModelIndex&) const +{ + return m_CurrentFilter.isEmpty() || + sourceModel()->data(sourceModel()->index(row, 0)).toString().contains(m_CurrentFilter, Qt::CaseInsensitive); } -bool PluginListSortProxy::lessThan(const QModelIndex& left, const QModelIndex& right) const { - PluginList* plugins = qobject_cast(sourceModel()); - switch (left.column()) { + +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; + 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) < rhsList.at(i); - } - } - return false; + 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) < rhsList.at(i); + } } + return false; + } } break; case PluginList::COL_MODINDEX: { - int leftVal = plugins->isEnabled(left.row()) ? plugins->getPriority(left.row()) : -1; - int rightVal = plugins->isEnabled(right.row()) ? plugins->getPriority(right.row()) : -1; - return leftVal < rightVal; + int leftVal = plugins->isEnabled(left.row()) ? plugins->getPriority(left.row()) : -1; + int rightVal = plugins->isEnabled(right.row()) ? plugins->getPriority(right.row()) : -1; + return leftVal < rightVal; } break; - default: { return plugins->getPriority(left.row()) < plugins->getPriority(right.row()); } 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; + +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)); + 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; + --row; } QModelIndex proxyIndex = index(row, column, parent); @@ -114,3 +136,4 @@ bool PluginListSortProxy::dropMimeData(const QMimeData* data, Qt::DropAction act return this->sourceModel()->dropMimeData(data, action, sourceIndex.row(), sourceIndex.column(), sourceIndex.parent()); } + diff --git a/src/pluginlistsortproxy.h b/src/pluginlistsortproxy.h index b9cdd0fa..89f60e70 100644 --- a/src/pluginlistsortproxy.h +++ b/src/pluginlistsortproxy.h @@ -20,39 +20,49 @@ along with Mod Organizer. If not, see . #ifndef PLUGINLISTSORTPROXY_H #define PLUGINLISTSORTPROXY_H -#include "pluginlist.h" -#include + #include +#include +#include "pluginlist.h" + -class PluginListSortProxy : public QSortFilterProxyModel { - Q_OBJECT +class PluginListSortProxy : public QSortFilterProxyModel +{ + Q_OBJECT public: - enum ESorting { SORT_ASCENDING, SORT_DESCENDING }; + + enum ESorting { + SORT_ASCENDING, + SORT_DESCENDING + }; public: - explicit PluginListSortProxy(QObject* parent = 0); - void setEnabledColumns(unsigned int columns); + explicit PluginListSortProxy(QObject *parent = 0); + + void setEnabledColumns(unsigned int columns); - virtual Qt::ItemFlags flags(const QModelIndex& modelIndex) const; + virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; - virtual bool dropMimeData(const QMimeData* data, Qt::DropAction action, int row, int column, - const QModelIndex& parent); + virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); public slots: - void updateFilter(const QString& filter); + 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; + + 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; + 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 9d38af62..0fcf8183 100644 --- a/src/pluginlistview.cpp +++ b/src/pluginlistview.cpp @@ -1,50 +1,58 @@ #include "pluginlistview.h" +#include #include #include -#include + class PluginListViewStyle : public QProxyStyle { public: - PluginListViewStyle(QStyle* style, int indentation); - - void drawPrimitive(PrimitiveElement element, const QStyleOption* option, QPainter* painter, - const QWidget* widget = 0) const; + PluginListViewStyle(QStyle *style, int indentation); + void drawPrimitive(PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget = 0) const; private: - int m_Indentation; + int m_Indentation; }; -PluginListViewStyle::PluginListViewStyle(QStyle* style, int indentation) - : QProxyStyle(style), m_Indentation(indentation) {} - -void PluginListViewStyle::drawPrimitive(PrimitiveElement element, const QStyleOption* option, QPainter* painter, - const QWidget* widget) const { - if (element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull()) { - QStyleOption opt(*option); - opt.rect.setLeft(m_Indentation); - if (widget) { - opt.rect.setRight(widget->width() - 5); // 5 is an arbitrary value that seems to work ok - } - QProxyStyle::drawPrimitive(element, &opt, painter, widget); - } else { - QProxyStyle::drawPrimitive(element, option, painter, widget); +PluginListViewStyle::PluginListViewStyle(QStyle *style, int indentation) + : QProxyStyle(style), m_Indentation(indentation) +{ +} + +void PluginListViewStyle::drawPrimitive(PrimitiveElement element, const QStyleOption *option, + QPainter *painter, const QWidget *widget) const +{ + if (element == QStyle::PE_IndicatorItemViewItemDrop && !option->rect.isNull()) { + QStyleOption opt(*option); + opt.rect.setLeft(m_Indentation); + if (widget) { + opt.rect.setRight(widget->width() - 5); // 5 is an arbitrary value that seems to work ok } + QProxyStyle::drawPrimitive(element, &opt, painter, widget); + } + else { + QProxyStyle::drawPrimitive(element, option, painter, widget); + } } -PluginListView::PluginListView(QWidget* parent) - : QTreeView(parent), m_Scrollbar(new ViewMarkingScrollBar(this->model(), this)) { - setVerticalScrollBar(m_Scrollbar); +PluginListView::PluginListView(QWidget *parent) + : QTreeView(parent) + , m_Scrollbar(new ViewMarkingScrollBar(this->model(), this)) +{ + setVerticalScrollBar(m_Scrollbar); } -void PluginListView::dragEnterEvent(QDragEnterEvent* event) { - emit dropModeUpdate(event->mimeData()->hasUrls()); +void PluginListView::dragEnterEvent(QDragEnterEvent *event) +{ + emit dropModeUpdate(event->mimeData()->hasUrls()); - QTreeView::dragEnterEvent(event); + QTreeView::dragEnterEvent(event); } -void PluginListView::setModel(QAbstractItemModel* model) { - QTreeView::setModel(model); - setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); +void PluginListView::setModel(QAbstractItemModel *model) +{ + QTreeView::setModel(model); + setVerticalScrollBar(new ViewMarkingScrollBar(model, this)); } #pragma once diff --git a/src/pluginlistview.h b/src/pluginlistview.h index 49e440ab..bdd4ee61 100644 --- a/src/pluginlistview.h +++ b/src/pluginlistview.h @@ -1,22 +1,24 @@ #ifndef PLUGINLISTVIEW_H #define PLUGINLISTVIEW_H -#include "viewmarkingscrollbar.h" -#include #include +#include +#include "viewmarkingscrollbar.h" -class PluginListView : public QTreeView { - Q_OBJECT +class PluginListView : public QTreeView +{ + Q_OBJECT public: - explicit PluginListView(QWidget* parent = 0); - virtual void dragEnterEvent(QDragEnterEvent* event); - virtual void setModel(QAbstractItemModel* model); + explicit PluginListView(QWidget *parent = 0); + virtual void dragEnterEvent(QDragEnterEvent *event); + virtual void setModel(QAbstractItemModel *model); signals: - void dropModeUpdate(bool dropOnRows); + void dropModeUpdate(bool dropOnRows); -public slots: + public slots: private: - ViewMarkingScrollBar* m_Scrollbar; + + ViewMarkingScrollBar *m_Scrollbar; }; #endif // PLUGINLISTVIEW_H diff --git a/src/previewdialog.cpp b/src/previewdialog.cpp index 4a50ace3..de33cdd0 100644 --- a/src/previewdialog.cpp +++ b/src/previewdialog.cpp @@ -2,40 +2,56 @@ #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(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; } +PreviewDialog::~PreviewDialog() +{ + delete ui; +} -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); - } +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(); } +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_variantsStack_currentChanged(int index) +{ + ui->modLabel->setText(ui->variantsStack->widget(index)->property("modName").toString()); } -void PreviewDialog::on_closeButton_clicked() { this->accept(); } +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_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()); +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 5dfb2b96..0011bc50 100644 --- a/src/previewdialog.h +++ b/src/previewdialog.h @@ -7,28 +7,30 @@ namespace Ui { class PreviewDialog; } -class PreviewDialog : public QDialog { - Q_OBJECT +class PreviewDialog : public QDialog +{ + Q_OBJECT public: - explicit PreviewDialog(const QString& fileName, QWidget* parent = 0); - ~PreviewDialog(); + explicit PreviewDialog(const QString &fileName, QWidget *parent = 0); + ~PreviewDialog(); - void addVariant(const QString& modName, QWidget* widget); - int numVariants() const; + void addVariant(const QString &modName, QWidget *widget); + int numVariants() const; private slots: - void on_variantsStack_currentChanged(int arg1); + void on_variantsStack_currentChanged(int arg1); - void on_closeButton_clicked(); + void on_closeButton_clicked(); - void on_previousButton_clicked(); + void on_previousButton_clicked(); - void on_nextButton_clicked(); + void on_nextButton_clicked(); private: - Ui::PreviewDialog* ui; + + Ui::PreviewDialog *ui; }; #endif // PREVIEWDIALOG_H diff --git a/src/previewgenerator.cpp b/src/previewgenerator.cpp index 93602152..280d69aa 100644 --- a/src/previewgenerator.cpp +++ b/src/previewgenerator.cpp @@ -18,34 +18,38 @@ along with Mod Organizer. If not, see . */ #include "previewgenerator.h" -#include #include -#include #include +#include #include +#include #include -PreviewGenerator::PreviewGenerator() { +PreviewGenerator::PreviewGenerator() +{ - QDesktopWidget desk; - m_MaxSize = desk.screenGeometry().size() * 0.8; + QDesktopWidget desk; + m_MaxSize = desk.screenGeometry().size() * 0.8; } -void PreviewGenerator::registerPlugin(MOBase::IPluginPreview* plugin) { - for (const QString& extension : plugin->supportedExtensions()) { - m_PreviewPlugins.insert(std::make_pair(extension, plugin)); - } +void PreviewGenerator::registerPlugin(MOBase::IPluginPreview *plugin) +{ + for (const QString &extension : plugin->supportedExtensions()) { + m_PreviewPlugins.insert(std::make_pair(extension, plugin)); + } } -bool PreviewGenerator::previewSupported(const QString& fileExtension) const { - return m_PreviewPlugins.find(fileExtension.toLower()) != m_PreviewPlugins.end(); +bool PreviewGenerator::previewSupported(const QString &fileExtension) const +{ + return m_PreviewPlugins.find(fileExtension.toLower()) != m_PreviewPlugins.end(); } -QWidget* PreviewGenerator::genPreview(const QString& fileName) const { - auto iter = m_PreviewPlugins.find(QFileInfo(fileName).suffix().toLower()); - if (iter != m_PreviewPlugins.end()) { - return iter->second->genFilePreview(fileName, m_MaxSize); - } else { - return nullptr; - } +QWidget *PreviewGenerator::genPreview(const QString &fileName) const +{ + auto iter = m_PreviewPlugins.find(QFileInfo(fileName).suffix().toLower()); + if (iter != m_PreviewPlugins.end()) { + return iter->second->genFilePreview(fileName, m_MaxSize); + } else { + return nullptr; + } } diff --git a/src/previewgenerator.h b/src/previewgenerator.h index 38cb2979..e872b06b 100644 --- a/src/previewgenerator.h +++ b/src/previewgenerator.h @@ -22,28 +22,32 @@ along with Mod Organizer. If not, see . #include #include +#include #include #include -#include -class PreviewGenerator { +class PreviewGenerator +{ public: - PreviewGenerator(); + PreviewGenerator(); - void registerPlugin(MOBase::IPluginPreview* plugin); + void registerPlugin(MOBase::IPluginPreview *plugin); - bool previewSupported(const QString& fileExtension) const; + bool previewSupported(const QString &fileExtension) const; - QWidget* genPreview(const QString& fileName) const; + QWidget *genPreview(const QString &fileName) const; private: - QWidget* genImagePreview(const QString& fileName) const; - QWidget* genTxtPreview(const QString& fileName) const; + + QWidget *genImagePreview(const QString &fileName) const; + QWidget *genTxtPreview(const QString &fileName) const; private: - std::map m_PreviewPlugins; - QSize m_MaxSize; + std::map m_PreviewPlugins; + + QSize m_MaxSize; + }; #endif // PREVIEWGENERATOR_H diff --git a/src/problemsdialog.cpp b/src/problemsdialog.cpp index 146672ba..60a0b1df 100644 --- a/src/problemsdialog.cpp +++ b/src/problemsdialog.cpp @@ -1,68 +1,82 @@ #include "problemsdialog.h" #include "ui_problemsdialog.h" +#include +#include #include #include -#include -#include + using namespace MOBase; -ProblemsDialog::ProblemsDialog(std::vector diagnosePlugins, QWidget* parent) - : QDialog(parent), ui(new Ui::ProblemsDialog), m_DiagnosePlugins(diagnosePlugins) { - ui->setupUi(this); - runDiagnosis(); +ProblemsDialog::ProblemsDialog(std::vector diagnosePlugins, QWidget *parent) + : QDialog(parent), ui(new Ui::ProblemsDialog), m_DiagnosePlugins(diagnosePlugins) +{ + ui->setupUi(this); - connect(ui->problemsWidget, SIGNAL(itemSelectionChanged()), this, SLOT(selectionChanged())); - connect(ui->descriptionText, SIGNAL(anchorClicked(QUrl)), this, SLOT(urlClicked(QUrl))); + runDiagnosis(); + + connect(ui->problemsWidget, SIGNAL(itemSelectionChanged()), this, SLOT(selectionChanged())); + connect(ui->descriptionText, SIGNAL(anchorClicked(QUrl)), this, SLOT(urlClicked(QUrl))); } -ProblemsDialog::~ProblemsDialog() { delete ui; } -void ProblemsDialog::runDiagnosis() { - ui->problemsWidget->clear(); - foreach (IPluginDiagnose* diagnose, m_DiagnosePlugins) { - 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)); +ProblemsDialog::~ProblemsDialog() +{ + delete ui; +} + +void ProblemsDialog::runDiagnosis() +{ + ui->problemsWidget->clear(); + foreach (IPluginDiagnose *diagnose, m_DiagnosePlugins) { + 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); + ui->problemsWidget->addTopLevelItem(newItem); - if (diagnose->hasGuidedFix(key)) { - newItem->setText(1, tr("Fix")); - QPushButton* fixButton = new QPushButton(tr("Fix")); - fixButton->setProperty("fix", qVariantFromValue(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 (diagnose->hasGuidedFix(key)) { + newItem->setText(1, tr("Fix")); + QPushButton *fixButton = new QPushButton(tr("Fix")); + fixButton->setProperty("fix", qVariantFromValue(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")); + } } + } } -bool ProblemsDialog::hasProblems() const { return ui->problemsWidget->topLevelItemCount() != 0; } +bool ProblemsDialog::hasProblems() const +{ + return ui->problemsWidget->topLevelItemCount() != 0; +} -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::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) { - qWarning("no button"); - return; - } - IPluginDiagnose* plugin = reinterpret_cast(fixButton->property("fix").value()); - plugin->startGuidedFix(fixButton->property("key").toUInt()); - runDiagnosis(); +void ProblemsDialog::startFix() +{ + QObject *fixButton = QObject::sender(); + if (fixButton == NULL) { + qWarning("no button"); + return; + } + IPluginDiagnose *plugin = reinterpret_cast(fixButton ->property("fix").value()); + plugin->startGuidedFix(fixButton ->property("key").toUInt()); + runDiagnosis(); } -void ProblemsDialog::urlClicked(const QUrl& url) { - ::ShellExecuteW(nullptr, L"open", ToWString(url.toString()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); +void ProblemsDialog::urlClicked(const QUrl &url) +{ + ::ShellExecuteW(nullptr, L"open", ToWString(url.toString()).c_str(), nullptr, nullptr, SW_SHOWNORMAL); } diff --git a/src/problemsdialog.h b/src/problemsdialog.h index f009d5c9..24a69cdf 100644 --- a/src/problemsdialog.h +++ b/src/problemsdialog.h @@ -1,36 +1,40 @@ #ifndef PROBLEMSDIALOG_H #define PROBLEMSDIALOG_H + #include #include #include + namespace Ui { class ProblemsDialog; } -class ProblemsDialog : public QDialog { - Q_OBJECT +class ProblemsDialog : public QDialog +{ + Q_OBJECT + public: - explicit ProblemsDialog(std::vector diagnosePlugins, QWidget* parent = 0); - ~ProblemsDialog(); - - bool hasProblems() const; + explicit ProblemsDialog(std::vector diagnosePlugins, QWidget *parent = 0); + ~ProblemsDialog(); + bool hasProblems() const; private: - void runDiagnosis(); -private slots: + void runDiagnosis(); - void selectionChanged(); - void urlClicked(const QUrl& url); +private slots: - void startFix(); + void selectionChanged(); + void urlClicked(const QUrl &url); + void startFix(); private: - Ui::ProblemsDialog* ui; - std::vector m_DiagnosePlugins; + + Ui::ProblemsDialog *ui; + std::vector m_DiagnosePlugins; }; #endif // PROBLEMSDIALOG_H diff --git a/src/profile.cpp b/src/profile.cpp index baef4259..bbb5b814 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -19,715 +19,823 @@ along with Mod Organizer. If not, see . #include "profile.h" -#include "appconfig.h" #include "modinfo.h" #include "settings.h" -#include -#include +#include #include +#include "appconfig.h" #include #include #include -#include +#include +#include #include -#include -#include -#include // for QFile -#include // for operator|, QFlags -#include // for QIODevice, etc +#include // for QFile +#include // for operator|, QFlags +#include // for QIODevice, etc #include #include -#include // for QStringList -#include // for qDebug, qWarning, etc -#include // for qPrintable +#include // for QStringList +#include // for qDebug, qWarning, etc +#include // for qPrintable +#include +#include #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 set +#include // for find #include -#include // for find using namespace MOBase; using namespace MOShared; -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()); - } +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()); + } } -Profile::Profile(const QString& name, IPluginGame const* gamePlugin, bool useDefaultSettings) - : m_ModListWriter(std::bind(&Profile::doWriteModlist, this)), m_GamePlugin(gamePlugin) { - QString profilesDir = Settings::instance().getProfileDirectory(); - QDir profileBase(profilesDir); - - m_Settings = new QSettings(profileBase.absoluteFilePath("settings.ini")); +Profile::Profile(const QString &name, IPluginGame const *gamePlugin, bool useDefaultSettings) + : m_ModListWriter(std::bind(&Profile::doWriteModlist, this)) + , m_GamePlugin(gamePlugin) +{ + QString profilesDir = Settings::instance().getProfileDirectory(); + QDir profileBase(profilesDir); - QString fixedName = name; - if (!fixDirectoryName(fixedName)) { - throw MyException(tr("invalid profile name %1").arg(name)); - } + m_Settings = new QSettings(profileBase.absoluteFilePath("settings.ini")); - if (!profileBase.exists() || !profileBase.mkdir(fixedName)) { - throw MyException(tr("failed to create %1").arg(fixedName).toUtf8().constData()); - } - QString fullPath = profilesDir + "/" + fixedName; - m_Directory = QDir(fullPath); + QString fixedName = name; + if (!fixDirectoryName(fixedName)) { + throw MyException(tr("invalid profile name %1").arg(name)); + } - try { - // create files. Needs to happen after m_Directory was set! - touchFile("modlist.txt"); - touchFile("archives.txt"); + if (!profileBase.exists() || !profileBase.mkdir(fixedName)) { + throw MyException(tr("failed to create %1").arg(fixedName).toUtf8().constData()); + } + QString fullPath = profilesDir + "/" + fixedName; + m_Directory = QDir(fullPath); - IPluginGame::ProfileSettings settings = IPluginGame::CONFIGURATION | IPluginGame::MODS | IPluginGame::SAVEGAMES; + try { + // create files. Needs to happen after m_Directory was set! + touchFile("modlist.txt"); + touchFile("archives.txt"); - if (useDefaultSettings) { - settings |= IPluginGame::PREFER_DEFAULTS; - } + IPluginGame::ProfileSettings settings = IPluginGame::CONFIGURATION + | IPluginGame::MODS + | IPluginGame::SAVEGAMES; - gamePlugin->initializeProfile(fullPath, settings); - } catch (...) { - // clean up in case of an error - shellDelete(QStringList(profileBase.absoluteFilePath(fixedName))); - throw; + if (useDefaultSettings) { + settings |= IPluginGame::PREFER_DEFAULTS; } - refreshModStatus(); + + gamePlugin->initializeProfile(fullPath, settings); + } catch (...) { + // clean up in case of an error + shellDelete(QStringList(profileBase.absoluteFilePath(fixedName))); + throw; + } + refreshModStatus(); } -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); +Profile::Profile(const QDir &directory, IPluginGame const *gamePlugin) + : m_Directory(directory) + , m_GamePlugin(gamePlugin) + , m_ModListWriter(std::bind(&Profile::doWriteModlist, this)) +{ + assert(gamePlugin != nullptr); - if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { - qWarning("missing modlist.txt in %s", qPrintable(directory.path())); - touchFile(m_Directory.filePath("modlist.txt")); - } + m_Settings = new QSettings(directory.absoluteFilePath("settings.ini"), + QSettings::IniFormat); - IPluginGame::ProfileSettings settings = IPluginGame::MODS | IPluginGame::SAVEGAMES; - gamePlugin->initializeProfile(directory, settings); + if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { + qWarning("missing modlist.txt in %s", qPrintable(directory.path())); + touchFile(m_Directory.filePath("modlist.txt")); + } - refreshModStatus(); + 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) -{ - m_Settings = new QSettings(m_Directory.absoluteFilePath("settings.ini"), QSettings::IniFormat); - 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() { - delete m_Settings; - m_ModListWriter.writeImmediately(true); +{ + m_Settings = new QSettings(m_Directory.absoluteFilePath("settings.ini"), + QSettings::IniFormat); + refreshModStatus(); } -bool Profile::exists() const { return m_Directory.exists(); } - -void Profile::writeModlist() { m_ModListWriter.write(); } -void Profile::writeModlistNow(bool onlyIfPending) { m_ModListWriter.writeImmediately(onlyIfPending); } +Profile::~Profile() +{ + delete m_Settings; + m_ModListWriter.writeImmediately(true); +} -void Profile::cancelModlistWrite() { m_ModListWriter.cancel(); } +bool Profile::exists() const +{ + return m_Directory.exists(); +} -void Profile::doWriteModlist() { - if (!m_Directory.exists()) - return; +void Profile::writeModlist() +{ + m_ModListWriter.write(); +} - try { - QString fileName = getModlistFileName(); - SafeWriteFile file(fileName); +void Profile::writeModlistNow(bool onlyIfPending) +{ + m_ModListWriter.writeImmediately(onlyIfPending); +} - file->write(QString("# This file was automatically generated by Mod Organizer.\r\n").toUtf8()); - if (m_ModStatus.empty()) { - return; - } +void Profile::cancelModlistWrite() +{ + m_ModListWriter.cancel(); +} - for (int i = static_cast(m_ModStatus.size()) - 1; i >= 0; --i) { - // the priority order was inverted on load so it has to be inverted again - unsigned int index = m_ModIndexByPriority[i]; - if (index != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - std::vector flags = modInfo->getFlags(); - if ((modInfo->getFixedPriority() == INT_MIN)) { - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) { - file->write("*"); - } else if (m_ModStatus[index].m_Enabled) { - file->write("+"); - } else { - file->write("-"); - } - file->write(modInfo->name().toUtf8()); - file->write("\r\n"); - } - } +void Profile::doWriteModlist() +{ + 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()); + if (m_ModStatus.empty()) { + return; + } + + for (int i = static_cast(m_ModStatus.size()) - 1; i >= 0; --i) { + // the priority order was inverted on load so it has to be inverted again + unsigned int index = m_ModIndexByPriority[i]; + if (index != UINT_MAX) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + std::vector flags = modInfo->getFlags(); + if ((modInfo->getFixedPriority() == INT_MIN)) { + if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_FOREIGN) != flags.end()) { + file->write("*"); + } else if (m_ModStatus[index].m_Enabled) { + file->write("+"); + } else { + file->write("-"); + } + file->write(modInfo->name().toUtf8()); + file->write("\r\n"); } + } + } - if (file.commitIfDifferent(m_LastModlistHash)) { - qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); - } - } catch (const std::exception& e) { - reportError(tr("failed to write mod list: %1").arg(e.what())); - return; + if (file.commitIfDifferent(m_LastModlistHash)) { + qDebug("%s saved", QDir::toNativeSeparators(fileName).toUtf8().constData()); } + } catch (const std::exception &e) { + reportError(tr("failed to write mod list: %1").arg(e.what())); + return; + } } -void Profile::createTweakedIniFile() { - QString tweakedIni = m_Directory.absoluteFilePath("initweaks.ini"); - if (QFile::exists(tweakedIni) && !shellDeleteQuiet(tweakedIni)) { - reportError(tr("failed to update tweaked ini file, wrong settings may be used: %1") - .arg(windowsErrorString(::GetLastError()))); - return; - } +void Profile::createTweakedIniFile() +{ + QString tweakedIni = m_Directory.absoluteFilePath("initweaks.ini"); - for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { - unsigned int idx = modIndexByPriority(i); - if ((idx != UINT_MAX) && m_ModStatus[idx].m_Enabled) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(idx); - mergeTweaks(modInfo, tweakedIni); - } + if (QFile::exists(tweakedIni) && !shellDeleteQuiet(tweakedIni)) { + reportError(tr("failed to update tweaked ini file, wrong settings may be used: %1").arg(windowsErrorString(::GetLastError()))); + return; + } + + for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { + unsigned int idx = modIndexByPriority(i); + if ((idx != UINT_MAX) && m_ModStatus[idx].m_Enabled) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(idx); + mergeTweaks(modInfo, tweakedIni); } + } - mergeTweak(getProfileTweaks(), tweakedIni); + mergeTweak(getProfileTweaks(), tweakedIni); - bool error = false; - if (!::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) { - error = true; - } + bool error = false; + if (!::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) { + error = true; + } - if (error) { - reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorStringA().c_str())); - } - qDebug("%s saved", qPrintable(QDir::toNativeSeparators(tweakedIni))); + if (error) { + reportError(tr("failed to create tweaked ini: %1").arg(getCurrentErrorStringA().c_str())); + } + qDebug("%s saved", qPrintable(QDir::toNativeSeparators(tweakedIni))); } // static -void Profile::renameModInAllProfiles(const QString& oldName, const QString& newName) { - QDir profilesDir(Settings::instance().getProfileDirectory()); - profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); - QDirIterator profileIter(profilesDir); - while (profileIter.hasNext()) { - profileIter.next(); - QFile modList(profileIter.filePath() + "/modlist.txt"); - if (modList.exists()) - renameModInList(modList, oldName, newName); - else - qWarning("Profile has no modlist.txt : %s", qPrintable(profileIter.filePath())); - } +void Profile::renameModInAllProfiles(const QString& oldName, const QString& newName) +{ + QDir profilesDir(Settings::instance().getProfileDirectory()); + profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); + QDirIterator profileIter(profilesDir); + while (profileIter.hasNext()) { + profileIter.next(); + QFile modList(profileIter.filePath() + "/modlist.txt"); + if (modList.exists()) + renameModInList(modList, oldName, newName); + else + qWarning("Profile has no modlist.txt : %s", qPrintable(profileIter.filePath())); + } } // static -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())); - return; - } +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())); + return; + } - QBuffer outBuffer; - outBuffer.open(QIODevice::WriteOnly); + QBuffer outBuffer; + outBuffer.open(QIODevice::WriteOnly); - int renamed = 0; - while (!modList.atEnd()) { - QByteArray line = modList.readLine(); + int renamed = 0; + while (!modList.atEnd()) { + QByteArray line = modList.readLine(); - if (line.length() == 0) { - // ignore empty lines - qWarning("mod list contained invalid data: empty line"); - continue; - } - - char spec = line.at(0); - if (spec == '#') { - // don't touch comments - outBuffer.write(line); - continue; - } + if (line.length() == 0) { + // ignore empty lines + qWarning("mod list contained invalid data: empty line"); + continue; + } - QString modName = QString::fromUtf8(line).mid(1).trimmed(); + char spec = line.at(0); + if (spec == '#') { + // don't touch comments + outBuffer.write(line); + continue; + } - if (modName.isEmpty()) { - // file broken? - qWarning("mod list contained invalid data: missing mod name"); - continue; - } + QString modName = QString::fromUtf8(line).mid(1).trimmed(); - outBuffer.write(QByteArray(1, spec)); - if (modName == oldName) { - modName = newName; - ++renamed; - } - outBuffer.write(modName.toUtf8().constData()); - outBuffer.write("\r\n"); + if (modName.isEmpty()) { + // file broken? + qWarning("mod list contained invalid data: missing mod name"); + continue; } - modList.close(); - if (renamed) { - modList.open(QIODevice::WriteOnly); - modList.write(outBuffer.buffer()); - modList.close(); + outBuffer.write(QByteArray(1, spec)); + if (modName == oldName) { + modName = newName; + ++renamed; } + outBuffer.write(modName.toUtf8().constData()); + outBuffer.write("\r\n"); + } + modList.close(); - if (renamed) - qDebug("Renamed %d \"%s\" mod to \"%s\" in %s", renamed, qPrintable(oldName), qPrintable(newName), - qPrintable(modList.fileName())); -} - -void Profile::refreshModStatus() { - writeModlistNow(true); // if there are pending changes write them first + if (renamed) { + modList.open(QIODevice::WriteOnly); + modList.write(outBuffer.buffer()); + modList.close(); + } - QFile file(getModlistFileName()); - if (!file.open(QIODevice::ReadOnly)) { - throw MyException(tr("\"%1\" is missing or inaccessible").arg(getModlistFileName())); - } + if (renamed) + qDebug("Renamed %d \"%s\" mod to \"%s\" in %s", + renamed, qPrintable(oldName), qPrintable(newName), qPrintable(modList.fileName())); +} - bool modStatusModified = false; - m_ModStatus.clear(); - m_ModStatus.resize(ModInfo::getNumMods()); - - std::set namesRead; - - // load mods from file and update enabled state and priority for them - int index = 0; - while (!file.atEnd()) { - QByteArray line = file.readLine().trimmed(); - bool enabled = true; - QString modName; - if (line.length() == 0) { - // empty line - continue; - } else if (line.at(0) == '#') { - // comment line - continue; - } else if (line.at(0) == '-') { - enabled = false; - modName = QString::fromUtf8(line.mid(1).trimmed().constData()); - } else if ((line.at(0) == '+') || (line.at(0) == '*')) { - modName = QString::fromUtf8(line.mid(1).trimmed().constData()); - } else { - modName = QString::fromUtf8(line.trimmed().constData()); - } - if (modName.size() > 0) { - QString lookupName = modName; - if (namesRead.find(lookupName) != namesRead.end()) { - continue; - } else { - namesRead.insert(lookupName); - } - unsigned int modIndex = ModInfo::getIndex(lookupName); - if (modIndex != UINT_MAX) { - ModInfo::Ptr info = ModInfo::getByIndex(modIndex); - if ((modIndex < m_ModStatus.size()) && (info->getFixedPriority() == INT_MIN)) { - m_ModStatus[modIndex].m_Enabled = enabled; - if (m_ModStatus[modIndex].m_Priority == -1) { - if (static_cast(index) >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - m_ModStatus[modIndex].m_Priority = index++; - } - } else { - qWarning("no mod state for \"%s\" (profile \"%s\")", qPrintable(modName), - m_Directory.path().toUtf8().constData()); - // need to rewrite the modlist to fix this - modStatusModified = true; - } - } else { - qDebug("mod \"%s\" (profile \"%s\") not found", qPrintable(modName), - m_Directory.path().toUtf8().constData()); - // need to rewrite the modlist to fix this - modStatusModified = true; +void Profile::refreshModStatus() +{ + 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())); + } + + bool modStatusModified = false; + m_ModStatus.clear(); + m_ModStatus.resize(ModInfo::getNumMods()); + + std::set namesRead; + + // load mods from file and update enabled state and priority for them + int index = 0; + while (!file.atEnd()) { + QByteArray line = file.readLine().trimmed(); + bool enabled = true; + QString modName; + if (line.length() == 0) { + // empty line + continue; + } else if (line.at(0) == '#') { + // comment line + continue; + } else if (line.at(0) == '-') { + enabled = false; + modName = QString::fromUtf8(line.mid(1).trimmed().constData()); + } else if ((line.at(0) == '+') + || (line.at(0) == '*')) { + modName = QString::fromUtf8(line.mid(1).trimmed().constData()); + } else { + modName = QString::fromUtf8(line.trimmed().constData()); + } + if (modName.size() > 0) { + QString lookupName = modName; + if (namesRead.find(lookupName) != namesRead.end()) { + continue; + } else { + namesRead.insert(lookupName); + } + unsigned int modIndex = ModInfo::getIndex(lookupName); + if (modIndex != UINT_MAX) { + ModInfo::Ptr info = ModInfo::getByIndex(modIndex); + if ((modIndex < m_ModStatus.size()) + && (info->getFixedPriority() == INT_MIN)) { + m_ModStatus[modIndex].m_Enabled = enabled; + if (m_ModStatus[modIndex].m_Priority == -1) { + if (static_cast(index) >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); } + m_ModStatus[modIndex].m_Priority = index++; + } + } else { + qWarning("no mod state for \"%s\" (profile \"%s\")", + qPrintable(modName), m_Directory.path().toUtf8().constData()); + // need to rewrite the modlist to fix this + modStatusModified = true; } + } else { + qDebug("mod \"%s\" (profile \"%s\") not found", + qPrintable(modName), m_Directory.path().toUtf8().constData()); + // need to rewrite the modlist to fix this + modStatusModified = true; + } } + } - int numKnownMods = index; + 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 - for (size_t i = 0; i < m_ModStatus.size(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(static_cast(i)); - if (modInfo->alwaysEnabled()) { - m_ModStatus[i].m_Enabled = true; - } - - if (modInfo->getFixedPriority() == INT_MAX) { - continue; - } - - if (m_ModStatus[i].m_Priority != -1) { - m_ModStatus[i].m_Priority = numKnownMods - m_ModStatus[i].m_Priority - 1; - } else { - if (static_cast(index) >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { - m_ModStatus[i].m_Priority = --topInsert; - } else { - m_ModStatus[i].m_Priority = index++; - } - // also, mark the mod-list as changed - modStatusModified = true; - } + // invert priority order to match that of the pluginlist. Also + // give priorities to mods not referenced in the profile + for (size_t i = 0; i < m_ModStatus.size(); ++i) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(static_cast(i)); + if (modInfo->alwaysEnabled()) { + m_ModStatus[i].m_Enabled = true; } - // to support insertion of new mods at the top we may now have mods with negative priority. 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) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(static_cast(i)); - if (modInfo->getFixedPriority() == INT_MAX) { - continue; - } - m_ModStatus[i].m_Priority += offset; - } + if (modInfo->getFixedPriority() == INT_MAX) { + continue; } - file.close(); - updateIndices(); - if (modStatusModified) { - m_ModListWriter.write(); + if (m_ModStatus[i].m_Priority != -1) { + m_ModStatus[i].m_Priority = numKnownMods - m_ModStatus[i].m_Priority - 1; + } else { + if (static_cast(index) >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + if (modInfo->hasFlag(ModInfo::FLAG_FOREIGN)) { + m_ModStatus[i].m_Priority = --topInsert; + } else { + m_ModStatus[i].m_Priority = index++; + } + // also, mark the mod-list as changed + modStatusModified = true; + } + } + // to support insertion of new mods at the top we may now have mods with negative priority. 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) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(static_cast(i)); + if (modInfo->getFixedPriority() == INT_MAX) { + continue; + } + + m_ModStatus[i].m_Priority += offset; } + } + + file.close(); + updateIndices(); + if (modStatusModified) { + m_ModListWriter.write(); + } } -void Profile::dumpModStatus() const { - for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { - ModInfo::Ptr info = ModInfo::getByIndex(i); - qWarning("%d: %s - %d (%s)", i, info->name().toUtf8().constData(), m_ModStatus[i].m_Priority, - m_ModStatus[i].m_Enabled ? "enabled" : "disabled"); - } + +void Profile::dumpModStatus() const +{ + for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { + ModInfo::Ptr info = ModInfo::getByIndex(i); + qWarning("%d: %s - %d (%s)", i, info->name().toUtf8().constData(), m_ModStatus[i].m_Priority, + m_ModStatus[i].m_Enabled ? "enabled" : "disabled"); + } } -void Profile::updateIndices() { - m_NumRegularMods = 0; - m_ModIndexByPriority.clear(); - m_ModIndexByPriority.resize(m_ModStatus.size(), UINT_MAX); - for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { - int priority = m_ModStatus[i].m_Priority; - if (priority < 0) { - // don't assign this to mapping at all, it's probably the overwrite mod - continue; - } else if (priority >= static_cast(m_ModIndexByPriority.size())) { - qCritical("invalid priority %d for mod", priority); - continue; - } else { - ++m_NumRegularMods; - m_ModIndexByPriority.at(priority) = i; - } + +void Profile::updateIndices() +{ + m_NumRegularMods = 0; + m_ModIndexByPriority.clear(); + m_ModIndexByPriority.resize(m_ModStatus.size(), UINT_MAX); + for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { + int priority = m_ModStatus[i].m_Priority; + if (priority < 0) { + // don't assign this to mapping at all, it's probably the overwrite mod + continue; + } else if (priority >= static_cast(m_ModIndexByPriority.size())) { + qCritical("invalid priority %d for mod", priority); + continue; + } else { + ++m_NumRegularMods; + m_ModIndexByPriority.at(priority) = i; } + } } -std::vector> Profile::getActiveMods() { - std::vector> result; - for (std::vector::const_iterator iter = m_ModIndexByPriority.begin(); - iter != m_ModIndexByPriority.end(); ++iter) { - if ((*iter != UINT_MAX) && m_ModStatus[*iter].m_Enabled) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(*iter); - result.push_back( - std::make_tuple(modInfo->internalName(), modInfo->absolutePath(), m_ModStatus[*iter].m_Priority)); - } + +std::vector > Profile::getActiveMods() +{ + std::vector > result; + for (std::vector::const_iterator iter = m_ModIndexByPriority.begin(); + iter != m_ModIndexByPriority.end(); ++iter) { + if ((*iter != UINT_MAX) && m_ModStatus[*iter].m_Enabled) { + ModInfo::Ptr modInfo = ModInfo::getByIndex(*iter); + result.push_back(std::make_tuple(modInfo->internalName(), modInfo->absolutePath(), m_ModStatus[*iter].m_Priority)); } + } - unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { - std::vector flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); - }); + unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { + std::vector flags = mod->getFlags(); + return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); - if (overwriteIndex != UINT_MAX) { - ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(overwriteIndex); - result.push_back(std::make_tuple(overwriteInfo->name(), overwriteInfo->absolutePath(), UINT_MAX)); - } else { - reportError(tr("Overwrite directory couldn't be parsed")); - } - return result; + if (overwriteIndex != UINT_MAX) { + ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(overwriteIndex); + result.push_back(std::make_tuple(overwriteInfo->name(), overwriteInfo->absolutePath(), UINT_MAX)); + } else { + reportError(tr("Overwrite directory couldn't be parsed")); + } + return result; } -unsigned int Profile::modIndexByPriority(unsigned int priority) const { - if (priority >= m_ModStatus.size()) { - throw MyException(tr("invalid priority %1").arg(priority)); - } - return m_ModIndexByPriority[priority]; +unsigned int Profile::modIndexByPriority(unsigned int priority) const +{ + if (priority >= m_ModStatus.size()) { + throw MyException(tr("invalid priority %1").arg(priority)); + } + + return m_ModIndexByPriority[priority]; } -void Profile::setModEnabled(unsigned int index, bool enabled) { - if (index >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - // we could quit in the following case, this shouldn't be a change anyway, - // but at least this allows the situation to be fixed in case of an error - if (modInfo->alwaysEnabled()) { - enabled = true; - } +void Profile::setModEnabled(unsigned int index, bool enabled) +{ + if (index >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } - if (enabled != m_ModStatus[index].m_Enabled) { - m_ModStatus[index].m_Enabled = enabled; - emit modStatusChanged(index); - } + ModInfo::Ptr modInfo = ModInfo::getByIndex(index); + // we could quit in the following case, this shouldn't be a change anyway, + // but at least this allows the situation to be fixed in case of an error + if (modInfo->alwaysEnabled()) { + enabled = true; + } + + if (enabled != m_ModStatus[index].m_Enabled) { + m_ModStatus[index].m_Enabled = enabled; + emit modStatusChanged(index); + } } -bool Profile::modEnabled(unsigned int index) const { - if (index >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } +bool Profile::modEnabled(unsigned int index) const +{ + if (index >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } - return m_ModStatus[index].m_Enabled; + return m_ModStatus[index].m_Enabled; } -int Profile::getModPriority(unsigned int index) const { - if (index >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - return m_ModStatus[index].m_Priority; +int Profile::getModPriority(unsigned int index) const +{ + if (index >= m_ModStatus.size()) { + throw MyException(tr("invalid index %1").arg(index)); + } + + return m_ModStatus[index].m_Priority; } -void Profile::setModPriority(unsigned int index, int& newPriority) { - if (m_ModStatus.at(index).m_Overwrite) { - // can't change priority of the overwrite - return; - } - int newPriorityTemp = (std::max)(0, (std::min)(static_cast(m_ModStatus.size()) - 1, newPriority)); +void Profile::setModPriority(unsigned int index, int &newPriority) +{ + if (m_ModStatus.at(index).m_Overwrite) { + // can't change priority of the overwrite + return; + } - // don't try to place below overwrite - while ((m_ModIndexByPriority.at(newPriorityTemp) >= m_ModStatus.size()) || - m_ModStatus.at(m_ModIndexByPriority.at(newPriorityTemp)).m_Overwrite) { - --newPriorityTemp; - } + int newPriorityTemp = + (std::max)(0, (std::min)(static_cast(m_ModStatus.size()) - 1, + newPriority)); - int oldPriority = m_ModStatus.at(index).m_Priority; - if (newPriorityTemp > oldPriority) { - // priority is higher than the old, so the gap we left is in lower priorities - for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) { - --m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; - } - } else { - for (int i = newPriorityTemp; i < oldPriority; ++i) { - ++m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; - } - ++newPriority; + // don't try to place below overwrite + while ((m_ModIndexByPriority.at(newPriorityTemp) >= m_ModStatus.size()) || + m_ModStatus.at(m_ModIndexByPriority.at(newPriorityTemp)).m_Overwrite) { + --newPriorityTemp; + } + + int oldPriority = m_ModStatus.at(index).m_Priority; + if (newPriorityTemp > oldPriority) { + // priority is higher than the old, so the gap we left is in lower priorities + for (int i = oldPriority + 1; i <= newPriorityTemp; ++i) { + --m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; + } + } else { + for (int i = newPriorityTemp; i < oldPriority; ++i) { + ++m_ModStatus.at(m_ModIndexByPriority.at(i)).m_Priority; } + ++newPriority; + } - m_ModStatus.at(index).m_Priority = newPriorityTemp; + m_ModStatus.at(index).m_Priority = newPriorityTemp; - updateIndices(); - m_ModListWriter.write(); + updateIndices(); + m_ModListWriter.write(); } -Profile* Profile::createPtrFrom(const QString& name, const Profile& reference, MOBase::IPluginGame const* gamePlugin) { - QString profileDirectory = Settings::instance().getProfileDirectory() + "/" + name; - reference.copyFilesTo(profileDirectory); - return new Profile(QDir(profileDirectory), gamePlugin); +Profile *Profile::createPtrFrom(const QString &name, const Profile &reference, MOBase::IPluginGame const *gamePlugin) +{ + QString profileDirectory = Settings::instance().getProfileDirectory() + "/" + name; + reference.copyFilesTo(profileDirectory); + return new Profile(QDir(profileDirectory), gamePlugin); } -void Profile::copyFilesTo(QString& target) const { copyDir(m_Directory.absolutePath(), target, false); } - -std::vector Profile::splitDZString(const wchar_t* buffer) const { - std::vector result; - const wchar_t* pos = buffer; - size_t length = wcslen(pos); - while (length != 0U) { - result.push_back(pos); - pos += length + 1; - length = wcslen(pos); - } - return result; +void Profile::copyFilesTo(QString &target) const +{ + copyDir(m_Directory.absolutePath(), target, false); } -void Profile::mergeTweak(const QString& tweakName, const QString& tweakedIni) const { - static const int bufferSize = 32768; - - std::wstring tweakNameW = ToWString(tweakName); - std::wstring tweakedIniW = ToWString(tweakedIni); - QScopedArrayPointer buffer(new wchar_t[bufferSize]); - - // retrieve a list of sections - DWORD size = ::GetPrivateProfileSectionNamesW(buffer.data(), bufferSize, tweakNameW.c_str()); - +std::vector Profile::splitDZString(const wchar_t *buffer) const +{ + std::vector result; + const wchar_t *pos = buffer; + size_t length = wcslen(pos); + while (length != 0U) { + result.push_back(pos); + pos += length + 1; + length = wcslen(pos); + } + return result; +} + +void Profile::mergeTweak(const QString &tweakName, const QString &tweakedIni) const +{ + static const int bufferSize = 32768; + + std::wstring tweakNameW = ToWString(tweakName); + std::wstring tweakedIniW = ToWString(tweakedIni); + QScopedArrayPointer buffer(new wchar_t[bufferSize]); + + // retrieve a list of sections + 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)); + } + + std::vector sections = splitDZString(buffer.data()); + + // now iterate over all sections and retrieve a list of keys in each + for (std::vector::iterator iter = sections.begin(); + iter != sections.end(); ++iter) { + // retrieve the names of all keys + size = ::GetPrivateProfileStringW(iter->c_str(), nullptr, nullptr, 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)); + throw MyException(QString("Buffer too small. Please report this as a bug. " + "For now you might want to split up %1").arg(tweakName)); } - std::vector sections = splitDZString(buffer.data()); - - // now iterate over all sections and retrieve a list of keys in each - for (std::vector::iterator iter = sections.begin(); iter != sections.end(); ++iter) { - // retrieve the names of all keys - size = - ::GetPrivateProfileStringW(iter->c_str(), nullptr, nullptr, buffer.data(), 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)); - } - - std::vector keys = splitDZString(buffer.data()); + 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()); - ::WritePrivateProfileStringW(iter->c_str(), keyIter->c_str(), buffer.data(), tweakedIniW.c_str()); - } + 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()); + ::WritePrivateProfileStringW(iter->c_str(), keyIter->c_str(), + buffer.data(), tweakedIniW.c_str()); } + } } -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) { - mergeTweak(*iter, tweakedIni); - } +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) { + mergeTweak(*iter, tweakedIni); + } } -bool Profile::invalidationActive(bool* supported) const { - BSAInvalidation* invalidation = m_GamePlugin->feature(); - DataArchives* dataArchives = m_GamePlugin->feature(); - if ((invalidation != nullptr) && (dataArchives != nullptr)) { - if (supported != nullptr) { - *supported = true; - } +bool Profile::invalidationActive(bool *supported) const +{ + BSAInvalidation *invalidation = m_GamePlugin->feature(); + DataArchives *dataArchives = m_GamePlugin->feature(); - for (const QString& archive : dataArchives->archives(this)) { - if (invalidation->isInvalidationBSA(archive)) { - return true; - } - } - return false; - } else { - if (supported != nullptr) { - *supported = false; - } + if ((invalidation != nullptr) && (dataArchives != nullptr)) { + if (supported != nullptr) { + *supported = true; + } + + for (const QString &archive : dataArchives->archives(this)) { + if (invalidation->isInvalidationBSA(archive)) { + return true; + } } return false; + } else { + if (supported != nullptr) { + *supported = false; + } + } + return false; } -void Profile::deactivateInvalidation() { - BSAInvalidation* invalidation = m_GamePlugin->feature(); - if (invalidation != nullptr) { - invalidation->deactivate(this); - } +void Profile::deactivateInvalidation() +{ + BSAInvalidation *invalidation = m_GamePlugin->feature(); + + if (invalidation != nullptr) { + invalidation->deactivate(this); + } } -void Profile::activateInvalidation() { - BSAInvalidation* invalidation = m_GamePlugin->feature(); - if (invalidation != nullptr) { - invalidation->activate(this); - } +void Profile::activateInvalidation() +{ + BSAInvalidation *invalidation = m_GamePlugin->feature(); + + if (invalidation != nullptr) { + invalidation->activate(this); + } } -bool Profile::localSavesEnabled() const { return m_Directory.exists("saves"); } -bool Profile::enableLocalSaves(bool enable) { - if (enable) { - if (m_Directory.exists("_saves")) { - m_Directory.rename("_saves", "saves"); - } else { - m_Directory.mkdir("saves"); - } +bool Profile::localSavesEnabled() const +{ + return m_Directory.exists("saves"); +} + + +bool Profile::enableLocalSaves(bool enable) +{ + if (enable) { + if (m_Directory.exists("_saves")) { + m_Directory.rename("_saves", "saves"); } else { - QMessageBox::StandardButton res = - QMessageBox::question(QApplication::activeModalWidget(), tr("Delete savegames?"), - tr("Do you want to delete local savegames? (If you select \"No\", the " - "save games will show up again if you re-enable local savegames)"), - QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, QMessageBox::Cancel); - if (res == QMessageBox::Yes) { - shellDelete(QStringList(m_Directory.absoluteFilePath("saves"))); - } else if (res == QMessageBox::No) { - m_Directory.rename("saves", "_saves"); - } else { - return false; - } + m_Directory.mkdir("saves"); + } + } else { + QMessageBox::StandardButton res = QMessageBox::question( + QApplication::activeModalWidget(), tr("Delete savegames?"), + tr("Do you want to delete local savegames? (If you select \"No\", the " + "save games will show up again if you re-enable local savegames)"), + QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel, + QMessageBox::Cancel); + if (res == QMessageBox::Yes) { + shellDelete(QStringList(m_Directory.absoluteFilePath("saves"))); + } else if (res == QMessageBox::No) { + m_Directory.rename("saves", "_saves"); + } else { + return false; } + } - // default: assume success - return true; + // default: assume success + return true; } -bool Profile::localSettingsEnabled() const { return m_Directory.exists(getIniFileName()); } +bool Profile::localSettingsEnabled() const +{ + return m_Directory.exists(getIniFileName()); +} -bool Profile::enableLocalSettings(bool enable) { - // TODO: this currently assumes game settings are stored in an ini file. - // This shall become very interesting when a game stores its settings in the - // registry - QString backupFile = getIniFileName() + "_"; - if (enable) { - if (m_Directory.exists(backupFile)) { - shellRename(backupFile, getIniFileName()); - } else { - IPluginGame* game = qApp->property("managed_game").value(); - game->initializeProfile(m_Directory, IPluginGame::CONFIGURATION); - } +bool Profile::enableLocalSettings(bool enable) +{ + // TODO: this currently assumes game settings are stored in an ini file. + // This shall become very interesting when a game stores its settings in the + // registry + QString backupFile = getIniFileName() + "_"; + if (enable) { + if (m_Directory.exists(backupFile)) { + shellRename(backupFile, getIniFileName()); } else { - shellRename(getIniFileName(), backupFile); + IPluginGame *game = qApp->property("managed_game").value(); + game->initializeProfile(m_Directory, IPluginGame::CONFIGURATION); } + } else { + shellRename(getIniFileName(), backupFile); + } + + return true; +} - return true; +QString Profile::getModlistFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("modlist.txt")); } -QString Profile::getModlistFileName() const { return QDir::cleanPath(m_Directory.absoluteFilePath("modlist.txt")); } +QString Profile::getPluginsFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("plugins.txt")); +} -QString Profile::getPluginsFileName() const { return QDir::cleanPath(m_Directory.absoluteFilePath("plugins.txt")); } +QString Profile::getLoadOrderFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("loadorder.txt")); +} -QString Profile::getLoadOrderFileName() const { return QDir::cleanPath(m_Directory.absoluteFilePath("loadorder.txt")); } +QString Profile::getLockedOrderFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("lockedorder.txt")); +} -QString Profile::getLockedOrderFileName() const { - return QDir::cleanPath(m_Directory.absoluteFilePath("lockedorder.txt")); +QString Profile::getArchivesFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("archives.txt")); } -QString Profile::getArchivesFileName() const { return QDir::cleanPath(m_Directory.absoluteFilePath("archives.txt")); } +QString Profile::getDeleterFileName() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("hide_plugins.txt")); +} -QString Profile::getDeleterFileName() const { - return QDir::cleanPath(m_Directory.absoluteFilePath("hide_plugins.txt")); +QString Profile::getIniFileName() const +{ + return m_Directory.absoluteFilePath(m_GamePlugin->iniFiles()[0]); } -QString Profile::getIniFileName() const { return m_Directory.absoluteFilePath(m_GamePlugin->iniFiles()[0]); } +QString Profile::getProfileTweaks() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath(ToQString(AppConfig::profileTweakIni()))); +} -QString Profile::getProfileTweaks() const { - return QDir::cleanPath(m_Directory.absoluteFilePath(ToQString(AppConfig::profileTweakIni()))); +QString Profile::absolutePath() const +{ + return QDir::cleanPath(m_Directory.absolutePath()); } -QString Profile::absolutePath() const { return QDir::cleanPath(m_Directory.absolutePath()); } +QString Profile::savePath() const +{ + return QDir::cleanPath(m_Directory.absoluteFilePath("saves")); -QString Profile::savePath() const { return QDir::cleanPath(m_Directory.absoluteFilePath("saves")); } +} -void Profile::rename(const QString& newName) { - QDir profileDir(Settings::instance().getProfileDirectory()); - profileDir.rename(name(), newName); - m_Directory = profileDir.absoluteFilePath(newName); +void Profile::rename(const QString &newName) +{ + QDir profileDir(Settings::instance().getProfileDirectory()); + profileDir.rename(name(), newName); + m_Directory = profileDir.absoluteFilePath(newName); } -QVariant Profile::setting(const QString& section, const QString& name, const QVariant& fallback) { - return m_Settings->value(section + "/" + name, fallback); +QVariant Profile::setting(const QString §ion, const QString &name, + const QVariant &fallback) +{ + return m_Settings->value(section + "/" + name, fallback); } -void Profile::storeSetting(const QString& section, const QString& name, const QVariant& value) { - m_Settings->setValue(section + "/" + name, value); +void Profile::storeSetting(const QString §ion, const QString &name, + const QVariant &value) +{ + m_Settings->setValue(section + "/" + name, value); } -void Profile::removeSetting(const QString& section, const QString& name) { m_Settings->remove(section + "/" + name); } \ No newline at end of file +void Profile::removeSetting(const QString §ion, const QString &name) +{ + m_Settings->remove(section + "/" + name); +} \ No newline at end of file diff --git a/src/profile.h b/src/profile.h index c1cd3e9f..1fcad046 100644 --- a/src/profile.h +++ b/src/profile.h @@ -20,15 +20,16 @@ along with Mod Organizer. If not, see . #ifndef PROFILE_H #define PROFILE_H + #include "modinfo.h" -#include #include +#include #include #include #include -#include #include +#include #include @@ -36,316 +37,322 @@ along with Mod Organizer. If not, see . #include #include -namespace MOBase { -class IPluginGame; -} + +namespace MOBase { class IPluginGame; } /** * @brief represents a profile **/ -class Profile : public QObject, public MOBase::IProfile { +class Profile : public QObject, public MOBase::IProfile +{ - Q_OBJECT + Q_OBJECT public: - typedef boost::shared_ptr Ptr; + + typedef boost::shared_ptr Ptr; public: - /** - * @brief constructor - * - * This constructor is used to create a new profile so it is to be assumed a profile - * by this name does not yet exist - * @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); - - /** - * @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 - * @param directory directory to read the profile from - **/ - Profile(const QDir& directory, MOBase::IPluginGame const* gamePlugin); - - Profile(const Profile& reference); - - ~Profile(); - - /** - * @return true if this profile (still) exists on disc - */ - bool exists() const; - - /** - * @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 void renameModInAllProfiles(const QString& oldName, const QString& newName); - - void writeModlist(); - - 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 - * @return true if archive invalidation is active - * @note currently, invalidation is not supported if the relevant entry in the ini file does not exist - **/ - bool invalidationActive(bool* supported) const; - - /** - * @brief deactivate archive invalidation if it was active - **/ - void deactivateInvalidation(); - - /** - * @brief activate archive invalidation - **/ - void activateInvalidation(); - - /** - * @return true if this profile uses local save games - */ - virtual bool localSavesEnabled() const override; - - /** - * @brief enables or disables the use of local save games for this profile - * when disabling the user will be asked if he wants to remove the save games - * in the profile - * @param enable if true, local saves are enabled, otherewise they are disabled - */ - bool enableLocalSaves(bool enable); - - /** - * @return true if this profile uses local ini files - */ - virtual bool localSettingsEnabled() const override; - - /** - * @brief enables or disables the use of local ini files for this profile - * disabling this does not delete existing ini files but the global ones will be used - * @param enable - */ - bool enableLocalSettings(bool enable); - - /** - * @return name of the profile (this is identical to its directory name) - **/ - virtual QString name() const override { return m_Directory.dirName(); } - - /** - * @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? - **/ - QString getPluginsFileName() const; - - /** - * @return the path of the loadorder file in this profile - **/ - QString getLoadOrderFileName() const; - - /** - * @return the path of the file containing locked mod indices - */ - QString getLockedOrderFileName() const; - - /** - * @return the path of the modlist file in this profile - */ - QString getModlistFileName() const; - - /** - * @return path of the archives file in this profile - */ - QString getArchivesFileName() const; - - /** - * @return the path of the delete file in this profile - * @note the deleter file lists plugins that should be hidden from the game and other tools - **/ - QString getDeleterFileName() const; - - /** - * @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 - **/ - QString getIniFileName() const; - - /** - * @return the path of the tweak ini in this profile - */ - QString getProfileTweaks() const; - - /** - * @return path to this profile - **/ - virtual QString absolutePath() const override; - - /** - * @return path to this profile's save games - **/ - QString savePath() const; - - /** - * @brief rename profile - * @param newName new name of profile - */ - void rename(const QString& newName); - - /** - * @brief create the ini file to be used by the game - * - * the tweaked ini file constructed by this file is a merger - * of the game-ini of this profile with ini tweaks applied */ - void createTweakedIniFile(); - - /** - * @brief re-read the modlist.txt and update the mod status from it - **/ - void refreshModStatus(); - - /** - * @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 - **/ - std::vector> getActiveMods(); - - /** - * retrieve the number of mods for which this object has status information. - * This is usually the same as ModInfo::getNumMods() except between - * calls to ModInfo::updateFromDisc() and the Profile::refreshModStatus() - * - * @return number of mods for which the profile has status information - **/ - size_t numMods() const { return m_ModStatus.size(); } - - /** - * @return the number of mods that can be enabled and where the priority can be modified - */ - unsigned int numRegularMods() const { return m_NumRegularMods; } - - /** - * @brief retrieve the mod index based on the priority - * - * @param priority priority to look up - * @return the index of the mod - * @throw std::exception an exception is thrown if there is no mod with the specified priority - **/ - unsigned int modIndexByPriority(unsigned int priority) const; - - /** - * @brief enable or disable a mod - * - * @param index index of the mod to enable/disable - * @param enabled true if the mod is to be enabled, false if it is to be disabled - **/ - void setModEnabled(unsigned int index, bool enabled); - - /** - * change the priority of a mod. Of course this also changes the priority of other mods. - * The priority of the mods in the range ]old, new priority] are shifted so that no gaps - * are possible. - * - * @param index index of the mod to change - * @param newPriority the new priority value - * - * @todo what happens if the new priority is outside the range? - **/ - void setModPriority(unsigned int index, int& newPriority); - - /** - * @brief determine if a mod is enabled - * - * @param index index of the mod to look up - * @return true if the mod is enabled, false otherwise - **/ - bool modEnabled(unsigned int index) const; - - /** - * @brief query the priority of a mod - * - * @param index index of the mod to look up - * @return priority of the specified mod - **/ - int getModPriority(unsigned int index) const; - - void dumpModStatus() const; - - QVariant setting(const QString& section, const QString& name, const QVariant& fallback = QVariant()); - - void storeSetting(const QString& section, const QString& name, const QVariant& value); - void removeSetting(const QString& section, const QString& name); + + /** + * @brief constructor + * + * This constructor is used to create a new profile so it is to be assumed a profile + * by this name does not yet exist + * @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); + + /** + * @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 + * @param directory directory to read the profile from + **/ + Profile(const QDir &directory, MOBase::IPluginGame const *gamePlugin); + + Profile(const Profile &reference); + + ~Profile(); + + /** + * @return true if this profile (still) exists on disc + */ + bool exists() const; + + /** + * @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 void renameModInAllProfiles(const QString& oldName, const QString& newName); + + void writeModlist(); + + 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 + * @return true if archive invalidation is active + * @note currently, invalidation is not supported if the relevant entry in the ini file does not exist + **/ + bool invalidationActive(bool *supported) const; + + /** + * @brief deactivate archive invalidation if it was active + **/ + void deactivateInvalidation(); + + /** + * @brief activate archive invalidation + **/ + void activateInvalidation(); + + /** + * @return true if this profile uses local save games + */ + virtual bool localSavesEnabled() const override; + + /** + * @brief enables or disables the use of local save games for this profile + * when disabling the user will be asked if he wants to remove the save games + * in the profile + * @param enable if true, local saves are enabled, otherewise they are disabled + */ + bool enableLocalSaves(bool enable); + + /** + * @return true if this profile uses local ini files + */ + virtual bool localSettingsEnabled() const override; + + /** + * @brief enables or disables the use of local ini files for this profile + * disabling this does not delete existing ini files but the global ones will be used + * @param enable + */ + bool enableLocalSettings(bool enable); + + /** + * @return name of the profile (this is identical to its directory name) + **/ + virtual QString name() const override { return m_Directory.dirName(); } + + /** + * @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? + **/ + QString getPluginsFileName() const; + + /** + * @return the path of the loadorder file in this profile + **/ + QString getLoadOrderFileName() const; + + /** + * @return the path of the file containing locked mod indices + */ + QString getLockedOrderFileName() const; + + /** + * @return the path of the modlist file in this profile + */ + QString getModlistFileName() const; + + /** + * @return path of the archives file in this profile + */ + QString getArchivesFileName() const; + + /** + * @return the path of the delete file in this profile + * @note the deleter file lists plugins that should be hidden from the game and other tools + **/ + QString getDeleterFileName() const; + + /** + * @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 + **/ + QString getIniFileName() const; + + /** + * @return the path of the tweak ini in this profile + */ + QString getProfileTweaks() const; + + /** + * @return path to this profile + **/ + virtual QString absolutePath() const override; + + /** + * @return path to this profile's save games + **/ + QString savePath() const; + + /** + * @brief rename profile + * @param newName new name of profile + */ + void rename(const QString &newName); + + /** + * @brief create the ini file to be used by the game + * + * the tweaked ini file constructed by this file is a merger + * of the game-ini of this profile with ini tweaks applied */ + void createTweakedIniFile(); + + /** + * @brief re-read the modlist.txt and update the mod status from it + **/ + void refreshModStatus(); + + /** + * @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 + **/ + std::vector > getActiveMods(); + + /** + * retrieve the number of mods for which this object has status information. + * This is usually the same as ModInfo::getNumMods() except between + * calls to ModInfo::updateFromDisc() and the Profile::refreshModStatus() + * + * @return number of mods for which the profile has status information + **/ + size_t numMods() const { return m_ModStatus.size(); } + + /** + * @return the number of mods that can be enabled and where the priority can be modified + */ + unsigned int numRegularMods() const { return m_NumRegularMods; } + + /** + * @brief retrieve the mod index based on the priority + * + * @param priority priority to look up + * @return the index of the mod + * @throw std::exception an exception is thrown if there is no mod with the specified priority + **/ + unsigned int modIndexByPriority(unsigned int priority) const; + + /** + * @brief enable or disable a mod + * + * @param index index of the mod to enable/disable + * @param enabled true if the mod is to be enabled, false if it is to be disabled + **/ + void setModEnabled(unsigned int index, bool enabled); + + /** + * change the priority of a mod. Of course this also changes the priority of other mods. + * The priority of the mods in the range ]old, new priority] are shifted so that no gaps + * are possible. + * + * @param index index of the mod to change + * @param newPriority the new priority value + * + * @todo what happens if the new priority is outside the range? + **/ + void setModPriority(unsigned int index, int &newPriority); + + /** + * @brief determine if a mod is enabled + * + * @param index index of the mod to look up + * @return true if the mod is enabled, false otherwise + **/ + bool modEnabled(unsigned int index) const; + + /** + * @brief query the priority of a mod + * + * @param index index of the mod to look up + * @return priority of the specified mod + **/ + int getModPriority(unsigned int index) const; + + void dumpModStatus() const; + + QVariant setting(const QString §ion, const QString &name, + const QVariant &fallback = QVariant()); + + void storeSetting(const QString §ion, const QString &name, + const QVariant &value); + void removeSetting(const QString §ion, const QString &name); signals: - /** - * @brief emitted whenever the status (enabled/disabled) of a mod changed - * - * @param index index of the mod that changed - **/ - void modStatusChanged(unsigned int index); + /** + * @brief emitted whenever the status (enabled/disabled) of a mod changed + * + * @param index index of the mod that changed + **/ + void modStatusChanged(unsigned int index); public slots: - // should only be called by DelayedFileWriter, use writeModlist() and writeModlistNow() instead - void doWriteModlist(); + // should only be called by DelayedFileWriter, use writeModlist() and writeModlistNow() instead + void doWriteModlist(); private: - class ModStatus { - friend class Profile; - - public: - ModStatus() : m_Overwrite(false), m_Enabled(false), m_Priority(-1) {} - private: - bool m_Overwrite; - bool m_Enabled; - int m_Priority; - }; + class ModStatus { + friend class Profile; + public: + ModStatus() : m_Overwrite(false), m_Enabled(false), m_Priority(-1) {} + private: + bool m_Overwrite; + bool m_Enabled; + int m_Priority; + }; private: - Profile& operator=(const Profile& reference); // not implemented + Profile& operator=(const Profile &reference); // not implemented - void initTimer(); + void initTimer(); - void updateIndices(); + 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; - void touchFile(QString fileName); - void finishChangeStatus() 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); + void finishChangeStatus() const; - 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; + QDir m_Directory; - const MOBase::IPluginGame* m_GamePlugin; + QSettings *m_Settings; - mutable QByteArray m_LastModlistHash; - std::vector m_ModStatus; - std::vector m_ModIndexByPriority; - unsigned int m_NumRegularMods; + const MOBase::IPluginGame *m_GamePlugin; + + mutable QByteArray m_LastModlistHash; + std::vector m_ModStatus; + std::vector m_ModIndexByPriority; + unsigned int m_NumRegularMods; + + MOBase::DelayedFileWriter m_ModListWriter; - MOBase::DelayedFileWriter m_ModListWriter; }; + #endif // PROFILE_H diff --git a/src/profileinputdialog.cpp b/src/profileinputdialog.cpp index 242f6356..5a1fd03c 100644 --- a/src/profileinputdialog.cpp +++ b/src/profileinputdialog.cpp @@ -21,18 +21,28 @@ along with Mod Organizer. If not, see . #include "ui_profileinputdialog.h" #include -ProfileInputDialog::ProfileInputDialog(QWidget* parent) : QDialog(parent), ui(new Ui::ProfileInputDialog) { - ui->setupUi(this); +ProfileInputDialog::ProfileInputDialog(QWidget *parent) : + QDialog(parent), + ui(new Ui::ProfileInputDialog) +{ + ui->setupUi(this); } -ProfileInputDialog::~ProfileInputDialog() { delete ui; } +ProfileInputDialog::~ProfileInputDialog() +{ + delete ui; +} -QString ProfileInputDialog::getName() const { - QString result = ui->nameEdit->text(); - MOBase::fixDirectoryName(result); - return result; +QString ProfileInputDialog::getName() const +{ + QString result = ui->nameEdit->text(); + MOBase::fixDirectoryName(result); + return result; } -bool ProfileInputDialog::getPreferDefaultSettings() const { - return ui->defaultSettingsBox->checkState() == Qt::Checked; + +bool ProfileInputDialog::getPreferDefaultSettings() const +{ + return ui->defaultSettingsBox->checkState() == Qt::Checked; } + diff --git a/src/profileinputdialog.h b/src/profileinputdialog.h index b82696ea..8a132666 100644 --- a/src/profileinputdialog.h +++ b/src/profileinputdialog.h @@ -26,18 +26,19 @@ namespace Ui { class ProfileInputDialog; } -class ProfileInputDialog : public QDialog { - Q_OBJECT - +class ProfileInputDialog : public QDialog +{ + Q_OBJECT + public: - explicit ProfileInputDialog(QWidget* parent = 0); - ~ProfileInputDialog(); - - QString getName() const; - bool getPreferDefaultSettings() const; + explicit ProfileInputDialog(QWidget *parent = 0); + ~ProfileInputDialog(); + QString getName() const; + bool getPreferDefaultSettings() const; + private: - Ui::ProfileInputDialog* ui; + Ui::ProfileInputDialog *ui; }; #endif // PROFILEINPUTDIALOG_H diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index 9470f101..04c1876d 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -26,9 +26,9 @@ along with Mod Organizer. If not, see . #include "profile.h" #include "profileinputdialog.h" #include "report.h" -#include "settings.h" #include "transfersavesdialog.h" #include "utility.h" +#include "settings.h" #include #include @@ -47,267 +47,291 @@ using namespace MOShared; Q_DECLARE_METATYPE(Profile::Ptr) -ProfilesDialog::ProfilesDialog(const QString& profileName, MOBase::IPluginGame const* game, QWidget* parent) - : TutorableDialog("Profiles", parent), ui(new Ui::ProfilesDialog), m_FailState(false), m_Game(game) { - ui->setupUi(this); - QDir profilesDir(Settings::instance().getProfileDirectory()); - profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); +ProfilesDialog::ProfilesDialog(const QString &profileName, MOBase::IPluginGame const *game, QWidget *parent) + : TutorableDialog("Profiles", parent) + , ui(new Ui::ProfilesDialog) + , m_FailState(false) + , m_Game(game) +{ + ui->setupUi(this); + + QDir profilesDir(Settings::instance().getProfileDirectory()); + profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); - QDirIterator profileIter(profilesDir); + QDirIterator profileIter(profilesDir); - while (profileIter.hasNext()) { - profileIter.next(); - QListWidgetItem* item = addItem(profileIter.filePath()); - if (profileName == profileIter.fileName()) { - ui->profilesList->setCurrentItem(item); - } + while (profileIter.hasNext()) { + profileIter.next(); + QListWidgetItem *item = addItem(profileIter.filePath()); + if (profileName == profileIter.fileName()) { + ui->profilesList->setCurrentItem(item); } + } - BSAInvalidation* invalidation = game->feature(); + BSAInvalidation *invalidation = game->feature(); - if (invalidation == nullptr) { - ui->invalidationBox->setToolTip(tr("Archive invalidation isn't required for this game.")); - ui->invalidationBox->setEnabled(false); - } + if (invalidation == nullptr) { + ui->invalidationBox->setToolTip(tr("Archive invalidation isn't required for this game.")); + ui->invalidationBox->setEnabled(false); + } } -ProfilesDialog::~ProfilesDialog() { delete ui; } +ProfilesDialog::~ProfilesDialog() +{ + delete ui; +} -void ProfilesDialog::showEvent(QShowEvent* event) { - TutorableDialog::showEvent(event); +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); + } +} - 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_closeButton_clicked() +{ + close(); } -void ProfilesDialog::on_closeButton_clicked() { close(); } -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; +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); - newItem->setData(Qt::UserRole, - QVariant::fromValue(Profile::Ptr(new Profile(name, m_Game, useDefaultSettings)))); - ui->profilesList->addItem(newItem); - m_FailState = false; - } catch (const std::exception&) { - m_FailState = true; - throw; - } +void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings) +{ + try { + QListWidgetItem *newItem = new QListWidgetItem(name, ui->profilesList); + newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(name, m_Game, useDefaultSettings)))); + ui->profilesList->addItem(newItem); + m_FailState = false; + } 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); - newItem->setData(Qt::UserRole, - QVariant::fromValue(Profile::Ptr(Profile::createPtrFrom(name, reference, m_Game)))); - ui->profilesList->addItem(newItem); - m_FailState = false; - } 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); + newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(Profile::createPtrFrom(name, reference, m_Game)))); + ui->profilesList->addItem(newItem); + m_FailState = false; + } 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_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_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() { - QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), - tr("Are you sure you want to remove this profile (including local savegames if any)?"), - QMessageBox::Yes | QMessageBox::No); - - if (confirmBox.exec() == QMessageBox::Yes) { - Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value(); - QString profilePath; - if (currentProfile.get() == nullptr) { - profilePath = Settings::instance().getProfileDirectory() + "/" + 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 = currentProfile->absolutePath(); - } - QListWidgetItem* item = ui->profilesList->takeItem(ui->profilesList->currentRow()); - if (item != nullptr) { - delete item; - } - if (!shellDelete(QStringList(profilePath))) { - qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qPrintable(profilePath), - ::GetLastError()); - if (!removeDir(profilePath)) { - qWarning("regular delete failed too"); - } - } +void ProfilesDialog::on_removeProfileButton_clicked() +{ + QMessageBox confirmBox(QMessageBox::Question, tr("Confirm"), tr("Are you sure you want to remove this profile (including local savegames if any)?"), + QMessageBox::Yes | QMessageBox::No); + + if (confirmBox.exec() == QMessageBox::Yes) { + Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value(); + QString profilePath; + if (currentProfile.get() == nullptr) { + profilePath = Settings::instance().getProfileDirectory() + + "/" + 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 = currentProfile->absolutePath(); } + QListWidgetItem* item = ui->profilesList->takeItem(ui->profilesList->currentRow()); + if (item != nullptr) { + delete item; + } + if (!shellDelete(QStringList(profilePath))) { + qWarning("Failed to shell-delete \"%s\" (errorcode %lu), trying regular delete", qPrintable(profilePath), ::GetLastError()); + if (!removeDir(profilePath)) { + qWarning("regular delete failed too"); + } + } + } } -void ProfilesDialog::on_renameButton_clicked() { - Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value(); - 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; - } +void ProfilesDialog::on_renameButton_clicked() +{ + Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value(); + + 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); - currentProfile->rename(name); + ui->profilesList->currentItem()->setText(name); + currentProfile->rename(name); } -void ProfilesDialog::on_invalidationBox_stateChanged(int state) { - QListWidgetItem* currentItem = ui->profilesList->currentItem(); - if (currentItem == nullptr) { - return; - } - if (!ui->invalidationBox->isEnabled()) { - return; + +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; } - 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())); + 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_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_localSavesBox_stateChanged(int state) { - Profile::Ptr currentProfile = ui->profilesList->currentItem()->data(Qt::UserRole).value(); +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); - } + 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_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(); +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); - } + 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 ac8e2b76..cab25f6a 100644 --- a/src/profilesdialog.h +++ b/src/profilesdialog.h @@ -28,72 +28,73 @@ class QListWidgetItem; #include class QString; -namespace Ui { -class ProfilesDialog; -} +namespace Ui { class ProfilesDialog; } + +namespace MOBase { class IPluginGame; } -namespace MOBase { -class IPluginGame; -} /** * @brief Dialog that can be used to create/delete/modify profiles **/ -class ProfilesDialog : public MOBase::TutorableDialog { - Q_OBJECT +class ProfilesDialog : public MOBase::TutorableDialog +{ + Q_OBJECT public: - /** - * @brief constructor - * - * @param profileName currently enabled profile - * @param parent parent widget - **/ - explicit ProfilesDialog(const QString& profileName, MOBase::IPluginGame const* game, QWidget* parent = 0); - ~ProfilesDialog(); - - /** - * @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; } + + /** + * @brief constructor + * + * @param profileName currently enabled profile + * @param parent parent widget + **/ + explicit ProfilesDialog(const QString &profileName, MOBase::IPluginGame const *game, QWidget *parent = 0); + ~ProfilesDialog(); + + /** + * @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; } protected: - virtual void showEvent(QShowEvent* event); + + virtual void showEvent(QShowEvent *event); private slots: - void on_localIniFilesBox_stateChanged(int state); + 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); + + QListWidgetItem *addItem(const QString &name); + void createProfile(const QString &name, bool useDefaultSettings); + void createProfile(const QString &name, const Profile &reference); private slots: - void on_closeButton_clicked(); + void on_closeButton_clicked(); - void on_addProfileButton_clicked(); + void on_addProfileButton_clicked(); - void on_invalidationBox_stateChanged(int arg1); + void on_invalidationBox_stateChanged(int arg1); - void on_copyProfileButton_clicked(); + void on_copyProfileButton_clicked(); - void on_profilesList_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous); + void on_profilesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - void on_removeProfileButton_clicked(); + void on_removeProfileButton_clicked(); - void on_localSavesBox_stateChanged(int arg1); + void on_localSavesBox_stateChanged(int arg1); - void on_transferButton_clicked(); + void on_transferButton_clicked(); - void on_renameButton_clicked(); + void on_renameButton_clicked(); private: - Ui::ProfilesDialog* ui; - QListWidget* m_ProfilesList; - bool m_FailState; - MOBase::IPluginGame const* m_Game; + Ui::ProfilesDialog *ui; + QListWidget *m_ProfilesList; + bool m_FailState; + MOBase::IPluginGame const *m_Game; }; #endif // PROFILESDIALOG_H diff --git a/src/qtgroupingproxy.cpp b/src/qtgroupingproxy.cpp index 51a1517c..5fcb84d3 100644 --- a/src/qtgroupingproxy.cpp +++ b/src/qtgroupingproxy.cpp @@ -16,6 +16,7 @@ // Modifications 2013-03-27 to 2013-03-29 by Sebastian Herbord + #include "qtgroupingproxy.h" #include @@ -30,890 +31,1038 @@ \ingroup model-view */ -QtGroupingProxy::QtGroupingProxy(QAbstractItemModel* model, 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) { - setSourceModel(model); - - // signal proxies - connect(sourceModel(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, - SLOT(modelDataChanged(const QModelIndex&, const QModelIndex&))); - 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())); - - if (groupedColumn != -1) - setGroupedColumn(groupedColumn); +QtGroupingProxy::QtGroupingProxy(QAbstractItemModel *model, 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 ) +{ + setSourceModel( model ); + + // signal proxies + connect( sourceModel(), + SIGNAL( dataChanged( const QModelIndex&, const QModelIndex& ) ), + this, SLOT( modelDataChanged( const QModelIndex&, const QModelIndex& ) ) + ); + 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()) ); + + if( groupedColumn != -1 ) + setGroupedColumn( groupedColumn ); } -QtGroupingProxy::~QtGroupingProxy() {} +QtGroupingProxy::~QtGroupingProxy() +{ +} -void QtGroupingProxy::setGroupedColumn(int groupedColumn) { - m_groupedColumn = groupedColumn; - 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) { - // qDebug() << __FILE__ << __FUNCTION__; - 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; + * + * @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 ) +{ + //qDebug() << __FILE__ << __FUNCTION__; + 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(); + // qDebug() << "role " << role << " : (" << variant.typeName() << ") : "<< variant; + 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; } - - QMapIterator i(itemData); - while (i.hasNext()) { - i.next(); - int role = i.key(); - QVariant variant = i.value(); - // qDebug() << "role " << role << " : (" << variant.typeName() << ") : "<< variant; - 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; - } + 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; + return rowDataList; } /* m_groupHash 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_groupHash.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); - // qDebug() << QString("building tree with %1 leafs.").arg( max ); - // 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; - - QHash> temp; - - for (auto iter = m_groupHash.begin(); iter != m_groupHash.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_groupHash = 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); +* 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_groupHash.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 ); + //qDebug() << QString("building tree with %1 leafs.").arg( max ); + //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; + + QHash > temp; + + for (auto iter = m_groupHash.begin(); iter != m_groupHash.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_groupHash = temp; - endResetModel(); - // restore expand-state - for (int row = 0; row < rowCount(); row++) { - QModelIndex idx = index(row, 0, QModelIndex()); - if (m_expandedItems.contains(idx.data(Qt::UserRole).toString())) { - emit expandItem(idx); - } + // 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); } -} - -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_groupHash.keys().contains(std::numeric_limits::max())) - m_groupHash.insert(std::numeric_limits::max(), QList()); // add an empty placeholder + } + + endResetModel(); + // restore expand-state + for( int row = 0; row < rowCount(); row++ ) { + QModelIndex idx = index( row, 0, QModelIndex() ); + if (m_expandedItems.contains(idx.data(Qt::UserRole).toString())) { + emit expandItem(idx); } + } +} - // an item can be in multiple groups - foreach (RowData data, groupData) { - int updatedGroup = -1; - if (!data.isEmpty()) { - // qDebug() << QString("index %1 belongs to group %2").arg( row ) - // .arg( data[0][Qt::DisplayRole].toString() ); - - 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_groupHash.keys().contains(updatedGroup)) - m_groupHash.insert(updatedGroup, QList()); // add an empty placeholder +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_groupHash.keys().contains( std::numeric_limits::max() ) ) + m_groupHash.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() ) + { + // qDebug() << QString("index %1 belongs to group %2").arg( row ) + // .arg( data[0][Qt::DisplayRole].toString() ); + + 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; } - - if (!updatedGroups.contains(updatedGroup)) - updatedGroups << updatedGroup; + } + + 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_groupHash.keys().contains( updatedGroup ) ) + m_groupHash.insert( updatedGroup, QList() ); //add an empty placeholder } - // update m_groupHash to the new source-model layout (one row added) - QMutableHashIterator> i(m_groupHash); - 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( updatedGroup ) ) + updatedGroups << updatedGroup; + } + + //update m_groupHash to the new source-model layout (one row added) + QMutableHashIterator > i( m_groupHash ); + 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()); - } + if( updatedGroups.contains( i.key() ) ) + { + //the row needs to be added to this group + groupList.insert( insertedProxyRow, idx.row() ); } + } - return updatedGroups; + 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; - - // dumpParentCreateList(); - // qDebug() << QString( "m_parentCreateList: (%1)" ).arg( m_parentCreateList.size() ); - // for( int i = 0 ; i < m_parentCreateList.size() ; i++ ) - // { - // qDebug() << i << " : " << m_parentCreateList[i].parentCreateIndex << - // " | " << m_parentCreateList[i].row; - // } - - return m_parentCreateList.size() - 1; -} - -QModelIndex QtGroupingProxy::index(int row, int column, const QModelIndex& parent) const { - // qDebug() << "index requested for: (" << row << "," << column << "), " << parent; - if (!hasIndex(row, column, parent)) { - return QModelIndex(); - } + * 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; + + //dumpParentCreateList(); + // qDebug() << QString( "m_parentCreateList: (%1)" ).arg( m_parentCreateList.size() ); + // for( int i = 0 ; i < m_parentCreateList.size() ; i++ ) + // { + // qDebug() << i << " : " << m_parentCreateList[i].parentCreateIndex << + // " | " << m_parentCreateList[i].row; + // } + + return m_parentCreateList.size() - 1; +} - if (parent.column() > 0) { - return QModelIndex(); - } +QModelIndex +QtGroupingProxy::index( int row, int column, const QModelIndex &parent ) const +{ + // qDebug() << "index requested for: (" << row << "," << column << "), " << parent; + 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. + /* 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); + int parentCreateIndex = indexOfParentCreate( parent ); - return createIndex(row, column, parentCreateIndex); + return createIndex( row, column, parentCreateIndex ); } -QModelIndex QtGroupingProxy::parent(const QModelIndex& index) const { - // qDebug() << "parent: " << index; - if (!index.isValid()) - return QModelIndex(); - - int parentCreateIndex = index.internalId(); - // qDebug() << "parentCreateIndex: " << parentCreateIndex; - if (parentCreateIndex == -1 || parentCreateIndex >= m_parentCreateList.count()) - return QModelIndex(); +QModelIndex +QtGroupingProxy::parent( const QModelIndex &index ) const +{ + //qDebug() << "parent: " << index; + if( !index.isValid() ) + return QModelIndex(); + + int parentCreateIndex = index.internalId(); + //qDebug() << "parentCreateIndex: " << parentCreateIndex; + if( parentCreateIndex == -1 || parentCreateIndex >= m_parentCreateList.count() ) + return QModelIndex(); + + struct ParentCreate pc = m_parentCreateList[parentCreateIndex]; + //qDebug() << "parentCreate: (" << pc.parentCreateIndex << "," << pc.row << ")"; + //only items at column 0 have children + return createIndex( pc.row, 0, pc.parentCreateIndex ); +} - struct ParentCreate pc = m_parentCreateList[parentCreateIndex]; - // qDebug() << "parentCreate: (" << pc.parentCreateIndex << "," << pc.row << ")"; - // only items at column 0 have children - return createIndex(pc.row, 0, pc.parentCreateIndex); +int +QtGroupingProxy::rowCount( const QModelIndex &index ) const +{ + //qDebug() << "rowCount: " << index; + if( !index.isValid() ) + { + //the number of top level groups + the number of non-grouped items + int rows = m_groupMaps.count() + m_groupHash.value( std::numeric_limits::max() ).count(); + //qDebug() << rows << " in root group"; + return rows; + } + + //TODO:group in group support. + if( isGroup( index ) ) + { + qint64 groupIndex = index.row(); + int rows = m_groupHash.value( groupIndex ).count(); + //qDebug() << rows << " in group " << m_groupMaps[groupIndex]; + return rows; + } else { + QModelIndex originalIndex = mapToSource( index ); + int rowCount = sourceModel()->rowCount( originalIndex ); + //qDebug() << "original item: rowCount == " << rowCount; + return rowCount; + } } -int QtGroupingProxy::rowCount(const QModelIndex& index) const { - // qDebug() << "rowCount: " << index; - if (!index.isValid()) { - // the number of top level groups + the number of non-grouped items - int rows = m_groupMaps.count() + m_groupHash.value(std::numeric_limits::max()).count(); - // qDebug() << rows << " in root group"; - return rows; - } +int +QtGroupingProxy::columnCount( const QModelIndex &index ) const +{ + if( !index.isValid() ) + return sourceModel()->columnCount( m_rootNode ); - // TODO:group in group support. - if (isGroup(index)) { - qint64 groupIndex = index.row(); - int rows = m_groupHash.value(groupIndex).count(); - // qDebug() << rows << " in group " << m_groupMaps[groupIndex]; - return rows; - } else { - QModelIndex originalIndex = mapToSource(index); - int rowCount = sourceModel()->rowCount(originalIndex); - // qDebug() << "original item: rowCount == " << rowCount; - return rowCount; - } + if( index.column() != 0 ) + return 0; + + return sourceModel()->columnCount( mapToSource( index ) ); } -int QtGroupingProxy::columnCount(const QModelIndex& index) const { - if (!index.isValid()) - return sourceModel()->columnCount(m_rootNode); - if (index.column() != 0) - return 0; +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(); + } - return sourceModel()->columnCount(mapToSource(index)); + // this should always work (comparing empty strings in the worst case) but + // the results may be wrong + return LHS.toString() < RHS.toString(); } -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 variantMax(const QVariantList& variants) { - QVariant result = variants.first(); - foreach (const QVariant& iter, variants) { - if (variantLess(result, iter)) { - result = iter; - } + +static QVariant variantMin(const QVariantList &variants) +{ + QVariant result = variants.first(); + foreach (const QVariant &iter, variants) { + if (variantLess(iter, result)) { + result = iter; } - return result; + } + 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(); - // qDebug() << __FUNCTION__ << index << " role: " << role; - 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_groupHash.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_groupHash.value(row).count() > 0) { - return this->index(0, column, parentIndex).data(role); - } else { - return QVariant(); - } - // return m_groupMaps[row][column].value( role ); - } break; - } - } - // qDebug() << __FUNCTION__ << "is a group"; - // use cached or precalculated data - if (m_groupMaps[row][column].contains(Qt::DisplayRole)) { - // qDebug() << "Using cached data for " << row << "x" << column << ": " << - // m_groupMaps[row][column].value(Qt::DisplayRole).toString(); - 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(); +QVariant +QtGroupingProxy::data( const QModelIndex &index, int role ) const +{ + if( !index.isValid() ) + return QVariant(); + // qDebug() << __FUNCTION__ << index << " role: " << role; + 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_groupHash.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_groupHash.value( row ).count() > 0) { + return this->index(0, column, parentIndex).data(role); } else { - return m_groupMaps[row][column].value(role).toString(); + return QVariant(); } - } + // return m_groupMaps[row][column].value( role ); + } break; + } + } - // 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_groupHash.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(); - } + //qDebug() << __FUNCTION__ << "is a group"; + //use cached or precalculated data + if( m_groupMaps[row][column].contains( Qt::DisplayRole ) ) + { + // qDebug() << "Using cached data for " << row << "x" << column << ": " << m_groupMaps[row][column].value(Qt::DisplayRole).toString(); + 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(); + } + } - // qDebug() << __FUNCTION__ << "childCount: " << childCount; - // 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); - // qDebug() << __FUNCTION__ << data << QVariant::typeToName(data.type()); - if (data.isValid() && !variantsOfChildren.contains(data)) - variantsOfChildren << data; - } - // qDebug() << "gathered this data from children: " << variantsOfChildren; - // saving in cache - ItemData roleMap = m_groupMaps[row].value(column); - foreach (const QVariant& variant, variantsOfChildren) { - if (roleMap[role] != variant) { - roleMap.insert(role, variantsOfChildren); - } - } + //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_groupHash.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(); + } + + //qDebug() << __FUNCTION__ << "childCount: " << childCount; + //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 ); + //qDebug() << __FUNCTION__ << data << QVariant::typeToName(data.type()); + if( data.isValid() && !variantsOfChildren.contains( data ) ) + variantsOfChildren << data; + } + //qDebug() << "gathered this data from children: " << variantsOfChildren; + //saving in cache + ItemData roleMap = m_groupMaps[row].value( column ); + foreach( const QVariant &variant, variantsOfChildren ) + { + if( roleMap[ role ] != variant ) { + roleMap.insert( role, variantsOfChildren ); + } + } - // qDebug() << QString("roleMap[%1]:").arg(role) << roleMap[role]; + //qDebug() << QString("roleMap[%1]:").arg(role) << roleMap[role]; - if (variantsOfChildren.count() == 0) - return QVariant(); + 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: + //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(); - case AGGR_MAX: - return variantMax(variantsOfChildren); - case AGGR_MIN: - return variantMin(variantsOfChildren); - default: { - if (variantsOfChildren.count() == 1) - return variantsOfChildren.first(); - return variantsOfChildren; + return variantsOfChildren; } break; - } } + } - return mapToSource(index).data(role); + 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; +bool +QtGroupingProxy::setData( const QModelIndex &idx, const QVariant &value, int role ) +{ + if( !idx.isValid() ) + return false; - if (isGroup(idx)) { - ItemData columnData = m_groupMaps[idx.row()][idx.column()]; + //no need to set data to exactly the same value + if( idx.data( role ) == value ) + return false; - columnData.insert(role, value); - // QItemDelegate will always use Qt::EditRole - if (role == Qt::EditRole) - columnData.insert(Qt::DisplayRole, value); + 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_groupHash.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 - // and make sure it's stored in the map - m_groupMaps[idx.row()].insert(idx.column(), columnData); + emit dataChanged( idx, idx ); + return true; + } - int columnToChange = idx.column() ? idx.column() : m_groupedColumn; - foreach (int originalRow, m_groupHash.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 + return sourceModel()->setData( mapToSource( idx ), value, role ); +} - emit dataChanged(idx, idx); - return true; - } +bool +QtGroupingProxy::isGroup( const QModelIndex &index ) const +{ + int parentCreateIndex = index.internalId(); + if( parentCreateIndex == -1 && index.row() < m_groupMaps.count() ) + return true; + return false; +} - return sourceModel()->setData(mapToSource(idx), value, role); +QModelIndex +QtGroupingProxy::mapToSource( const QModelIndex &index ) const +{ + //qDebug() << "mapToSource: " << index; + if( !index.isValid() ) { + return m_rootNode; + } + + if( isGroup( index ) ) + { + //qDebug() << "is a group: " << index.data( Qt::DisplayRole ).toString(); + return m_rootNode; + } + + QModelIndex proxyParent = index.parent(); + //qDebug() << "parent: " << proxyParent; + QModelIndex originalParent = mapToSource( proxyParent ); + //qDebug() << "originalParent: " << originalParent; + int originalRow = index.row(); + if( originalParent == m_rootNode ) + { + int indexInGroup = index.row(); + if( !proxyParent.isValid() ) + indexInGroup -= m_groupMaps.count(); + //qDebug() << "indexInGroup" << indexInGroup; + QList childRows = m_groupHash.value( proxyParent.row() ); + if( childRows.isEmpty() || indexInGroup >= childRows.count() || indexInGroup < 0 ) + return QModelIndex(); + + originalRow = childRows.at( indexInGroup ); + //qDebug() << "originalRow: " << originalRow; + } + return sourceModel()->index( originalRow, index.column(), originalParent ); } -bool QtGroupingProxy::isGroup(const QModelIndex& index) const { - int parentCreateIndex = index.internalId(); - if (parentCreateIndex == -1 && index.row() < m_groupMaps.count()) - return true; - return false; +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::mapToSource(const QModelIndex& index) const { - // qDebug() << "mapToSource: " << index; - if (!index.isValid()) { - return m_rootNode; +QModelIndex +QtGroupingProxy::mapFromSource( const QModelIndex &idx ) const +{ + if( !idx.isValid() ) + return QModelIndex(); + + QModelIndex proxyParent; + QModelIndex sourceParent = idx.parent(); + //qDebug() << "sourceParent: " << sourceParent; + 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; + QHashIterator > iterator( m_groupHash ); + while( iterator.hasNext() ) + { + iterator.next(); + if( iterator.value().contains( sourceRow ) ) + { + groupRow = iterator.key(); + break; + } } - if (isGroup(index)) { - // qDebug() << "is a group: " << index.data( Qt::DisplayRole ).toString(); - return m_rootNode; + if( groupRow != -1 ) //it's in a group, let's find the correct row. + { + proxyParent = this->index( groupRow, 0, QModelIndex() ); + proxyRow = m_groupHash.value( groupRow ).indexOf( sourceRow ); } - - QModelIndex proxyParent = index.parent(); - // qDebug() << "parent: " << proxyParent; - QModelIndex originalParent = mapToSource(proxyParent); - // qDebug() << "originalParent: " << originalParent; - int originalRow = index.row(); - if (originalParent == m_rootNode) { - int indexInGroup = index.row(); - if (!proxyParent.isValid()) - indexInGroup -= m_groupMaps.count(); - // qDebug() << "indexInGroup" << indexInGroup; - QList childRows = m_groupHash.value(proxyParent.row()); - if (childRows.isEmpty() || indexInGroup >= childRows.count() || indexInGroup < 0) - return QModelIndex(); - - originalRow = childRows.at(indexInGroup); - // qDebug() << "originalRow: " << originalRow; + else + { + proxyParent = QModelIndex(); + // if the proxy item is not in a group it will be below the groups. + int groupLength = m_groupMaps.count(); + //qDebug() << "groupNames length: " << groupLength; + int i = m_groupHash.value( std::numeric_limits::max() ).indexOf( sourceRow ); + //qDebug() << "index in hash: " << i; + proxyRow = groupLength + i; } - 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; + //qDebug() << "proxyParent: " << proxyParent; + //qDebug() << "proxyRow: " << proxyRow; + return this->index( proxyRow, idx.column(), proxyParent ); } -QModelIndex QtGroupingProxy::mapFromSource(const QModelIndex& idx) const { - if (!idx.isValid()) - return QModelIndex(); - - QModelIndex proxyParent; - QModelIndex sourceParent = idx.parent(); - // qDebug() << "sourceParent: " << sourceParent; - 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; - QHashIterator> iterator(m_groupHash); - 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. +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 0; + } + //only if the grouped column has the editable flag set allow the + //actions leading to setData on the source (edit & drop) + // qDebug() << idx; + 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_groupHash.value( idx.row() ) ) + { + QModelIndex originalIdx = sourceModel()->index( originalRow, 0, + m_rootNode.parent() ); + if ( (originalIdx.flags() & Qt::ItemIsUserCheckable) == 0 ) { - proxyParent = this->index(groupRow, 0, QModelIndex()); - proxyRow = m_groupHash.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(); - // qDebug() << "groupNames length: " << groupLength; - int i = m_groupHash.value(std::numeric_limits::max()).indexOf(sourceRow); - // qDebug() << "index in hash: " << i; - proxyRow = groupLength + i; + qDebug("row %d is not checkable", originalRow); + checkable = false; } - } - - // qDebug() << "proxyParent: " << proxyParent; - // qDebug() << "proxyRow: " << proxyRow; - 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 0; + if ( checkable ) { + defaultFlags |= Qt::ItemIsUserCheckable; + } } - // only if the grouped column has the editable flag set allow the - // actions leading to setData on the source (edit & drop) - // qDebug() << idx; - 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_groupHash.value(idx.row())) { - QModelIndex originalIdx = sourceModel()->index(originalRow, 0, m_rootNode.parent()); - if ((originalIdx.flags() & Qt::ItemIsUserCheckable) == 0) { - qDebug("row %d is not checkable", originalRow); - checkable = false; - } - } - - if (checkable) { - defaultFlags |= Qt::ItemIsUserCheckable; - } - } - // it's possible to have empty groups - if (m_groupHash.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_groupHash.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; + //it's possible to have empty groups + if( m_groupHash.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 ); } - - 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; + else + { + foreach( int originalRow, m_groupHash.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); +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; +bool +QtGroupingProxy::canFetchMore( const QModelIndex &parent ) const +{ + if( !parent.isValid() ) + return false; - if (isGroup(parent)) - return false; + if( isGroup( parent ) ) + return false; - return sourceModel()->canFetchMore(mapToSource(parent)); + return sourceModel()->canFetchMore( mapToSource( parent ) ); } -void QtGroupingProxy::fetchMore(const QModelIndex& parent) { - if (!parent.isValid()) - return; +void +QtGroupingProxy::fetchMore ( const QModelIndex & parent ) +{ + if( !parent.isValid() ) + return; - if (isGroup(parent)) - return; + if( isGroup( parent ) ) + return; - return sourceModel()->fetchMore(mapToSource(parent)); + 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()); +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_groupHash.remove(idx.row()); - m_groupMaps.removeAt(idx.row()); - m_parentCreateList.removeAt(idx.internalId()); - endRemoveRows(); +bool +QtGroupingProxy::removeGroup( const QModelIndex &idx ) +{ + beginRemoveRows( idx.parent(), idx.row(), idx.row() ); + m_groupHash.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; +} - // TODO: only true if all data could be unset. +bool +QtGroupingProxy::hasChildren( const QModelIndex &parent ) const +{ + if( !parent.isValid() ) { return true; -} + } -bool QtGroupingProxy::hasChildren(const QModelIndex& parent) const { - if (!parent.isValid()) { - return true; - } + if( isGroup( parent ) ) { + return !m_groupHash.value( parent.row() ).isEmpty(); + } - if (isGroup(parent)) { - return !m_groupHash.value(parent.row()).isEmpty(); - } - - return sourceModel()->hasChildren(mapToSource(parent)); + 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_groupHash.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); +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_groupHash.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 { - 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()); - } + 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 - // qDebug() << parent; - QModelIndex proxyParent = mapFromSource(parent); - // qDebug() << proxyParent; - beginInsertRows(proxyParent, start, end); - } +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 + // qDebug() << parent; + QModelIndex proxyParent = mapFromSource( parent ); + // qDebug() << proxyParent; + 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); - qDebug() << proxyParent; - // beginInsertRows had to be called in modelRowsAboutToBeInserted() - endInsertRows(); +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 ); + qDebug() << proxyParent; + //beginInsertRows had to be called in modelRowsAboutToBeInserted() + endInsertRows(); + } } -void QtGroupingProxy::modelRowsAboutToBeRemoved(const QModelIndex& parent, int start, int end) { - if (parent == m_rootNode) { - QHash>::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_groupHash.constBegin(); i != m_groupHash.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); - } - } +void +QtGroupingProxy::modelRowsAboutToBeRemoved( const QModelIndex &parent, int start, int end ) +{ + if( parent == m_rootNode ) + { + QHash >::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_groupHash.constBegin(); i != m_groupHash.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 - // qDebug() << parent; - QModelIndex proxyParent = mapFromSource(parent); - // qDebug() << proxyParent; - beginRemoveRows(proxyParent, start, end); + } } + } + else + { + //child item(s) of an original item will be removed, remap and pass it on + // qDebug() << parent; + QModelIndex proxyParent = mapFromSource( parent ); + // qDebug() << proxyParent; + 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_groupHash 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 - QMutableHashIterator> iter(m_groupHash); - 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. - } +void +QtGroupingProxy::modelRowsRemoved( const QModelIndex &parent, int start, int end ) +{ + if( parent == m_rootNode ) + { + //TODO: can be optimised by iterating over m_groupHash 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 + QMutableHashIterator > iter( m_groupHash ); + 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 ); } - - return; + //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. + } } - // beginRemoveRows had to be called in modelRowsAboutToBeRemoved(); - endRemoveRows(); -} - -void QtGroupingProxy::resetModel() { buildTree(); } + return; + } -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; + //beginRemoveRows had to be called in modelRowsAboutToBeRemoved(); + endRemoveRows(); +} - if (topLeft == bottomRight) { - emit dataChanged(proxyTopLeft, proxyTopLeft); - } else { - QModelIndex proxyBottomRight = mapFromSource(bottomRight); - emit dataChanged(proxyTopLeft, proxyBottomRight); - } +void +QtGroupingProxy::resetModel() +{ + buildTree(); } -bool QtGroupingProxy::isAGroupSelected(const QModelIndexList& list) const { - foreach (const QModelIndex& index, list) { - if (isGroup(index)) - return true; - } - return false; +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 ); + } } -void QtGroupingProxy::dumpGroups() const { - qDebug() << "m_groupHash: "; - for (int groupIndex = -1; groupIndex < m_groupHash.keys().count() - 1; groupIndex++) { - qDebug() << groupIndex << " : " << m_groupHash.value(groupIndex); - } +bool +QtGroupingProxy::isAGroupSelected( const QModelIndexList& list ) const +{ + foreach( const QModelIndex &index, list ) + { + if( isGroup( index ) ) + return true; + } + return false; +} - qDebug() << "m_groupMaps: "; - for (int groupIndex = 0; groupIndex < m_groupMaps.count(); groupIndex++) - qDebug() << m_groupMaps[groupIndex] << ": " << m_groupHash.value(groupIndex); - qDebug() << m_groupHash.value(std::numeric_limits::max()); +void +QtGroupingProxy::dumpGroups() const +{ + qDebug() << "m_groupHash: "; + for( int groupIndex = -1; groupIndex < m_groupHash.keys().count() - 1; groupIndex++ ) + { + qDebug() << groupIndex << " : " << m_groupHash.value( groupIndex ); + } + + qDebug() << "m_groupMaps: "; + for( int groupIndex = 0; groupIndex < m_groupMaps.count(); groupIndex++ ) + qDebug() << m_groupMaps[groupIndex] << ": " << m_groupHash.value( groupIndex ); + qDebug() << m_groupHash.value( std::numeric_limits::max() ); } -void QtGroupingProxy::expanded(const QModelIndex& index) { - m_expandedItems.insert(index.data(Qt::UserRole).toString()); + +void QtGroupingProxy::expanded(const QModelIndex &index) +{ + m_expandedItems.insert(index.data(Qt::UserRole).toString()); } -void QtGroupingProxy::collapsed(const QModelIndex& index) { - m_expandedItems.remove(index.data(Qt::UserRole).toString()); +void QtGroupingProxy::collapsed(const QModelIndex &index) +{ + m_expandedItems.remove(index.data(Qt::UserRole).toString()); } diff --git a/src/qtgroupingproxy.h b/src/qtgroupingproxy.h index 1f166265..6567cb0e 100644 --- a/src/qtgroupingproxy.h +++ b/src/qtgroupingproxy.h @@ -20,143 +20,150 @@ #define GROUPINGPROXY_H #include -#include #include #include -#include #include +#include +#include typedef QMap ItemData; typedef QMap RowData; -class QtGroupingProxy : public QAbstractProxyModel { - Q_OBJECT +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 - }; + 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(QAbstractItemModel* model, QModelIndex rootNode = QModelIndex(), int groupedColumn = -1, - int groupedRole = Qt::DisplayRole, unsigned int flags = 0, - int aggregateRole = Qt::DisplayRole); - ~QtGroupingProxy(); - - 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); - - QStringList expandedState(); + explicit QtGroupingProxy( QAbstractItemModel *model, QModelIndex rootNode = QModelIndex(), + int groupedColumn = -1, int groupedRole = Qt::DisplayRole, + unsigned int flags = 0, + int aggregateRole = Qt::DisplayRole); + ~QtGroupingProxy(); + + 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 ); + + QStringList expandedState(); signals: - void expandItem(const QModelIndex& index); + void expandItem(const QModelIndex &index); public slots: - /** - * @brief update expanded state - * @param index index of the expanded/collapsed item (from the base model!) - */ - void expanded(const QModelIndex& index); - /** - * @brief update expanded state - * @param index index of the expanded/collapsed item (from the base model!) - */ - void collapsed(const QModelIndex& index); + /** + * @brief update expanded state + * @param index index of the expanded/collapsed item (from the base model!) + */ + void expanded(const QModelIndex &index); + /** + * @brief update expanded state + * @param index index of the expanded/collapsed item (from the base model!) + */ + void collapsed(const QModelIndex &index); protected slots: - virtual void buildTree(); + 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(); + 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 - */ - QHash> m_groupHash; - /** 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; + /** 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 + */ + QHash > m_groupHash; + /** 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: - QSet m_expandedItems; - unsigned int m_flags; - int m_groupedRole; + QSet m_expandedItems; + unsigned int m_flags; + int m_groupedRole; + + int m_aggregateRole; - int m_aggregateRole; }; -#endif // GROUPINGPROXY_H +#endif //GROUPINGPROXY_H diff --git a/src/queryoverwritedialog.cpp b/src/queryoverwritedialog.cpp index 1cc92a01..932b2637 100644 --- a/src/queryoverwritedialog.cpp +++ b/src/queryoverwritedialog.cpp @@ -20,31 +20,46 @@ along with Mod Organizer. If not, see . #include "queryoverwritedialog.h" #include "ui_queryoverwritedialog.h" -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(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; } +QueryOverwriteDialog::~QueryOverwriteDialog() +{ + delete ui; +} -bool QueryOverwriteDialog::backup() const { return ui->backupBox->isChecked(); } +bool QueryOverwriteDialog::backup() const +{ + return ui->backupBox->isChecked(); +} -void QueryOverwriteDialog::on_mergeBtn_clicked() { - this->m_Action = ACT_MERGE; - this->accept(); +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_replaceBtn_clicked() +{ + this->m_Action = ACT_REPLACE; + this->accept(); } -void QueryOverwriteDialog::on_renameBtn_clicked() { - this->m_Action = ACT_RENAME; - this->accept(); +void QueryOverwriteDialog::on_renameBtn_clicked() +{ + this->m_Action = ACT_RENAME; + this->accept(); } -void QueryOverwriteDialog::on_cancelBtn_clicked() { this->reject(); } +void QueryOverwriteDialog::on_cancelBtn_clicked() +{ + this->reject(); +} diff --git a/src/queryoverwritedialog.h b/src/queryoverwritedialog.h index 519e108f..3f7b3194 100644 --- a/src/queryoverwritedialog.h +++ b/src/queryoverwritedialog.h @@ -26,27 +26,36 @@ namespace Ui { class QueryOverwriteDialog; } -class QueryOverwriteDialog : public QDialog { - Q_OBJECT +class QueryOverwriteDialog : public QDialog +{ + Q_OBJECT public: - enum Action { ACT_NONE, ACT_MERGE, ACT_REPLACE, ACT_RENAME }; - - enum Backup { BACKUP_NO, BACKUP_YES }; + 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; } + 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(); + void on_mergeBtn_clicked(); + void on_replaceBtn_clicked(); + void on_renameBtn_clicked(); + void on_cancelBtn_clicked(); private: - Ui::QueryOverwriteDialog* ui; - Action m_Action; + Ui::QueryOverwriteDialog *ui; + Action m_Action; }; #endif // QUERYOVERWRITEDIALOG_H diff --git a/src/savetextasdialog.cpp b/src/savetextasdialog.cpp index eb455143..8f0095d8 100644 --- a/src/savetextasdialog.cpp +++ b/src/savetextasdialog.cpp @@ -1,36 +1,50 @@ #include "savetextasdialog.h" #include "ui_savetextasdialog.h" +#include #include #include -#include + using MOBase::reportError; -SaveTextAsDialog::SaveTextAsDialog(QWidget* parent) : QDialog(parent), ui(new Ui::SaveTextAsDialog) { - ui->setupUi(this); + +SaveTextAsDialog::SaveTextAsDialog(QWidget *parent) + : QDialog(parent), ui(new Ui::SaveTextAsDialog) +{ + ui->setupUi(this); } -SaveTextAsDialog::~SaveTextAsDialog() { delete ui; } +SaveTextAsDialog::~SaveTextAsDialog() +{ + delete ui; +} -void SaveTextAsDialog::setText(const QString& text) { ui->textEdit->setPlainText(text); } +void SaveTextAsDialog::setText(const QString &text) +{ + ui->textEdit->setPlainText(text); +} -void SaveTextAsDialog::on_closeBtn_clicked() { this->close(); } +void SaveTextAsDialog::on_closeBtn_clicked() +{ + this->close(); +} -void SaveTextAsDialog::on_clipboardBtn_clicked() { - QClipboard* clipboard = QApplication::clipboard(); - clipboard->setText(ui->textEdit->toPlainText()); +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()); +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 ee5cc89b..1a17a2db 100644 --- a/src/savetextasdialog.h +++ b/src/savetextasdialog.h @@ -7,24 +7,25 @@ namespace Ui { class SaveTextAsDialog; } -class SaveTextAsDialog : public QDialog { - Q_OBJECT - +class SaveTextAsDialog : public QDialog +{ + Q_OBJECT + public: - explicit SaveTextAsDialog(QWidget* parent = 0); - ~SaveTextAsDialog(); + explicit SaveTextAsDialog(QWidget *parent = 0); + ~SaveTextAsDialog(); - void setText(const QString& text); + void setText(const QString &text); private slots: - void on_closeBtn_clicked(); + void on_closeBtn_clicked(); - void on_clipboardBtn_clicked(); + void on_clipboardBtn_clicked(); - void on_saveAsBtn_clicked(); + void on_saveAsBtn_clicked(); private: - Ui::SaveTextAsDialog* ui; + Ui::SaveTextAsDialog *ui; }; #endif // SAVETEXTASDIALOG_H diff --git a/src/selectiondialog.cpp b/src/selectiondialog.cpp index 4f582fd7..55728751 100644 --- a/src/selectiondialog.cpp +++ b/src/selectiondialog.cpp @@ -22,63 +22,80 @@ along with Mod Organizer. If not, see . #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); +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); + 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; +SelectionDialog::~SelectionDialog() +{ + delete ui; } -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; +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; } -int SelectionDialog::numChoices() const { return ui->buttonBox->findChildren(QString()).count(); } +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"); +} -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::getChoiceString() +{ + if ((m_Choice == nullptr) || + (m_ValidateByData && !m_Choice->property("data").isValid())) { + return QString(); + } else { + return m_Choice->text(); + } } -void SelectionDialog::disableCancel() { - ui->cancelButton->setEnabled(false); - ui->cancelButton->setHidden(true); +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_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(); } +void SelectionDialog::on_cancelButton_clicked() +{ + this->reject(); +} diff --git a/src/selectiondialog.h b/src/selectiondialog.h index 3f066707..3c4b25df 100644 --- a/src/selectiondialog.h +++ b/src/selectiondialog.h @@ -20,50 +20,54 @@ along with Mod Organizer. If not, see . #ifndef SELECTIONDIALOG_H #define SELECTIONDIALOG_H -#include #include +#include namespace Ui { class SelectionDialog; } -class SelectionDialog : public QDialog { - Q_OBJECT - +class SelectionDialog : public QDialog +{ + Q_OBJECT + public: - explicit SelectionDialog(const QString& description, QWidget* parent = 0, const QSize& iconSize = QSize()); - ~SelectionDialog(); + explicit SelectionDialog(const QString &description, QWidget *parent = 0, const QSize &iconSize = QSize()); - /** - * @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); + ~SelectionDialog(); - void addChoice(const QIcon& icon, const QString& buttonText, const QString& description, const QVariant& data); + /** + * @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); - int numChoices() const; + void addChoice(const QIcon &icon, const QString &buttonText, const QString &description, const QVariant &data); - QVariant getChoiceData(); - QString getChoiceString(); + int numChoices() const; - void disableCancel(); + QVariant getChoiceData(); + QString getChoiceString(); + + void disableCancel(); private slots: - void on_buttonBox_clicked(QAbstractButton* button); + void on_buttonBox_clicked(QAbstractButton *button); - void on_cancelButton_clicked(); + void on_cancelButton_clicked(); private: - Ui::SelectionDialog* ui; - QAbstractButton* m_Choice; - bool m_ValidateByData; - QSize m_IconSize; + + 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 3cf0d5cb..273e9b45 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -20,21 +20,20 @@ along with Mod Organizer. If not, see . #include "selfupdater.h" #include "archive.h" -#include "bbcode.h" #include "callback.h" -#include "downloadmanager.h" +#include "utility.h" #include "installationmanager.h" #include "iplugingame.h" #include "messagedialog.h" +#include "downloadmanager.h" #include "nexusinterface.h" #include "nxmaccessmanager.h" #include "settings.h" -#include "utility.h" +#include "bbcode.h" +#include #include #include -#include -#include #include #include #include @@ -42,8 +41,8 @@ along with Mod Organizer. If not, see . #include #include #include -#include #include +#include #include #include #include @@ -52,10 +51,11 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include -#include #include +#include #include @@ -69,231 +69,279 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; + typedef Archive* (*CreateArchiveType)(); -template -static T resolveFunction(QLibrary& lib, const char* name) { - T temp = reinterpret_cast(lib.resolve(name)); - if (temp == nullptr) { - throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData()); - } - return temp; + +template static T resolveFunction(QLibrary &lib, const char *name) +{ + T temp = reinterpret_cast(lib.resolve(name)); + if (temp == nullptr) { + throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData()); + } + return temp; } -SelfUpdater::SelfUpdater(NexusInterface* nexusInterface) - : m_Parent(nullptr), m_Interface(nexusInterface), m_Reply(nullptr), m_Attempts(3) { - QLibrary archiveLib(QCoreApplication::applicationDirPath() + "\\dlls\\archive.dll"); - if (!archiveLib.load()) { - throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString())); - } - CreateArchiveType CreateArchiveFunc = resolveFunction(archiveLib, "CreateArchive"); +SelfUpdater::SelfUpdater(NexusInterface *nexusInterface) + : m_Parent(nullptr) + , m_Interface(nexusInterface) + , m_Reply(nullptr) + , m_Attempts(3) +{ + QLibrary archiveLib(QCoreApplication::applicationDirPath() + "\\dlls\\archive.dll"); + if (!archiveLib.load()) { + throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString())); + } - m_ArchiveHandler = CreateArchiveFunc(); - if (!m_ArchiveHandler->isValid()) { - throw MyException(InstallationManager::getErrorString(m_ArchiveHandler->getLastError())); - } + CreateArchiveType CreateArchiveFunc = resolveFunction(archiveLib, "CreateArchive"); - VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); + m_ArchiveHandler = CreateArchiveFunc(); + if (!m_ArchiveHandler->isValid()) { + throw MyException(InstallationManager::getErrorString(m_ArchiveHandler->getLastError())); + } - m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, version.dwFileVersionMS & 0xFFFF, - version.dwFileVersionLS >> 16, version.dwFileVersionLS & 0xFFFF); -} + VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); -SelfUpdater::~SelfUpdater() { delete m_ArchiveHandler; } - -void SelfUpdater::setUserInterface(QWidget* widget) { m_Parent = widget; } - -void SelfUpdater::testForUpdate() { - // TODO: if prereleases are disabled we could just request the latest release - // directly - try { - m_GitHub.releases(GitHub::Repository("LePresidente", "modorganizer"), [this](const QJsonArray& releases) { - QJsonObject newest; - for (const QJsonValue& releaseVal : releases) { - QJsonObject release = releaseVal.toObject(); - if (!release["draft"].toBool() && - (Settings::instance().usePrereleases() || !release["prerelease"].toBool())) { - if (newest.empty() || - (VersionInfo(release["tag_name"].toString()) > VersionInfo(newest["tag_name"].toString()))) { - newest = release; - } - } - } - - if (!newest.empty()) { - VersionInfo newestVer(newest["tag_name"].toString()); - if (newestVer > this->m_MOVersion) { - m_UpdateCandidate = newest; - qDebug("update available: %s -> %s", qPrintable(this->m_MOVersion.displayString()), - qPrintable(newestVer.displayString())); - emit updateAvailable(); - } else if (newestVer < this->m_MOVersion) { - // this could happen if the user switches from using prereleases to - // stable builds. Should we downgrade? - qDebug("this version is newer than the newest installed one: %s -> %s", - qPrintable(this->m_MOVersion.displayString()), qPrintable(newestVer.displayString())); - } - } - }); - } - // Catch all is bad by design, should be improved - catch (...) { - qDebug("Unable to connect to github.com to check version"); - } + m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, + version.dwFileVersionMS & 0xFFFF, + version.dwFileVersionLS >> 16, + version.dwFileVersionLS & 0xFFFF); } -void SelfUpdater::startUpdate() { - // the button can't be pressed if there isn't an update candidate - Q_ASSERT(!m_UpdateCandidate.empty()); - QMessageBox query( - QMessageBox::Question, tr("New update available (%1)").arg(m_UpdateCandidate["tag_name"].toString()), - BBCode::convertToHTML(m_UpdateCandidate["body"].toString()), QMessageBox::Yes | QMessageBox::Cancel, m_Parent); - - query.button(QMessageBox::Yes)->setText(tr("Install")); +SelfUpdater::~SelfUpdater() +{ + delete m_ArchiveHandler; +} - int res = query.exec(); +void SelfUpdater::setUserInterface(QWidget *widget) +{ + m_Parent = widget; +} - if (query.result() == QMessageBox::Yes) { - bool found = false; - for (const QJsonValue& assetVal : m_UpdateCandidate["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::testForUpdate() +{ + // TODO: if prereleases are disabled we could just request the latest release + // directly + try { + m_GitHub.releases(GitHub::Repository("LePresidente", "modorganizer"), + [this](const QJsonArray &releases) { + QJsonObject newest; + for (const QJsonValue &releaseVal : releases) { + QJsonObject release = releaseVal.toObject(); + if (!release["draft"].toBool() && (Settings::instance().usePrereleases() + || !release["prerelease"].toBool())) { + if (newest.empty() || (VersionInfo(release["tag_name"].toString()) + > VersionInfo(newest["tag_name"].toString()))) { + newest = release; } + } } -} -void SelfUpdater::showProgress() { - if (m_Progress == nullptr) { - m_Progress = new QProgressDialog(m_Parent, Qt::Dialog); - connect(m_Progress, SIGNAL(canceled()), this, SLOT(downloadCancel())); + if (!newest.empty()) { + VersionInfo newestVer(newest["tag_name"].toString()); + if (newestVer > this->m_MOVersion) { + m_UpdateCandidate = newest; + qDebug("update available: %s -> %s", + qPrintable(this->m_MOVersion.displayString()), + qPrintable(newestVer.displayString())); + emit updateAvailable(); + } else if (newestVer < this->m_MOVersion) { + // this could happen if the user switches from using prereleases to + // stable builds. Should we downgrade? + qDebug("this version is newer than the newest installed one: %s -> %s", + qPrintable(this->m_MOVersion.displayString()), + qPrintable(newestVer.displayString())); + } } - m_Progress->setModal(true); - m_Progress->show(); - m_Progress->setValue(0); - m_Progress->setWindowTitle(tr("Update")); - m_Progress->setLabelText(tr("Download in progress")); + }); + } + //Catch all is bad by design, should be improved + catch (...) { + qDebug("Unable to connect to github.com to check version"); + } } -void SelfUpdater::closeProgress() { - if (m_Progress != nullptr) { - m_Progress->hide(); - m_Progress->deleteLater(); - m_Progress = nullptr; +void SelfUpdater::startUpdate() +{ + // the button can't be pressed if there isn't an update candidate + Q_ASSERT(!m_UpdateCandidate.empty()); + + QMessageBox query(QMessageBox::Question, + tr("New update available (%1)") + .arg(m_UpdateCandidate["tag_name"].toString()), + BBCode::convertToHTML(m_UpdateCandidate["body"].toString()), + QMessageBox::Yes | QMessageBox::Cancel, m_Parent); + + query.button(QMessageBox::Yes)->setText(tr("Install")); + + int res = query.exec(); + + if (query.result() == QMessageBox::Yes) { + bool found = false; + for (const QJsonValue &assetVal : m_UpdateCandidate["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::openOutputFile(const QString& fileName) { - QString outputPath = QDir::fromNativeSeparators(qApp->property("dataPath").toString()) + "/" + fileName; - qDebug("downloading to %s", qPrintable(outputPath)); - m_UpdateFile.setFileName(outputPath); - m_UpdateFile.open(QIODevice::WriteOnly); + +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::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::closeProgress() +{ + if (m_Progress != nullptr) { + m_Progress->hide(); + m_Progress->deleteLater(); + m_Progress = nullptr; + } } -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::openOutputFile(const QString &fileName) +{ + QString outputPath = QDir::fromNativeSeparators(qApp->property("dataPath").toString()) + "/" + fileName; + qDebug("downloading to %s", qPrintable(outputPath)); + m_UpdateFile.setFileName(outputPath); + m_UpdateFile.open(QIODevice::WriteOnly); } -void SelfUpdater::downloadReadyRead() { - if (m_Reply != nullptr) { - m_UpdateFile.write(m_Reply->readAll()); - } +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::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; +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); } - m_UpdateFile.write(m_Reply->readAll()); + } + } + } +} - error = m_Reply->error(); - if (m_Reply->header(QNetworkRequest::ContentTypeHeader).toString().startsWith("text", Qt::CaseInsensitive)) { - m_Canceled = true; - } +void SelfUpdater::downloadReadyRead() +{ + if (m_Reply != nullptr) { + m_UpdateFile.write(m_Reply->readAll()); + } +} + - closeProgress(); +void SelfUpdater::downloadFinished() +{ + int error = QNetworkReply::NoError; - m_Reply->close(); - m_Reply->deleteLater(); - m_Reply = nullptr; + 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()); - m_UpdateFile.close(); + error = m_Reply->error(); - if ((m_UpdateFile.size() == 0) || (error != QNetworkReply::NoError) || m_Canceled) { - if (!m_Canceled) { - reportError(tr("Download failed: %1").arg(error)); - } - m_UpdateFile.remove(); - return; + if (m_Reply->header(QNetworkRequest::ContentTypeHeader).toString().startsWith("text", Qt::CaseInsensitive)) { + m_Canceled = true; } - qDebug("download: %s", m_UpdateFile.fileName().toUtf8().constData()); + closeProgress(); + + m_Reply->close(); + m_Reply->deleteLater(); + m_Reply = nullptr; + } + + m_UpdateFile.close(); - try { - installUpdate(); - } catch (const std::exception& e) { - reportError(tr("Failed to install update: %1").arg(e.what())); + if ((m_UpdateFile.size() == 0) || + (error != QNetworkReply::NoError) || + m_Canceled) { + if (!m_Canceled) { + reportError(tr("Download failed: %1").arg(error)); } + m_UpdateFile.remove(); + return; + } + + qDebug("download: %s", m_UpdateFile.fileName().toUtf8().constData()); + + 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 mopath = QDir::fromNativeSeparators(qApp->property("dataPath").toString()); +void SelfUpdater::downloadCancel() +{ + m_Canceled = true; +} - HINSTANCE res = - ::ShellExecuteW(nullptr, L"open", m_UpdateFile.fileName().toStdWString().c_str(), nullptr, nullptr, SW_SHOW); - if (res > (HINSTANCE)32) { - QCoreApplication::quit(); - } else { - reportError(tr("Failed to start %1: %2").arg(m_UpdateFile.fileName()).arg((int)res)); - } +void SelfUpdater::installUpdate() +{ + const QString mopath + = QDir::fromNativeSeparators(qApp->property("dataPath").toString()); - m_UpdateFile.remove(); + HINSTANCE res = ::ShellExecuteW( + nullptr, L"open", m_UpdateFile.fileName().toStdWString().c_str(), nullptr, + nullptr, SW_SHOW); + + if (res > (HINSTANCE)32) { + QCoreApplication::quit(); + } else { + reportError(tr("Failed to start %1: %2") + .arg(m_UpdateFile.fileName()) + .arg((int)res)); + } + + m_UpdateFile.remove(); } -void SelfUpdater::report7ZipError(QString const& errorMessage) { - QMessageBox::critical(m_Parent, tr("Error"), errorMessage); +void SelfUpdater::report7ZipError(QString const &errorMessage) +{ + QMessageBox::critical(m_Parent, tr("Error"), errorMessage); } diff --git a/src/selfupdater.h b/src/selfupdater.h index 12b473bd..4743f6a4 100644 --- a/src/selfupdater.h +++ b/src/selfupdater.h @@ -20,14 +20,13 @@ along with Mod Organizer. If not, see . #ifndef SELFUPDATER_H #define SELFUPDATER_H -#include + #include +#include class Archive; class NexusInterface; -namespace MOBase { -class IPluginGame; -} +namespace MOBase { class IPluginGame; } #include #include @@ -38,6 +37,7 @@ class IPluginGame; class QNetworkReply; class QProgressDialog; + /** * @brief manages updates for Mod Organizer itself * This class is used to update the Mod Organizer @@ -57,88 +57,94 @@ class QProgressDialog; * * @todo use NexusBridge **/ -class SelfUpdater : public QObject { +class SelfUpdater : public QObject +{ - Q_OBJECT + 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); - - /** - * @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; } + + /** + * @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); + + /** + * @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; } public slots: - /** - * @brief request information about the current version - **/ - void testForUpdate(); + /** + * @brief request information about the current version + **/ + void testForUpdate(); signals: - /** - * @brief emitted if a restart of the client is necessary to complete the update - **/ - void restart(); + /** + * @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 an update is available + **/ + void updateAvailable(); - /** - * @brief emitted if a message of the day was received - **/ - void motdAvailable(const QString& motd); + /** + * @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(); + + 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(); + 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; - - Archive* m_ArchiveHandler; - - GitHub m_GitHub; - QJsonObject m_UpdateCandidate; + + 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; + + Archive *m_ArchiveHandler; + + GitHub m_GitHub; + QJsonObject m_UpdateCandidate; + }; + #endif // SELFUPDATER_H diff --git a/src/serverinfo.h b/src/serverinfo.h index 36b0e3f3..8e5e935a 100644 --- a/src/serverinfo.h +++ b/src/serverinfo.h @@ -1,15 +1,16 @@ #ifndef SERVERINFO_H #define SERVERINFO_H +#include #include #include -#include -struct ServerInfo { - QString name; - bool premium; - QDate lastSeen; - bool preferred; +struct ServerInfo +{ + QString name; + bool premium; + QDate lastSeen; + bool preferred; }; Q_DECLARE_METATYPE(ServerInfo) diff --git a/src/settings.cpp b/src/settings.cpp index 2694c59a..d1130e05 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -19,38 +19,39 @@ along with Mod Organizer. If not, see . #include "settings.h" -#include "appconfig.h" -#include "organizercore.h" #include "pluginsetting.h" #include "serverinfo.h" #include "settingsdialog.h" #include "versioninfo.h" +#include "appconfig.h" +#include "organizercore.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 // for Qt::UserRole, etc +#include // for Qt::UserRole, etc #include // for qDebug, qWarning #include // For ShellExecuteW, HINSTANCE, etc @@ -66,808 +67,934 @@ using namespace MOBase; template class QListWidgetItemEx : public QListWidgetItem { public: - QListWidgetItemEx(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 { - return this->data(m_SortRole).value() < other.data(m_SortRole).value(); - } + QListWidgetItemEx(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 { + return this->data(m_SortRole).value() < other.data(m_SortRole).value(); + } private: - int m_SortRole; + int m_SortRole; }; -static const unsigned char Key2[20] = {0x99, 0xb8, 0x76, 0x42, 0x3e, 0xc1, 0x60, 0xa4, 0x5b, 0x01, - 0xdb, 0xf8, 0x43, 0x3a, 0xb7, 0xb6, 0x98, 0xd4, 0x7d, 0xa2}; -Settings* Settings::s_Instance = nullptr; +static const unsigned char Key2[20] = { 0x99, 0xb8, 0x76, 0x42, 0x3e, 0xc1, 0x60, 0xa4, 0x5b, 0x01, + 0xdb, 0xf8, 0x43, 0x3a, 0xb7, 0xb6, 0x98, 0xd4, 0x7d, 0xa2 }; -Settings::Settings(const QSettings& settingsSource) : m_Settings(settingsSource.fileName(), settingsSource.format()) { - if (s_Instance != nullptr) { - throw std::runtime_error("second instance of \"Settings\" created"); - } else { - s_Instance = this; - } -} +Settings *Settings::s_Instance = nullptr; -Settings::~Settings() { s_Instance = nullptr; } -Settings& Settings::instance() { - if (s_Instance == nullptr) { - throw std::runtime_error("no instance of \"Settings\""); - } - return *s_Instance; +Settings::Settings(const QSettings &settingsSource) + : m_Settings(settingsSource.fileName(), settingsSource.format()) +{ + if (s_Instance != nullptr) { + throw std::runtime_error("second instance of \"Settings\" created"); + } else { + s_Instance = this; + } } -void Settings::clearPlugins() { - m_Plugins.clear(); - m_PluginSettings.clear(); - m_PluginBlacklist.clear(); - int count = m_Settings.beginReadArray("pluginBlacklist"); - for (int i = 0; i < count; ++i) { - m_Settings.setArrayIndex(i); - m_PluginBlacklist.insert(m_Settings.value("name").toString()); - } - m_Settings.endArray(); +Settings::~Settings() +{ + s_Instance = nullptr; } -bool Settings::pluginBlacklisted(const QString& fileName) const { return m_PluginBlacklist.contains(fileName); } -void Settings::registerAsNXMHandler(bool force) { - std::wstring nxmPath = ToWString(QCoreApplication::applicationDirPath() + "/nxmhandler.exe"); - std::wstring executable = ToWString(QCoreApplication::applicationFilePath()); - std::wstring mode = force ? L"forcereg" : L"reg"; - std::wstring parameters = mode + L" " + m_GamePlugin->gameShortName().toStdWString() + L" \"" + executable + L"\""; - HINSTANCE res = ::ShellExecuteW(nullptr, L"open", nxmPath.c_str(), parameters.c_str(), nullptr, SW_SHOWNORMAL); - if ((int)res <= 32) { - QMessageBox::critical(nullptr, tr("Failed"), tr("Sorry, failed to start the helper application")); - } +Settings &Settings::instance() +{ + if (s_Instance == nullptr) { + throw std::runtime_error("no instance of \"Settings\""); + } + return *s_Instance; } -void Settings::managedGameChanged(IPluginGame const* gamePlugin) { m_GamePlugin = gamePlugin; } +void Settings::clearPlugins() +{ + m_Plugins.clear(); + m_PluginSettings.clear(); -void Settings::registerPlugin(IPlugin* plugin) { - m_Plugins.push_back(plugin); - m_PluginSettings.insert(plugin->name(), QMap()); - m_PluginDescriptions.insert(plugin->name(), QMap()); - for (const PluginSetting& setting : plugin->settings()) { - QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue); - if (!temp.convert(setting.defaultValue.type())) { - qWarning("failed to interpret \"%s\" as correct type for \"%s\" in plugin \"%s\", using default", - qPrintable(temp.toString()), qPrintable(setting.key), qPrintable(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_PluginBlacklist.clear(); + int count = m_Settings.beginReadArray("pluginBlacklist"); + for (int i = 0; i < count; ++i) { + m_Settings.setArrayIndex(i); + m_PluginBlacklist.insert(m_Settings.value("name").toString()); + } + m_Settings.endArray(); } -QString Settings::obfuscate(const QString& password) { - QByteArray temp = password.toUtf8(); +bool Settings::pluginBlacklisted(const QString &fileName) const +{ + return m_PluginBlacklist.contains(fileName); +} - QByteArray buffer; - for (int i = 0; i < temp.length(); ++i) { - buffer.append(temp.at(i) ^ Key2[i % 20]); - } - return buffer.toBase64(); +void Settings::registerAsNXMHandler(bool force) +{ + std::wstring nxmPath = ToWString(QCoreApplication::applicationDirPath() + "/nxmhandler.exe"); + std::wstring executable = ToWString(QCoreApplication::applicationFilePath()); + std::wstring mode = force ? L"forcereg" : L"reg"; + std::wstring parameters = mode + L" " + m_GamePlugin->gameShortName().toStdWString() + L" \"" + executable + L"\""; + HINSTANCE res = ::ShellExecuteW(nullptr, L"open", nxmPath.c_str(), parameters.c_str(), nullptr, SW_SHOWNORMAL); + if ((int)res <= 32) { + QMessageBox::critical(nullptr, tr("Failed"), + tr("Sorry, failed to start the helper application")); + } } -QString Settings::deObfuscate(const QString& password) { - QByteArray temp(QByteArray::fromBase64(password.toUtf8())); +void Settings::managedGameChanged(IPluginGame const *gamePlugin) +{ + m_GamePlugin = gamePlugin; +} - QByteArray buffer; - for (int i = 0; i < temp.length(); ++i) { - buffer.append(temp.at(i) ^ Key2[i % 20]); +void Settings::registerPlugin(IPlugin *plugin) +{ + m_Plugins.push_back(plugin); + m_PluginSettings.insert(plugin->name(), QMap()); + m_PluginDescriptions.insert(plugin->name(), QMap()); + for (const PluginSetting &setting : plugin->settings()) { + QVariant temp = m_Settings.value("Plugins/" + plugin->name() + "/" + setting.key, setting.defaultValue); + if (!temp.convert(setting.defaultValue.type())) { + qWarning("failed to interpret \"%s\" as correct type for \"%s\" in plugin \"%s\", using default", + qPrintable(temp.toString()), qPrintable(setting.key), qPrintable(plugin->name())); + temp = setting.defaultValue; } - return QString::fromUtf8(buffer.constData()); + m_PluginSettings[plugin->name()][setting.key] = temp; + m_PluginDescriptions[plugin->name()][setting.key] = QString("%1 (default: %2)").arg(setting.description).arg(setting.defaultValue.toString()); + } } -bool Settings::hideUncheckedPlugins() const { - return m_Settings.value("Settings/hide_unchecked_plugins", false).toBool(); -} +QString Settings::obfuscate(const QString &password) +{ + QByteArray temp = password.toUtf8(); -bool Settings::forceEnableCoreFiles() const { - return m_Settings.value("Settings/force_enable_core_files", true).toBool(); + QByteArray buffer; + for (int i = 0; i < temp.length(); ++i) { + buffer.append(temp.at(i) ^ Key2[i % 20]); + } + return buffer.toBase64(); } -bool Settings::automaticLoginEnabled() const { return m_Settings.value("Settings/nexus_login", false).toBool(); } +QString Settings::deObfuscate(const QString &password) +{ + QByteArray temp(QByteArray::fromBase64(password.toUtf8())); + + QByteArray buffer; + for (int i = 0; i < temp.length(); ++i) { + buffer.append(temp.at(i) ^ Key2[i % 20]); + } + return QString::fromUtf8(buffer.constData()); +} -QString Settings::getSteamAppID() const { - return m_Settings.value("Settings/app_id", m_GamePlugin->steamAPPId()).toString(); +bool Settings::hideUncheckedPlugins() const +{ + return m_Settings.value("Settings/hide_unchecked_plugins", false).toBool(); } -bool Settings::usePrereleases() const { return m_Settings.value("Settings/use_prereleases", false).toBool(); } +bool Settings::forceEnableCoreFiles() const +{ + return m_Settings.value("Settings/force_enable_core_files", true).toBool(); +} -void Settings::setDownloadSpeed(const QString& serverName, int bytesPerSecond) { - m_Settings.beginGroup("Servers"); +bool Settings::automaticLoginEnabled() const +{ + return m_Settings.value("Settings/nexus_login", false).toBool(); +} - for (const QString& serverKey : m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); - if (serverKey == serverName) { - data["downloadCount"] = data["downloadCount"].toInt() + 1; - data["downloadSpeed"] = data["downloadSpeed"].toDouble() + static_cast(bytesPerSecond); - m_Settings.setValue(serverKey, data); - } - } +QString Settings::getSteamAppID() const +{ + return m_Settings.value("Settings/app_id", m_GamePlugin->steamAPPId()).toString(); +} - m_Settings.endGroup(); - m_Settings.sync(); +bool Settings::usePrereleases() const +{ + return m_Settings.value("Settings/use_prereleases", false).toBool(); } -std::map Settings::getPreferredServers() { - std::map result; - m_Settings.beginGroup("Servers"); +void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) +{ + m_Settings.beginGroup("Servers"); - for (const QString& serverKey : m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); - int preference = data["preferred"].toInt(); - if (preference > 0) { - result[serverKey] = preference; - } + for (const QString &serverKey : m_Settings.childKeys()) { + QVariantMap data = m_Settings.value(serverKey).toMap(); + if (serverKey == serverName) { + data["downloadCount"] = data["downloadCount"].toInt() + 1; + data["downloadSpeed"] = data["downloadSpeed"].toDouble() + static_cast(bytesPerSecond); + m_Settings.setValue(serverKey, data); } - m_Settings.endGroup(); + } - return result; + m_Settings.endGroup(); + m_Settings.sync(); } -QString Settings::getConfigurablePath(const QString& key, const QString& def, bool resolve) const { - QString result = QDir::fromNativeSeparators( - m_Settings.value(QString("settings/") + key, QString("%BASE_DIR%/") + def).toString()); - if (resolve) { - result.replace("%BASE_DIR%", getBaseDirectory()); +std::map Settings::getPreferredServers() +{ + std::map result; + m_Settings.beginGroup("Servers"); + + for (const QString &serverKey : m_Settings.childKeys()) { + QVariantMap data = m_Settings.value(serverKey).toMap(); + int preference = data["preferred"].toInt(); + if (preference > 0) { + result[serverKey] = preference; } - return result; -} + } + m_Settings.endGroup(); -QString Settings::getBaseDirectory() const { - return QDir::fromNativeSeparators( - m_Settings.value("settings/base_directory", qApp->property("dataPath").toString()).toString()); + return result; } -QString Settings::getDownloadDirectory(bool resolve) const { - return getConfigurablePath("download_directory", ToQString(AppConfig::downloadPath()), resolve); +QString Settings::getConfigurablePath(const QString &key, + const QString &def, + bool resolve) const +{ + QString result = QDir::fromNativeSeparators( + m_Settings.value(QString("settings/") + key, QString("%BASE_DIR%/") + def) + .toString()); + if (resolve) { + result.replace("%BASE_DIR%", getBaseDirectory()); + } + return result; } -QString Settings::getCacheDirectory(bool resolve) const { - return getConfigurablePath("cache_directory", ToQString(AppConfig::cachePath()), resolve); +QString Settings::getBaseDirectory() const +{ + return QDir::fromNativeSeparators(m_Settings.value( + "settings/base_directory", qApp->property("dataPath").toString()).toString()); } -QString Settings::getModDirectory(bool resolve) const { - return getConfigurablePath("mod_directory", ToQString(AppConfig::modsPath()), resolve); +QString Settings::getDownloadDirectory(bool resolve) const +{ + return getConfigurablePath("download_directory", ToQString(AppConfig::downloadPath()), resolve); } -QString Settings::getProfileDirectory(bool resolve) const { - return getConfigurablePath("profiles_directory", ToQString(AppConfig::profilesPath()), resolve); +QString Settings::getCacheDirectory(bool resolve) const +{ + return getConfigurablePath("cache_directory", ToQString(AppConfig::cachePath()), resolve); } -QString Settings::getOverwriteDirectory(bool resolve) const { - return getConfigurablePath("overwrite_directory", ToQString(AppConfig::overwritePath()), resolve); +QString Settings::getModDirectory(bool resolve) const +{ + return getConfigurablePath("mod_directory", ToQString(AppConfig::modsPath()), resolve); } -QString Settings::getNMMVersion() const { - static const QString MIN_NMM_VERSION = "0.61.13"; - QString result = m_Settings.value("Settings/nmm_version", MIN_NMM_VERSION).toString(); - if (VersionInfo(result) < VersionInfo(MIN_NMM_VERSION)) { - result = MIN_NMM_VERSION; - } - return result; +QString Settings::getProfileDirectory(bool resolve) const +{ + return getConfigurablePath("profiles_directory", ToQString(AppConfig::profilesPath()), resolve); } -bool Settings::getNexusLogin(QString& username, QString& password) const { - if (m_Settings.value("Settings/nexus_login", false).toBool()) { - username = m_Settings.value("Settings/nexus_username", "").toString(); - password = deObfuscate(m_Settings.value("Settings/nexus_password", "").toString()); - return true; - } else { - return false; - } +QString Settings::getOverwriteDirectory(bool resolve) const +{ + return getConfigurablePath("overwrite_directory", + ToQString(AppConfig::overwritePath()), resolve); } -bool Settings::getSteamLogin(QString& username, QString& password) const { - if (m_Settings.contains("Settings/steam_username") && m_Settings.contains("Settings/steam_password")) { - username = m_Settings.value("Settings/steam_username").toString(); - password = deObfuscate(m_Settings.value("Settings/steam_password").toString()); - return true; - } else { - return false; - } +QString Settings::getNMMVersion() const +{ + static const QString MIN_NMM_VERSION = "0.61.13"; + QString result = m_Settings.value("Settings/nmm_version", MIN_NMM_VERSION).toString(); + if (VersionInfo(result) < VersionInfo(MIN_NMM_VERSION)) { + result = MIN_NMM_VERSION; + } + return result; } -bool Settings::compactDownloads() const { return m_Settings.value("Settings/compact_downloads", false).toBool(); } - -bool Settings::metaDownloads() const { return m_Settings.value("Settings/meta_downloads", false).toBool(); } -bool Settings::offlineMode() const { return m_Settings.value("Settings/offline_mode", false).toBool(); } - -int Settings::logLevel() const { - return m_Settings.value("Settings/log_level", static_cast(LogLevel::Info)).toInt(); +bool Settings::getNexusLogin(QString &username, QString &password) const +{ + if (m_Settings.value("Settings/nexus_login", false).toBool()) { + username = m_Settings.value("Settings/nexus_username", "").toString(); + password = deObfuscate(m_Settings.value("Settings/nexus_password", "").toString()); + return true; + } else { + return false; + } } -int Settings::crashDumpsType() const { - return m_Settings.value("Settings/crash_dumps_type", static_cast(CrashDumpsType::Mini)).toInt(); +bool Settings::getSteamLogin(QString &username, QString &password) const +{ + if (m_Settings.contains("Settings/steam_username") + && m_Settings.contains("Settings/steam_password")) { + username = m_Settings.value("Settings/steam_username").toString(); + password = deObfuscate(m_Settings.value("Settings/steam_password").toString()); + return true; + } else { + return false; + } } - -int Settings::crashDumpsMax() const { return m_Settings.value("Settings/crash_dumps_max", 5).toInt(); } - -void Settings::setNexusLogin(QString username, QString password) { - m_Settings.setValue("Settings/nexus_login", true); - m_Settings.setValue("Settings/nexus_username", username); - m_Settings.setValue("Settings/nexus_password", obfuscate(password)); +bool Settings::compactDownloads() const +{ + return m_Settings.value("Settings/compact_downloads", false).toBool(); } -void Settings::setSteamLogin(QString username, QString password) { - if (username == "") { - m_Settings.remove("Settings/steam_username"); - password = ""; - } else { - m_Settings.setValue("Settings/steam_username", username); - } - if (password == "") { - m_Settings.remove("Settings/steam_password"); - } else { - m_Settings.setValue("Settings/steam_password", obfuscate(password)); - } +bool Settings::metaDownloads() const +{ + return m_Settings.value("Settings/meta_downloads", false).toBool(); } -LoadMechanism::EMechanism Settings::getLoadMechanism() const { - switch (m_Settings.value("Settings/load_mechanism").toInt()) { - case LoadMechanism::LOAD_MODORGANIZER: - return LoadMechanism::LOAD_MODORGANIZER; - case LoadMechanism::LOAD_SCRIPTEXTENDER: - return LoadMechanism::LOAD_SCRIPTEXTENDER; - case LoadMechanism::LOAD_PROXYDLL: - return LoadMechanism::LOAD_PROXYDLL; - } - throw std::runtime_error("invalid load mechanism"); +bool Settings::offlineMode() const +{ + return m_Settings.value("Settings/offline_mode", false).toBool(); } -void Settings::setupLoadMechanism() { m_LoadMechanism.activate(getLoadMechanism()); } - -bool Settings::useProxy() const { return m_Settings.value("Settings/use_proxy", false).toBool(); } - -bool Settings::displayForeign() const { return m_Settings.value("Settings/display_foreign", true).toBool(); } - -void Settings::setMotDHash(uint hash) { m_Settings.setValue("motd_hash", hash); } - -uint Settings::getMotDHash() const { return m_Settings.value("motd_hash", 0).toUInt(); } - -QVariant Settings::pluginSetting(const QString& pluginName, const QString& key) const { - auto iterPlugin = m_PluginSettings.find(pluginName); - if (iterPlugin == m_PluginSettings.end()) { - return QVariant(); - } - auto iterSetting = iterPlugin->find(key); - if (iterSetting == iterPlugin->end()) { - return QVariant(); - } - - return *iterSetting; +int Settings::logLevel() const +{ + return m_Settings.value("Settings/log_level", static_cast(LogLevel::Info)).toInt(); } -void Settings::setPluginSetting(const QString& pluginName, const QString& key, const QVariant& value) { - auto iterPlugin = m_PluginSettings.find(pluginName); - if (iterPlugin == m_PluginSettings.end()) { - throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); - } - - // store the new setting both in memory and in the ini - m_PluginSettings[pluginName][key] = value; - m_Settings.setValue("Plugins/" + pluginName + "/" + key, value); +int Settings::crashDumpsType() const +{ + return m_Settings.value("Settings/crash_dumps_type", static_cast(CrashDumpsType::Mini)).toInt(); } -QVariant Settings::pluginPersistent(const QString& pluginName, const QString& key, const QVariant& def) const { - if (!m_PluginSettings.contains(pluginName)) { - return def; - } - return m_Settings.value("PluginPersistance/" + pluginName + "/" + key, def); +int Settings::crashDumpsMax() const +{ + return m_Settings.value("Settings/crash_dumps_max", 5).toInt(); } -void Settings::setPluginPersistent(const QString& pluginName, const QString& key, const QVariant& value, bool sync) { - if (!m_PluginSettings.contains(pluginName)) { - throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); - } - m_Settings.setValue("PluginPersistance/" + pluginName + "/" + key, value); - if (sync) { - m_Settings.sync(); - } +void Settings::setNexusLogin(QString username, QString password) +{ + m_Settings.setValue("Settings/nexus_login", true); + m_Settings.setValue("Settings/nexus_username", username); + m_Settings.setValue("Settings/nexus_password", obfuscate(password)); } -QString Settings::language() { - QString result = m_Settings.value("Settings/language", "").toString(); - if (result.isEmpty()) { - QStringList languagePreferences = QLocale::system().uiLanguages(); - if (languagePreferences.length() > 0) { - // the users most favoritest language - result = languagePreferences.at(0); - } else { - // fallback system locale - result = QLocale::system().name(); - } - } - return result; +void Settings::setSteamLogin(QString username, QString password) +{ + if (username == "") { + m_Settings.remove("Settings/steam_username"); + password = ""; + } else { + m_Settings.setValue("Settings/steam_username", username); + } + if (password == "") { + m_Settings.remove("Settings/steam_password"); + } else { + m_Settings.setValue("Settings/steam_password", obfuscate(password)); + } } -void Settings::updateServers(const QList& servers) { - m_Settings.beginGroup("Servers"); - QStringList oldServerKeys = m_Settings.childKeys(); - - for (const ServerInfo& server : servers) { - if (!oldServerKeys.contains(server.name)) { - // not yet known server - QVariantMap newVal; - newVal["premium"] = server.premium; - newVal["preferred"] = server.preferred ? 1 : 0; - newVal["lastSeen"] = server.lastSeen; - newVal["downloadCount"] = 0; - newVal["downloadSpeed"] = 0.0; - - m_Settings.setValue(server.name, newVal); - } else { - QVariantMap data = m_Settings.value(server.name).toMap(); - data["lastSeen"] = server.lastSeen; - data["premium"] = server.premium; +LoadMechanism::EMechanism Settings::getLoadMechanism() const +{ + switch (m_Settings.value("Settings/load_mechanism").toInt()) { + case LoadMechanism::LOAD_MODORGANIZER: return LoadMechanism::LOAD_MODORGANIZER; + case LoadMechanism::LOAD_SCRIPTEXTENDER: return LoadMechanism::LOAD_SCRIPTEXTENDER; + case LoadMechanism::LOAD_PROXYDLL: return LoadMechanism::LOAD_PROXYDLL; + } + throw std::runtime_error("invalid load mechanism"); +} - m_Settings.setValue(server.name, data); - } - } - // clean up unavailable servers - QDate now = QDate::currentDate(); - for (const QString& key : m_Settings.childKeys()) { - QVariantMap val = m_Settings.value(key).toMap(); - QDate lastSeen = val["lastSeen"].toDate(); - if (lastSeen.daysTo(now) > 30) { - qDebug("removing server %s since it hasn't been available for downloads in over a month", qPrintable(key)); - m_Settings.remove(key); - } - } +void Settings::setupLoadMechanism() +{ + m_LoadMechanism.activate(getLoadMechanism()); +} - m_Settings.endGroup(); - m_Settings.sync(); +bool Settings::useProxy() const +{ + return m_Settings.value("Settings/use_proxy", false).toBool(); } -void Settings::addBlacklistPlugin(const QString& fileName) { - m_PluginBlacklist.insert(fileName); - writePluginBlacklist(); +bool Settings::displayForeign() const +{ + return m_Settings.value("Settings/display_foreign", true).toBool(); } -void Settings::writePluginBlacklist() { - m_Settings.beginWriteArray("pluginBlacklist"); - int idx = 0; - for (const QString& plugin : m_PluginBlacklist) { - m_Settings.setArrayIndex(idx++); - m_Settings.setValue("name", plugin); - } - - m_Settings.endArray(); -} - -void Settings::addLanguages(QComboBox* languageBox) { - std::vector> languages; - - QDirIterator langIter(QCoreApplication::applicationDirPath() + "/translations", QDir::Files); - QString pattern = ToQString(AppConfig::translationPrefix()) + "_([a-z]{2,3}(_[A-Z]{2,2})?).qm"; - QRegExp exp(pattern); - while (langIter.hasNext()) { - langIter.next(); - QString file = langIter.fileName(); - if (exp.exactMatch(file)) { - QString languageCode = exp.cap(1); - QLocale locale(languageCode); - QString languageString = - QString("%1 (%2)") - .arg(locale.nativeLanguageName()) - .arg(locale.nativeCountryName()); // QLocale::languageToString(locale.language()); - if (locale.language() == QLocale::Chinese) { - if (languageCode == "zh_TW") { - languageString = "Chinese (traditional)"; - } else { - languageString = "Chinese (simplified)"; - } - } - languages.push_back(std::make_pair(QString("%1").arg(languageString), exp.cap(1))); - // languageBox->addItem(QString("%1").arg(languageString), exp.cap(1)); - } - } - if (!languageBox->findText("English")) { - languages.push_back(std::make_pair(QString("English"), QString("en_US"))); - // languageBox->addItem("English", "en_US"); - } - std::sort(languages.begin(), languages.end()); - for (const auto& lang : languages) { - languageBox->addItem(lang.first, lang.second); - } +void Settings::setMotDHash(uint hash) +{ + m_Settings.setValue("motd_hash", hash); } -void Settings::addStyles(QComboBox* styleBox) { - styleBox->addItem("None", ""); - styleBox->addItem("Fusion", "Fusion"); - - QDirIterator langIter(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()), - QStringList("*.qss"), QDir::Files); - while (langIter.hasNext()) { - langIter.next(); - QString style = langIter.fileName(); - styleBox->addItem(style, style); - } +uint Settings::getMotDHash() const +{ + return m_Settings.value("motd_hash", 0).toUInt(); } -void Settings::resetDialogs() { QuestionBoxMemory::resetDialogs(); } - -void Settings::query(QWidget* parent) { - SettingsDialog dialog(parent); - - connect(&dialog, SIGNAL(resetDialogs()), this, SLOT(resetDialogs())); - - std::vector> tabs; +QVariant Settings::pluginSetting(const QString &pluginName, const QString &key) const +{ + auto iterPlugin = m_PluginSettings.find(pluginName); + if (iterPlugin == m_PluginSettings.end()) { + return QVariant(); + } + auto iterSetting = iterPlugin->find(key); + if (iterSetting == iterPlugin->end()) { + return QVariant(); + } - tabs.push_back(std::unique_ptr(new GeneralTab(this, dialog))); - tabs.push_back(std::unique_ptr(new PathsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new DiagnosticsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new NexusTab(this, dialog))); - tabs.push_back(std::unique_ptr(new SteamTab(this, dialog))); - tabs.push_back(std::unique_ptr(new PluginsTab(this, dialog))); - tabs.push_back(std::unique_ptr(new WorkaroundsTab(this, dialog))); - - if (dialog.exec() == QDialog::Accepted) { - // remember settings before change - QMap before; - m_Settings.beginGroup("Settings"); - for (auto k : m_Settings.allKeys()) - before[k] = m_Settings.value(k).toString(); - m_Settings.endGroup(); - - // transfer modified settings to configuration file - for (std::unique_ptr const& tab : tabs) { - tab->update(); - } - - // print "changed" settings - m_Settings.beginGroup("Settings"); - bool first_update = true; - for (auto k : m_Settings.allKeys()) - if (m_Settings.value(k).toString() != before[k] && !k.contains("username") && !k.contains("password")) { - if (first_update) { - qDebug("Changed settings:"); - first_update = false; - } - qDebug(" %s=%s", k.toUtf8().data(), m_Settings.value(k).toString().toUtf8().data()); - } - m_Settings.endGroup(); - } + return *iterSetting; } -Settings::SettingsTab::SettingsTab(Settings* m_parent, SettingsDialog& m_dialog) - : m_parent(m_parent), m_Settings(m_parent->m_Settings), m_dialog(m_dialog) {} - -Settings::SettingsTab::~SettingsTab() {} - -Settings::GeneralTab::GeneralTab(Settings* m_parent, SettingsDialog& m_dialog) - : Settings::SettingsTab(m_parent, m_dialog), m_languageBox(m_dialog.findChild("languageBox")), - m_styleBox(m_dialog.findChild("styleBox")), - m_compactBox(m_dialog.findChild("compactBox")), - m_showMetaBox(m_dialog.findChild("showMetaBox")), - m_usePrereleaseBox(m_dialog.findChild("usePrereleaseBox")) { - // FIXME I think 'addLanguages' lives in here not in parent - m_parent->addLanguages(m_languageBox); - { - QString languageCode = m_parent->language(); - int currentID = m_languageBox->findData(languageCode); - // I made a mess. :( Most languages are stored with only the iso country - // code (2 characters like "de") but chinese - // with the exact language variant (zh_TW) so I have to search for both - // variants - if (currentID == -1) { - currentID = m_languageBox->findData(languageCode.mid(0, 2)); - } - if (currentID != -1) { - m_languageBox->setCurrentIndex(currentID); - } - } - - // FIXME I think addStyles lives in here not in parent - m_parent->addStyles(m_styleBox); - { - int currentID = m_styleBox->findData(m_Settings.value("Settings/style", "").toString()); - if (currentID != -1) { - m_styleBox->setCurrentIndex(currentID); - } - } +void Settings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) +{ + auto iterPlugin = m_PluginSettings.find(pluginName); + if (iterPlugin == m_PluginSettings.end()) { + throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); + } - m_compactBox->setChecked(m_parent->compactDownloads()); - m_showMetaBox->setChecked(m_parent->metaDownloads()); - m_usePrereleaseBox->setChecked(m_parent->usePrereleases()); + // store the new setting both in memory and in the ini + m_PluginSettings[pluginName][key] = value; + m_Settings.setValue("Plugins/" + pluginName + "/" + key, value); } -void Settings::GeneralTab::update() { - QString oldLanguage = m_parent->language(); - QString newLanguage = m_languageBox->itemData(m_languageBox->currentIndex()).toString(); - if (newLanguage != oldLanguage) { - m_Settings.setValue("Settings/language", newLanguage); - emit m_parent->languageChanged(newLanguage); - } - - QString oldStyle = m_Settings.value("Settings/style", "").toString(); - QString newStyle = m_styleBox->itemData(m_styleBox->currentIndex()).toString(); - if (oldStyle != newStyle) { - m_Settings.setValue("Settings/style", newStyle); - emit m_parent->styleChanged(newStyle); - } - - m_Settings.setValue("Settings/compact_downloads", m_compactBox->isChecked()); - m_Settings.setValue("Settings/meta_downloads", m_showMetaBox->isChecked()); - m_Settings.setValue("Settings/use_prereleases", m_usePrereleaseBox->isChecked()); -} - -Settings::PathsTab::PathsTab(Settings* parent, SettingsDialog& dialog) - : SettingsTab(parent, dialog), m_baseDirEdit(m_dialog.findChild("baseDirEdit")), - m_downloadDirEdit(m_dialog.findChild("downloadDirEdit")), - m_modDirEdit(m_dialog.findChild("modDirEdit")), - m_cacheDirEdit(m_dialog.findChild("cacheDirEdit")), - m_profilesDirEdit(m_dialog.findChild("profilesDirEdit")), - m_overwriteDirEdit(m_dialog.findChild("overwriteDirEdit")) { - m_baseDirEdit->setText(m_parent->getBaseDirectory()); - - QString basePath = parent->getBaseDirectory(); - QDir baseDir(basePath); - for (const auto& dir : {std::make_pair(m_downloadDirEdit, m_parent->getDownloadDirectory(false)), - std::make_pair(m_modDirEdit, m_parent->getModDirectory(false)), - std::make_pair(m_cacheDirEdit, m_parent->getCacheDirectory(false)), - std::make_pair(m_profilesDirEdit, m_parent->getProfileDirectory(false)), - std::make_pair(m_overwriteDirEdit, m_parent->getOverwriteDirectory(false))}) { - QString storePath = baseDir.relativeFilePath(dir.second); - storePath = dir.second; - dir.first->setText(storePath); - } +QVariant Settings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const +{ + if (!m_PluginSettings.contains(pluginName)) { + return def; + } + return m_Settings.value("PluginPersistance/" + pluginName + "/" + key, def); } -void Settings::PathsTab::update() { - typedef std::tuple Directory; - - QString basePath = m_parent->getBaseDirectory(); - - for (const Directory& dir : - {Directory{m_downloadDirEdit->text(), "download_directory", AppConfig::downloadPath()}, - Directory{m_cacheDirEdit->text(), "cache_directory", AppConfig::cachePath()}, - Directory{m_modDirEdit->text(), "mod_directory", AppConfig::modsPath()}, - Directory{m_overwriteDirEdit->text(), "overwrite_directory", AppConfig::overwritePath()}, - Directory{m_profilesDirEdit->text(), "profiles_directory", AppConfig::profilesPath()}}) { - QString path, settingsKey; - std::wstring defaultName; - std::tie(path, settingsKey, defaultName) = dir; - - settingsKey = QString("Settings/%1").arg(settingsKey); - - QString realPath = path; - realPath.replace("%BASE_DIR%", m_baseDirEdit->text()); - - if (!QDir(realPath).exists()) { - if (!QDir().mkpath(realPath)) { - QMessageBox::warning(qApp->activeWindow(), tr("Error"), - tr("Failed to create \"%1\", you may not have the " - "necessary permission. path remains unchanged.") - .arg(realPath)); - } - } - - if (QFileInfo(realPath) != QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) { - m_Settings.setValue(settingsKey, path); - } else { - m_Settings.remove(settingsKey); - } - } +void Settings::setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) +{ + if (!m_PluginSettings.contains(pluginName)) { + throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); + } + m_Settings.setValue("PluginPersistance/" + pluginName + "/" + key, value); + if (sync) { + m_Settings.sync(); + } +} - if (QFileInfo(m_baseDirEdit->text()) != QFileInfo(qApp->property("dataPath").toString())) { - m_Settings.setValue("Settings/base_directory", m_baseDirEdit->text()); +QString Settings::language() +{ + QString result = m_Settings.value("Settings/language", "").toString(); + if (result.isEmpty()) { + QStringList languagePreferences = QLocale::system().uiLanguages(); + if (languagePreferences.length() > 0) { + // the users most favoritest language + result = languagePreferences.at(0); } else { - m_Settings.remove("Settings/base_directory"); + // fallback system locale + result = QLocale::system().name(); } + } + return result; } -Settings::DiagnosticsTab::DiagnosticsTab(Settings* m_parent, SettingsDialog& m_dialog) - : Settings::SettingsTab(m_parent, m_dialog), m_logLevelBox(m_dialog.findChild("logLevelBox")), - m_dumpsTypeBox(m_dialog.findChild("dumpsTypeBox")), - m_dumpsMaxEdit(m_dialog.findChild("dumpsMaxEdit")), - m_diagnosticsExplainedLabel(m_dialog.findChild("diagnosticsExplainedLabel")) { - m_logLevelBox->setCurrentIndex(m_parent->logLevel()); - m_dumpsTypeBox->setCurrentIndex(m_parent->crashDumpsType()); - m_dumpsMaxEdit->setValue(m_parent->crashDumpsMax()); - QString logsPath = qApp->property("dataPath").toString() + "/" + QString::fromStdWString(AppConfig::logPath()); - m_diagnosticsExplainedLabel->setText( - m_diagnosticsExplainedLabel->text() - .replace("LOGS_FULL_PATH", logsPath) - .replace("LOGS_DIR", QString::fromStdWString(AppConfig::logPath())) - .replace("DUMPS_FULL_PATH", QString::fromStdWString(OrganizerCore::crashDumpsPath())) - .replace("DUMPS_DIR", QString::fromStdWString(AppConfig::dumpsDir()))); -} - -void Settings::DiagnosticsTab::update() { - m_Settings.setValue("Settings/log_level", m_logLevelBox->currentIndex()); - m_Settings.setValue("Settings/crash_dumps_type", m_dumpsTypeBox->currentIndex()); - m_Settings.setValue("Settings/crash_dumps_max", m_dumpsMaxEdit->value()); -} - -Settings::NexusTab::NexusTab(Settings* parent, SettingsDialog& dialog) - : Settings::SettingsTab(parent, dialog), m_loginCheckBox(dialog.findChild("loginCheckBox")), - m_usernameEdit(dialog.findChild("usernameEdit")), - m_passwordEdit(dialog.findChild("passwordEdit")), - m_offlineBox(dialog.findChild("offlineBox")), m_proxyBox(dialog.findChild("proxyBox")), - m_knownServersList(dialog.findChild("knownServersList")), - m_preferredServersList(dialog.findChild("preferredServersList")) { - if (parent->automaticLoginEnabled()) { - m_loginCheckBox->setChecked(true); - m_usernameEdit->setText(m_Settings.value("Settings/nexus_username", "").toString()); - m_passwordEdit->setText(deObfuscate(m_Settings.value("Settings/nexus_password", "").toString())); - } - - m_offlineBox->setChecked(parent->offlineMode()); - m_proxyBox->setChecked(parent->useProxy()); +void Settings::updateServers(const QList &servers) +{ + m_Settings.beginGroup("Servers"); + QStringList oldServerKeys = m_Settings.childKeys(); - // display server preferences - m_Settings.beginGroup("Servers"); - for (const QString& key : m_Settings.childKeys()) { - QVariantMap val = m_Settings.value(key).toMap(); - QString type = val["premium"].toBool() ? "(premium)" : "(free)"; + for (const ServerInfo &server : servers) { + if (!oldServerKeys.contains(server.name)) { + // not yet known server + QVariantMap newVal; + newVal["premium"] = server.premium; + newVal["preferred"] = server.preferred ? 1 : 0; + newVal["lastSeen"] = server.lastSeen; + newVal["downloadCount"] = 0; + newVal["downloadSpeed"] = 0.0; - QString descriptor = key + " " + type; - if (val.contains("downloadSpeed") && val.contains("downloadCount") && (val["downloadCount"].toInt() > 0)) { - int bps = static_cast(val["downloadSpeed"].toDouble() / val["downloadCount"].toInt()); - descriptor += QString(" (%1 kbps)").arg(bps / 1024); - } - - QListWidgetItem* newItem = new QListWidgetItemEx(descriptor, Qt::UserRole + 1); + m_Settings.setValue(server.name, newVal); + } else { + QVariantMap data = m_Settings.value(server.name).toMap(); + data["lastSeen"] = server.lastSeen; + data["premium"] = server.premium; - newItem->setData(Qt::UserRole, key); - newItem->setData(Qt::UserRole + 1, val["preferred"].toInt()); - if (val["preferred"].toInt() > 0) { - m_preferredServersList->addItem(newItem); - } else { - m_knownServersList->addItem(newItem); - } - m_preferredServersList->sortItems(Qt::DescendingOrder); + m_Settings.setValue(server.name, data); } - m_Settings.endGroup(); -} + } -void Settings::NexusTab::update() { - if (m_loginCheckBox->isChecked()) { - m_Settings.setValue("Settings/nexus_login", true); - m_Settings.setValue("Settings/nexus_username", m_usernameEdit->text()); - m_Settings.setValue("Settings/nexus_password", obfuscate(m_passwordEdit->text())); - } else { - m_Settings.setValue("Settings/nexus_login", false); - m_Settings.remove("Settings/nexus_username"); - m_Settings.remove("Settings/nexus_password"); - } - m_Settings.setValue("Settings/offline_mode", m_offlineBox->isChecked()); - m_Settings.setValue("Settings/use_proxy", m_proxyBox->isChecked()); - - // store server preference - m_Settings.beginGroup("Servers"); - for (int i = 0; i < m_knownServersList->count(); ++i) { - QString key = m_knownServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = m_Settings.value(key).toMap(); - val["preferred"] = 0; - m_Settings.setValue(key, val); - } - int count = m_preferredServersList->count(); - for (int i = 0; i < count; ++i) { - QString key = m_preferredServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = m_Settings.value(key).toMap(); - val["preferred"] = count - i; - m_Settings.setValue(key, val); + // clean up unavailable servers + QDate now = QDate::currentDate(); + for (const QString &key : m_Settings.childKeys()) { + QVariantMap val = m_Settings.value(key).toMap(); + QDate lastSeen = val["lastSeen"].toDate(); + if (lastSeen.daysTo(now) > 30) { + qDebug("removing server %s since it hasn't been available for downloads in over a month", qPrintable(key)); + m_Settings.remove(key); } - m_Settings.endGroup(); -} + } -Settings::SteamTab::SteamTab(Settings* m_parent, SettingsDialog& m_dialog) - : Settings::SettingsTab(m_parent, m_dialog), m_steamUserEdit(m_dialog.findChild("steamUserEdit")), - m_steamPassEdit(m_dialog.findChild("steamPassEdit")) { - if (m_Settings.contains("Settings/steam_username")) { - m_steamUserEdit->setText(m_Settings.value("Settings/steam_username", "").toString()); - if (m_Settings.contains("Settings/steam_password")) { - m_steamPassEdit->setText(deObfuscate(m_Settings.value("Settings/steam_password", "").toString())); - } - } -} + m_Settings.endGroup(); -void Settings::SteamTab::update() { - // FIXME this should be inlined here? - m_parent->setSteamLogin(m_steamUserEdit->text(), m_steamPassEdit->text()); + m_Settings.sync(); } -Settings::PluginsTab::PluginsTab(Settings* m_parent, SettingsDialog& m_dialog) - : Settings::SettingsTab(m_parent, m_dialog), m_pluginsList(m_dialog.findChild("pluginsList")), - m_pluginBlacklistList(m_dialog.findChild("pluginBlacklist")) { - // display plugin settings - for (IPlugin* plugin : m_parent->m_Plugins) { - QListWidgetItem* listItem = new QListWidgetItem(plugin->name(), m_pluginsList); - listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin)); - listItem->setData(Qt::UserRole + 1, m_parent->m_PluginSettings[plugin->name()]); - listItem->setData(Qt::UserRole + 2, m_parent->m_PluginDescriptions[plugin->name()]); - m_pluginsList->addItem(listItem); - } - - // display plugin blacklist - for (const QString& pluginName : m_parent->m_PluginBlacklist) { - m_pluginBlacklistList->addItem(pluginName); - } +void Settings::addBlacklistPlugin(const QString &fileName) +{ + m_PluginBlacklist.insert(fileName); + writePluginBlacklist(); } -void Settings::PluginsTab::update() { - // transfer plugin settings to in-memory structure - for (int i = 0; i < m_pluginsList->count(); ++i) { - QListWidgetItem* item = m_pluginsList->item(i); - m_parent->m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap(); - } - // store plugin settings on disc - for (auto iterPlugins = m_parent->m_PluginSettings.begin(); iterPlugins != m_parent->m_PluginSettings.end(); - ++iterPlugins) { - for (auto iterSettings = iterPlugins->begin(); iterSettings != iterPlugins->end(); ++iterSettings) { - m_Settings.setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value()); - } - } +void Settings::writePluginBlacklist() +{ + m_Settings.beginWriteArray("pluginBlacklist"); + int idx = 0; + for (const QString &plugin : m_PluginBlacklist) { + m_Settings.setArrayIndex(idx++); + m_Settings.setValue("name", plugin); + } - // store plugin blacklist - m_parent->m_PluginBlacklist.clear(); - for (QListWidgetItem* item : m_pluginBlacklistList->findItems("*", Qt::MatchWildcard)) { - m_parent->m_PluginBlacklist.insert(item->text()); - } - m_parent->writePluginBlacklist(); + m_Settings.endArray(); } -Settings::WorkaroundsTab::WorkaroundsTab(Settings* m_parent, SettingsDialog& m_dialog) - : Settings::SettingsTab(m_parent, m_dialog), m_appIDEdit(m_dialog.findChild("appIDEdit")), - m_mechanismBox(m_dialog.findChild("mechanismBox")), - m_nmmVersionEdit(m_dialog.findChild("nmmVersionEdit")), - m_hideUncheckedBox(m_dialog.findChild("hideUncheckedBox")), - m_forceEnableBox(m_dialog.findChild("forceEnableBox")), - m_displayForeignBox(m_dialog.findChild("displayForeignBox")) { - m_appIDEdit->setText(m_parent->getSteamAppID()); - - LoadMechanism::EMechanism mechanismID = m_parent->getLoadMechanism(); - int index = 0; +void Settings::addLanguages(QComboBox *languageBox) +{ + std::vector> languages; - if (m_parent->m_LoadMechanism.isDirectLoadingSupported()) { - m_mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER); - if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) { - index = m_mechanismBox->count() - 1; + QDirIterator langIter(QCoreApplication::applicationDirPath() + "/translations", QDir::Files); + QString pattern = ToQString(AppConfig::translationPrefix()) + "_([a-z]{2,3}(_[A-Z]{2,2})?).qm"; + QRegExp exp(pattern); + while (langIter.hasNext()) { + langIter.next(); + QString file = langIter.fileName(); + if (exp.exactMatch(file)) { + QString languageCode = exp.cap(1); + QLocale locale(languageCode); + QString languageString = QString("%1 (%2)").arg(locale.nativeLanguageName()).arg(locale.nativeCountryName()); //QLocale::languageToString(locale.language()); + if (locale.language() == QLocale::Chinese) { + if (languageCode == "zh_TW") { + languageString = "Chinese (traditional)"; + } else { + languageString = "Chinese (simplified)"; } - } + } + languages.push_back(std::make_pair(QString("%1").arg(languageString), exp.cap(1))); + //languageBox->addItem(QString("%1").arg(languageString), exp.cap(1)); + } + } + if (!languageBox->findText("English")) { + languages.push_back(std::make_pair(QString("English"), QString("en_US"))); + //languageBox->addItem("English", "en_US"); + } + std::sort(languages.begin(), languages.end()); + for (const auto &lang : languages) { + languageBox->addItem(lang.first, lang.second); + } +} + +void Settings::addStyles(QComboBox *styleBox) +{ + styleBox->addItem("None", ""); + styleBox->addItem("Fusion", "Fusion"); + + QDirIterator langIter(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()), QStringList("*.qss"), QDir::Files); + while (langIter.hasNext()) { + langIter.next(); + QString style = langIter.fileName(); + styleBox->addItem(style, style); + } +} + +void Settings::resetDialogs() +{ + QuestionBoxMemory::resetDialogs(); +} + + +void Settings::query(QWidget *parent) +{ + SettingsDialog dialog(parent); + + connect(&dialog, SIGNAL(resetDialogs()), this, SLOT(resetDialogs())); + + std::vector> tabs; + + tabs.push_back(std::unique_ptr(new GeneralTab(this, dialog))); + tabs.push_back(std::unique_ptr(new PathsTab(this, dialog))); + tabs.push_back(std::unique_ptr(new DiagnosticsTab(this, dialog))); + tabs.push_back(std::unique_ptr(new NexusTab(this, dialog))); + tabs.push_back(std::unique_ptr(new SteamTab(this, dialog))); + tabs.push_back(std::unique_ptr(new PluginsTab(this, dialog))); + tabs.push_back(std::unique_ptr(new WorkaroundsTab(this, dialog))); + + if (dialog.exec() == QDialog::Accepted) { + // remember settings before change + QMap before; + m_Settings.beginGroup("Settings"); + for (auto k : m_Settings.allKeys()) + before[k] = m_Settings.value(k).toString(); + m_Settings.endGroup(); - if (m_parent->m_LoadMechanism.isScriptExtenderSupported()) { - m_mechanismBox->addItem(QObject::tr("Script Extender"), LoadMechanism::LOAD_SCRIPTEXTENDER); - if (mechanismID == LoadMechanism::LOAD_SCRIPTEXTENDER) { - index = m_mechanismBox->count() - 1; - } + // transfer modified settings to configuration file + for (std::unique_ptr const &tab: tabs) { + tab->update(); } - if (m_parent->m_LoadMechanism.isProxyDLLSupported()) { - m_mechanismBox->addItem(QObject::tr("Proxy DLL"), LoadMechanism::LOAD_PROXYDLL); - if (mechanismID == LoadMechanism::LOAD_PROXYDLL) { - index = m_mechanismBox->count() - 1; + // print "changed" settings + m_Settings.beginGroup("Settings"); + bool first_update = true; + for (auto k : m_Settings.allKeys()) + if (m_Settings.value(k).toString() != before[k] && !k.contains("username") && !k.contains("password")) + { + if (first_update) { + qDebug("Changed settings:"); + first_update = false; } + qDebug(" %s=%s", k.toUtf8().data(), m_Settings.value(k).toString().toUtf8().data()); + } + m_Settings.endGroup(); + } +} + +Settings::SettingsTab::SettingsTab(Settings *m_parent, SettingsDialog &m_dialog) + : m_parent(m_parent) + , m_Settings(m_parent->m_Settings) + , m_dialog(m_dialog) +{ +} + +Settings::SettingsTab::~SettingsTab() +{} + +Settings::GeneralTab::GeneralTab(Settings *m_parent, SettingsDialog &m_dialog) + : Settings::SettingsTab(m_parent, m_dialog) + , m_languageBox(m_dialog.findChild("languageBox")) + , m_styleBox(m_dialog.findChild("styleBox")) + , m_compactBox(m_dialog.findChild("compactBox")) + , m_showMetaBox(m_dialog.findChild("showMetaBox")) + , m_usePrereleaseBox(m_dialog.findChild("usePrereleaseBox")) +{ + // FIXME I think 'addLanguages' lives in here not in parent + m_parent->addLanguages(m_languageBox); + { + QString languageCode = m_parent->language(); + int currentID = m_languageBox->findData(languageCode); + // I made a mess. :( Most languages are stored with only the iso country + // code (2 characters like "de") but chinese + // with the exact language variant (zh_TW) so I have to search for both + // variants + if (currentID == -1) { + currentID = m_languageBox->findData(languageCode.mid(0, 2)); + } + if (currentID != -1) { + m_languageBox->setCurrentIndex(currentID); + } + } + + // FIXME I think addStyles lives in here not in parent + m_parent->addStyles(m_styleBox); + { + int currentID = m_styleBox->findData( + m_Settings.value("Settings/style", "").toString()); + if (currentID != -1) { + m_styleBox->setCurrentIndex(currentID); + } + } + + m_compactBox->setChecked(m_parent->compactDownloads()); + m_showMetaBox->setChecked(m_parent->metaDownloads()); + m_usePrereleaseBox->setChecked(m_parent->usePrereleases()); +} + +void Settings::GeneralTab::update() +{ + QString oldLanguage = m_parent->language(); + QString newLanguage = m_languageBox->itemData(m_languageBox->currentIndex()).toString(); + if (newLanguage != oldLanguage) { + m_Settings.setValue("Settings/language", newLanguage); + emit m_parent->languageChanged(newLanguage); + } + + QString oldStyle = m_Settings.value("Settings/style", "").toString(); + QString newStyle = m_styleBox->itemData(m_styleBox->currentIndex()).toString(); + if (oldStyle != newStyle) { + m_Settings.setValue("Settings/style", newStyle); + emit m_parent->styleChanged(newStyle); + } + + m_Settings.setValue("Settings/compact_downloads", m_compactBox->isChecked()); + m_Settings.setValue("Settings/meta_downloads", m_showMetaBox->isChecked()); + m_Settings.setValue("Settings/use_prereleases", m_usePrereleaseBox->isChecked()); +} + +Settings::PathsTab::PathsTab(Settings *parent, SettingsDialog &dialog) + : SettingsTab(parent, dialog) + , m_baseDirEdit(m_dialog.findChild("baseDirEdit")) + , m_downloadDirEdit(m_dialog.findChild("downloadDirEdit")) + , m_modDirEdit(m_dialog.findChild("modDirEdit")) + , m_cacheDirEdit(m_dialog.findChild("cacheDirEdit")) + , m_profilesDirEdit(m_dialog.findChild("profilesDirEdit")) + , m_overwriteDirEdit(m_dialog.findChild("overwriteDirEdit")) +{ + m_baseDirEdit->setText(m_parent->getBaseDirectory()); + + QString basePath = parent->getBaseDirectory(); + QDir baseDir(basePath); + for (const auto &dir : { + std::make_pair(m_downloadDirEdit, m_parent->getDownloadDirectory(false)), + std::make_pair(m_modDirEdit, m_parent->getModDirectory(false)), + std::make_pair(m_cacheDirEdit, m_parent->getCacheDirectory(false)), + std::make_pair(m_profilesDirEdit, m_parent->getProfileDirectory(false)), + std::make_pair(m_overwriteDirEdit, m_parent->getOverwriteDirectory(false)) + }) { + QString storePath = baseDir.relativeFilePath(dir.second); + storePath = dir.second; + dir.first->setText(storePath); + } +} + +void Settings::PathsTab::update() +{ + typedef std::tuple Directory; + + QString basePath = m_parent->getBaseDirectory(); + + for (const Directory &dir :{ + Directory{m_downloadDirEdit->text(), "download_directory", AppConfig::downloadPath()}, + Directory{m_cacheDirEdit->text(), "cache_directory", AppConfig::cachePath()}, + Directory{m_modDirEdit->text(), "mod_directory", AppConfig::modsPath()}, + Directory{m_overwriteDirEdit->text(), "overwrite_directory", AppConfig::overwritePath()}, + Directory{m_profilesDirEdit->text(), "profiles_directory", AppConfig::profilesPath()} + }) { + QString path, settingsKey; + std::wstring defaultName; + std::tie(path, settingsKey, defaultName) = dir; + + settingsKey = QString("Settings/%1").arg(settingsKey); + + QString realPath = path; + realPath.replace("%BASE_DIR%", m_baseDirEdit->text()); + + if (!QDir(realPath).exists()) { + if (!QDir().mkpath(realPath)) { + QMessageBox::warning(qApp->activeWindow(), tr("Error"), + tr("Failed to create \"%1\", you may not have the " + "necessary permission. path remains unchanged.") + .arg(realPath)); + } + } + + if (QFileInfo(realPath) + != QFileInfo(basePath + "/" + QString::fromStdWString(defaultName))) { + m_Settings.setValue(settingsKey, path); + } else { + m_Settings.remove(settingsKey); + } + } + + if (QFileInfo(m_baseDirEdit->text()) != + QFileInfo(qApp->property("dataPath").toString())) { + m_Settings.setValue("Settings/base_directory", m_baseDirEdit->text()); + } else { + m_Settings.remove("Settings/base_directory"); + } +} + +Settings::DiagnosticsTab::DiagnosticsTab(Settings *m_parent, SettingsDialog &m_dialog) + : Settings::SettingsTab(m_parent, m_dialog) + , m_logLevelBox(m_dialog.findChild("logLevelBox")) + , m_dumpsTypeBox(m_dialog.findChild("dumpsTypeBox")) + , m_dumpsMaxEdit(m_dialog.findChild("dumpsMaxEdit")) + , m_diagnosticsExplainedLabel(m_dialog.findChild("diagnosticsExplainedLabel")) +{ + m_logLevelBox->setCurrentIndex(m_parent->logLevel()); + m_dumpsTypeBox->setCurrentIndex(m_parent->crashDumpsType()); + m_dumpsMaxEdit->setValue(m_parent->crashDumpsMax()); + QString logsPath = qApp->property("dataPath").toString() + + "/" + QString::fromStdWString(AppConfig::logPath()); + m_diagnosticsExplainedLabel->setText( + m_diagnosticsExplainedLabel->text() + .replace("LOGS_FULL_PATH", logsPath) + .replace("LOGS_DIR", QString::fromStdWString(AppConfig::logPath())) + .replace("DUMPS_FULL_PATH", QString::fromStdWString(OrganizerCore::crashDumpsPath())) + .replace("DUMPS_DIR", QString::fromStdWString(AppConfig::dumpsDir())) + ); +} + +void Settings::DiagnosticsTab::update() +{ + m_Settings.setValue("Settings/log_level", m_logLevelBox->currentIndex()); + m_Settings.setValue("Settings/crash_dumps_type", m_dumpsTypeBox->currentIndex()); + m_Settings.setValue("Settings/crash_dumps_max", m_dumpsMaxEdit->value()); +} + +Settings::NexusTab::NexusTab(Settings *parent, SettingsDialog &dialog) + : Settings::SettingsTab(parent, dialog) + , m_loginCheckBox(dialog.findChild("loginCheckBox")) + , m_usernameEdit(dialog.findChild("usernameEdit")) + , m_passwordEdit(dialog.findChild("passwordEdit")) + , m_offlineBox(dialog.findChild("offlineBox")) + , m_proxyBox(dialog.findChild("proxyBox")) + , m_knownServersList(dialog.findChild("knownServersList")) + , m_preferredServersList( + dialog.findChild("preferredServersList")) +{ + if (parent->automaticLoginEnabled()) { + m_loginCheckBox->setChecked(true); + m_usernameEdit->setText(m_Settings.value("Settings/nexus_username", "").toString()); + m_passwordEdit->setText(deObfuscate(m_Settings.value("Settings/nexus_password", "").toString())); + } + + m_offlineBox->setChecked(parent->offlineMode()); + m_proxyBox->setChecked(parent->useProxy()); + + // display server preferences + m_Settings.beginGroup("Servers"); + for (const QString &key : m_Settings.childKeys()) { + QVariantMap val = m_Settings.value(key).toMap(); + QString type = val["premium"].toBool() ? "(premium)" : "(free)"; + + QString descriptor = key + " " + type; + if (val.contains("downloadSpeed") && val.contains("downloadCount") && (val["downloadCount"].toInt() > 0)) { + int bps = static_cast(val["downloadSpeed"].toDouble() / val["downloadCount"].toInt()); + descriptor += QString(" (%1 kbps)").arg(bps / 1024); + } + + QListWidgetItem *newItem = new QListWidgetItemEx(descriptor, Qt::UserRole + 1); + + newItem->setData(Qt::UserRole, key); + newItem->setData(Qt::UserRole + 1, val["preferred"].toInt()); + if (val["preferred"].toInt() > 0) { + m_preferredServersList->addItem(newItem); + } else { + m_knownServersList->addItem(newItem); } - - m_mechanismBox->setCurrentIndex(index); - - m_nmmVersionEdit->setText(m_parent->getNMMVersion()); - m_hideUncheckedBox->setChecked(m_parent->hideUncheckedPlugins()); - m_forceEnableBox->setChecked(m_parent->forceEnableCoreFiles()); - m_displayForeignBox->setChecked(m_parent->displayForeign()); + m_preferredServersList->sortItems(Qt::DescendingOrder); + } + m_Settings.endGroup(); } -void Settings::WorkaroundsTab::update() { - if (m_appIDEdit->text() != m_parent->m_GamePlugin->steamAPPId()) { - m_Settings.setValue("Settings/app_id", m_appIDEdit->text()); - } else { - m_Settings.remove("Settings/app_id"); - } - m_Settings.setValue("Settings/load_mechanism", m_mechanismBox->itemData(m_mechanismBox->currentIndex()).toInt()); - m_Settings.setValue("Settings/nmm_version", m_nmmVersionEdit->text()); - m_Settings.setValue("Settings/hide_unchecked_plugins", m_hideUncheckedBox->isChecked()); - m_Settings.setValue("Settings/force_enable_core_files", m_forceEnableBox->isChecked()); - m_Settings.setValue("Settings/display_foreign", m_displayForeignBox->isChecked()); +void Settings::NexusTab::update() +{ + if (m_loginCheckBox->isChecked()) { + m_Settings.setValue("Settings/nexus_login", true); + m_Settings.setValue("Settings/nexus_username", m_usernameEdit->text()); + m_Settings.setValue("Settings/nexus_password", obfuscate(m_passwordEdit->text())); + } else { + m_Settings.setValue("Settings/nexus_login", false); + m_Settings.remove("Settings/nexus_username"); + m_Settings.remove("Settings/nexus_password"); + } + m_Settings.setValue("Settings/offline_mode", m_offlineBox->isChecked()); + m_Settings.setValue("Settings/use_proxy", m_proxyBox->isChecked()); + + // store server preference + m_Settings.beginGroup("Servers"); + for (int i = 0; i < m_knownServersList->count(); ++i) { + QString key = m_knownServersList->item(i)->data(Qt::UserRole).toString(); + QVariantMap val = m_Settings.value(key).toMap(); + val["preferred"] = 0; + m_Settings.setValue(key, val); + } + int count = m_preferredServersList->count(); + for (int i = 0; i < count; ++i) { + QString key = m_preferredServersList->item(i)->data(Qt::UserRole).toString(); + QVariantMap val = m_Settings.value(key).toMap(); + val["preferred"] = count - i; + m_Settings.setValue(key, val); + } + m_Settings.endGroup(); +} + +Settings::SteamTab::SteamTab(Settings *m_parent, SettingsDialog &m_dialog) + : Settings::SettingsTab(m_parent, m_dialog) + , m_steamUserEdit(m_dialog.findChild("steamUserEdit")) + , m_steamPassEdit(m_dialog.findChild("steamPassEdit")) +{ + if (m_Settings.contains("Settings/steam_username")) { + m_steamUserEdit->setText(m_Settings.value("Settings/steam_username", "").toString()); + if (m_Settings.contains("Settings/steam_password")) { + m_steamPassEdit->setText(deObfuscate(m_Settings.value("Settings/steam_password", "").toString())); + } + } +} + +void Settings::SteamTab::update() +{ + //FIXME this should be inlined here? + m_parent->setSteamLogin(m_steamUserEdit->text(), m_steamPassEdit->text()); +} + +Settings::PluginsTab::PluginsTab(Settings *m_parent, SettingsDialog &m_dialog) + : Settings::SettingsTab(m_parent, m_dialog) + , m_pluginsList(m_dialog.findChild("pluginsList")) + , m_pluginBlacklistList(m_dialog.findChild("pluginBlacklist")) +{ + // display plugin settings + for (IPlugin *plugin : m_parent->m_Plugins) { + QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), m_pluginsList); + listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin)); + listItem->setData(Qt::UserRole + 1, m_parent->m_PluginSettings[plugin->name()]); + listItem->setData(Qt::UserRole + 2, m_parent->m_PluginDescriptions[plugin->name()]); + m_pluginsList->addItem(listItem); + } + + // display plugin blacklist + for (const QString &pluginName : m_parent->m_PluginBlacklist) { + m_pluginBlacklistList->addItem(pluginName); + } +} + +void Settings::PluginsTab::update() +{ + // transfer plugin settings to in-memory structure + for (int i = 0; i < m_pluginsList->count(); ++i) { + QListWidgetItem *item = m_pluginsList->item(i); + m_parent->m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap(); + } + // store plugin settings on disc + for (auto iterPlugins = m_parent->m_PluginSettings.begin(); iterPlugins != m_parent->m_PluginSettings.end(); ++iterPlugins) { + for (auto iterSettings = iterPlugins->begin(); iterSettings != iterPlugins->end(); ++iterSettings) { + m_Settings.setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value()); + } + } + + // store plugin blacklist + m_parent->m_PluginBlacklist.clear(); + for (QListWidgetItem *item : m_pluginBlacklistList->findItems("*", Qt::MatchWildcard)) { + m_parent->m_PluginBlacklist.insert(item->text()); + } + m_parent->writePluginBlacklist(); +} + +Settings::WorkaroundsTab::WorkaroundsTab(Settings *m_parent, + SettingsDialog &m_dialog) + : Settings::SettingsTab(m_parent, m_dialog) + , m_appIDEdit(m_dialog.findChild("appIDEdit")) + , m_mechanismBox(m_dialog.findChild("mechanismBox")) + , m_nmmVersionEdit(m_dialog.findChild("nmmVersionEdit")) + , m_hideUncheckedBox(m_dialog.findChild("hideUncheckedBox")) + , m_forceEnableBox(m_dialog.findChild("forceEnableBox")) + , m_displayForeignBox(m_dialog.findChild("displayForeignBox")) +{ + m_appIDEdit->setText(m_parent->getSteamAppID()); + + LoadMechanism::EMechanism mechanismID = m_parent->getLoadMechanism(); + int index = 0; + + if (m_parent->m_LoadMechanism.isDirectLoadingSupported()) { + m_mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER); + if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) { + index = m_mechanismBox->count() - 1; + } + } + + if (m_parent->m_LoadMechanism.isScriptExtenderSupported()) { + m_mechanismBox->addItem(QObject::tr("Script Extender"), LoadMechanism::LOAD_SCRIPTEXTENDER); + if (mechanismID == LoadMechanism::LOAD_SCRIPTEXTENDER) { + index = m_mechanismBox->count() - 1; + } + } + + if (m_parent->m_LoadMechanism.isProxyDLLSupported()) { + m_mechanismBox->addItem(QObject::tr("Proxy DLL"), LoadMechanism::LOAD_PROXYDLL); + if (mechanismID == LoadMechanism::LOAD_PROXYDLL) { + index = m_mechanismBox->count() - 1; + } + } + + m_mechanismBox->setCurrentIndex(index); + + m_nmmVersionEdit->setText(m_parent->getNMMVersion()); + m_hideUncheckedBox->setChecked(m_parent->hideUncheckedPlugins()); + m_forceEnableBox->setChecked(m_parent->forceEnableCoreFiles()); + m_displayForeignBox->setChecked(m_parent->displayForeign()); + +} + +void Settings::WorkaroundsTab::update() +{ + if (m_appIDEdit->text() != m_parent->m_GamePlugin->steamAPPId()) { + m_Settings.setValue("Settings/app_id", m_appIDEdit->text()); + } else { + m_Settings.remove("Settings/app_id"); + } + m_Settings.setValue("Settings/load_mechanism", m_mechanismBox->itemData(m_mechanismBox->currentIndex()).toInt()); + m_Settings.setValue("Settings/nmm_version", m_nmmVersionEdit->text()); + m_Settings.setValue("Settings/hide_unchecked_plugins", m_hideUncheckedBox->isChecked()); + m_Settings.setValue("Settings/force_enable_core_files", m_forceEnableBox->isChecked()); + m_Settings.setValue("Settings/display_foreign", m_displayForeignBox->isChecked()); } diff --git a/src/settings.h b/src/settings.h index 015a2c13..ae38223f 100644 --- a/src/settings.h +++ b/src/settings.h @@ -46,9 +46,9 @@ class QLabel; struct ServerInfo; namespace MOBase { -class IPlugin; -class IPluginGame; -} // namespace MOBase + class IPlugin; + class IPluginGame; +} class SettingsDialog; @@ -56,441 +56,455 @@ class SettingsDialog; * manages the settings for Mod Organizer. The settings are not cached * inside the class but read/written directly from/to disc **/ -class Settings : public QObject { +class Settings : public QObject +{ - Q_OBJECT + Q_OBJECT public: - /** - * @brief constructor - **/ - Settings(const QSettings& settingsSource); - - virtual ~Settings(); - - static Settings& instance(); - - /** - * unregister all plugins from settings - */ - void clearPlugins(); - - /** - * @brief register plugin to be configurable - * @param plugin the plugin to register - * @return true if the plugin may be registered, false if it is blacklisted - */ - void registerPlugin(MOBase::IPlugin* plugin); - - /** - * displays a SettingsDialog that allows the user to change settings. If the - * user accepts the changes, the settings are immediately written - **/ - void query(QWidget* parent); - - /** - * set up the settings for the specified plugins - **/ - void addPluginSettings(const std::vector& plugins); - - /** - * @return true if the user wants unchecked plugins (esp, esm) should be hidden from - * the virtual dat adirectory - **/ - bool hideUncheckedPlugins() const; - - /** - * @return true if files of the core game are forced-enabled so the user can't accidentally disable them - */ - bool forceEnableCoreFiles() const; - - /** - * @brief register download speed - * @param url complete download url - * @param bytesPerSecond download size in bytes per second - */ - void setDownloadSpeed(const QString& serverName, int bytesPerSecond); - - /** - * the steam appid is assigned by the steam platform to each product sold there. - * The appid may differ between different versions of a game so it may be impossible - * for Mod Organizer to automatically recognize it, though usually it does - * @return the steam appid for the game - **/ - QString getSteamAppID() const; - - /** - * retrieves the base directory under which the other directories usually - * reside - */ - QString getBaseDirectory() const; - - /** - * retrieve the directory where downloads are stored (with native separators) - **/ - QString getDownloadDirectory(bool resolve = true) const; - - /** - * retrieve a sorted list of preferred servers - */ - std::map getPreferredServers(); - - /** - * retrieve the directory where mods are stored (with native separators) - **/ - QString getModDirectory(bool resolve = true) const; - - /** - * returns the version of nmm to impersonate when connecting to nexus - **/ - QString getNMMVersion() const; - - /** - * retrieve the directory where the web cache is stored (with native separators) - **/ - QString getCacheDirectory(bool resolve = true) const; - - /** - * retrieve the directory where profiles stored (with native separators) - **/ - QString getProfileDirectory(bool resolve = true) const; - - /** - * retrieve the directory were new files are stored that can't be assigned - * to a mod (with native separators) - */ - QString getOverwriteDirectory(bool resolve = true) const; - - /** - * @return true if the user has set up automatic login to nexus - **/ - bool automaticLoginEnabled() const; - - /** - * @brief retrieve the login information for nexus - * - * @param username (out) receives the user name for nexus - * @param password (out) received the password for nexus - * @return true if automatic login is active, false otherwise - **/ - bool getNexusLogin(QString& username, QString& password) const; - - /** - * @brief retrieve the login information for steam - * - * @param username (out) receives the user name for nexus - * @param password (out) received the password for nexus - * @return true if a username has been specified, false otherwise - **/ - bool getSteamLogin(QString& username, QString& password) const; - - /** - * @return true if the user disabled internet features - */ - bool offlineMode() const; - - /** - * @return true if the user chose compact downloads - */ - bool compactDownloads() const; - - /** - * @return true if the user chose meta downloads - */ - bool metaDownloads() const; - - /** - * @return the configured log level - */ - int logLevel() const; - - /** - * @return the configured crash dumps type - */ - int crashDumpsType() const; - - /** - * @return the configured crash dumps max - */ - int crashDumpsMax() const; - - /** - * @brief set the nexus login information - * - * @param username username - * @param password password - */ - void setNexusLogin(QString username, QString password); - - /** - * @brief set the steam login information - * - * @param username username - * @param password password - */ - void setSteamLogin(QString username, QString password); - - /** - * @return the load mechanism to be used - **/ - LoadMechanism::EMechanism getLoadMechanism() const; - - /** - * @brief activate the load mechanism selected by the user - **/ - void setupLoadMechanism(); - - /** - * @return true if the user configured the use of a network proxy - */ - bool useProxy() const; - - /** - * @return true if the user wants to see non-official plugins installed outside MO in his mod list - */ - bool displayForeign() const; - - /** - * @brief sets the new motd hash - **/ - void setMotDHash(uint hash); - - /** - * @return hash of the last displayed message of the day - **/ - uint getMotDHash() const; - - /** - * @brief allows direct access to the wrapped QSettings object - * @return the wrapped QSettings object - */ - QSettings& directInterface() { return m_Settings; } - - /** - * @brief retrieve a setting for one of the installed plugins - * @param pluginName name of the plugin - * @param key name of the setting to retrieve - * @return the requested value as a QVariant - * @note an invalid QVariant is returned if the the plugin/setting is not declared - */ - QVariant pluginSetting(const QString& pluginName, const QString& key) const; - - /** - * @brief set a setting for one of the installed mods - * @param pluginName name of the plugin - * @param key name of the setting to change - * @param value the new value to set - * @throw an exception is thrown if pluginName is invalid - */ - void setPluginSetting(const QString& pluginName, const QString& key, const QVariant& value); - - /** - * @brief retrieve a persistent value for a plugin - * @param pluginName name of the plugin to store data for - * @param key id of the value to retrieve - * @param def default value to return if the value is not set - * @return the requested value - */ - QVariant pluginPersistent(const QString& pluginName, const QString& key, const QVariant& def) const; - - /** - * @brief set a persistent value for a plugin - * @param pluginName name of the plugin to store data for - * @param key id of the value to retrieve - * @param value value to set - * @throw an exception is thrown if pluginName is invalid - */ - void setPluginPersistent(const QString& pluginName, const QString& key, const QVariant& value, bool sync); - - /** - * @return short code of the configured language (corresponding to the translation files) - */ - QString language(); - - /** - * @brief updates the list of known servers - * @param list of servers from a recent query - */ - void updateServers(const QList& servers); - - /** - * @brief add a plugin that is to be blacklisted - * @param fileName name of the plugin to blacklist - */ - void addBlacklistPlugin(const QString& fileName); - - /** - * @brief test if a plugin is blacklisted and shouldn't be loaded - * @param fileName name of the plugin - * @return true if the file is blacklisted - */ - bool pluginBlacklisted(const QString& fileName) const; - - /** - * @return all loaded MO plugins - */ - std::vector plugins() const { return m_Plugins; } - - bool usePrereleases() const; - - /** - * @brief register MO as the handler for nxm links - * @param force set to true to enforce the registration dialog to show up, - * even if the user said earlier not to - */ - void registerAsNXMHandler(bool force); + + /** + * @brief constructor + **/ + Settings(const QSettings &settingsSource); + + virtual ~Settings(); + + static Settings &instance(); + + /** + * unregister all plugins from settings + */ + void clearPlugins(); + + /** + * @brief register plugin to be configurable + * @param plugin the plugin to register + * @return true if the plugin may be registered, false if it is blacklisted + */ + void registerPlugin(MOBase::IPlugin *plugin); + + /** + * displays a SettingsDialog that allows the user to change settings. If the + * user accepts the changes, the settings are immediately written + **/ + void query(QWidget *parent); + + /** + * set up the settings for the specified plugins + **/ + void addPluginSettings(const std::vector &plugins); + + /** + * @return true if the user wants unchecked plugins (esp, esm) should be hidden from + * the virtual dat adirectory + **/ + bool hideUncheckedPlugins() const; + + /** + * @return true if files of the core game are forced-enabled so the user can't accidentally disable them + */ + bool forceEnableCoreFiles() const; + + /** + * @brief register download speed + * @param url complete download url + * @param bytesPerSecond download size in bytes per second + */ + void setDownloadSpeed(const QString &serverName, int bytesPerSecond); + + /** + * the steam appid is assigned by the steam platform to each product sold there. + * The appid may differ between different versions of a game so it may be impossible + * for Mod Organizer to automatically recognize it, though usually it does + * @return the steam appid for the game + **/ + QString getSteamAppID() const; + + /** + * retrieves the base directory under which the other directories usually + * reside + */ + QString getBaseDirectory() const; + + /** + * retrieve the directory where downloads are stored (with native separators) + **/ + QString getDownloadDirectory(bool resolve = true) const; + + /** + * retrieve a sorted list of preferred servers + */ + std::map getPreferredServers(); + + /** + * retrieve the directory where mods are stored (with native separators) + **/ + QString getModDirectory(bool resolve = true) const; + + /** + * returns the version of nmm to impersonate when connecting to nexus + **/ + QString getNMMVersion() const; + + /** + * retrieve the directory where the web cache is stored (with native separators) + **/ + QString getCacheDirectory(bool resolve = true) const; + + /** + * retrieve the directory where profiles stored (with native separators) + **/ + QString getProfileDirectory(bool resolve = true) const; + + /** + * retrieve the directory were new files are stored that can't be assigned + * to a mod (with native separators) + */ + QString getOverwriteDirectory(bool resolve = true) const; + + /** + * @return true if the user has set up automatic login to nexus + **/ + bool automaticLoginEnabled() const; + + /** + * @brief retrieve the login information for nexus + * + * @param username (out) receives the user name for nexus + * @param password (out) received the password for nexus + * @return true if automatic login is active, false otherwise + **/ + bool getNexusLogin(QString &username, QString &password) const; + + /** + * @brief retrieve the login information for steam + * + * @param username (out) receives the user name for nexus + * @param password (out) received the password for nexus + * @return true if a username has been specified, false otherwise + **/ + bool getSteamLogin(QString &username, QString &password) const; + + /** + * @return true if the user disabled internet features + */ + bool offlineMode() const; + + /** + * @return true if the user chose compact downloads + */ + bool compactDownloads() const; + + /** + * @return true if the user chose meta downloads + */ + bool metaDownloads() const; + + /** + * @return the configured log level + */ + int logLevel() const; + + /** + * @return the configured crash dumps type + */ + int crashDumpsType() const; + + /** + * @return the configured crash dumps max + */ + int crashDumpsMax() const; + + /** + * @brief set the nexus login information + * + * @param username username + * @param password password + */ + void setNexusLogin(QString username, QString password); + + /** + * @brief set the steam login information + * + * @param username username + * @param password password + */ + void setSteamLogin(QString username, QString password); + + /** + * @return the load mechanism to be used + **/ + LoadMechanism::EMechanism getLoadMechanism() const; + + /** + * @brief activate the load mechanism selected by the user + **/ + void setupLoadMechanism(); + + /** + * @return true if the user configured the use of a network proxy + */ + bool useProxy() const; + + /** + * @return true if the user wants to see non-official plugins installed outside MO in his mod list + */ + bool displayForeign() const; + + /** + * @brief sets the new motd hash + **/ + void setMotDHash(uint hash); + + /** + * @return hash of the last displayed message of the day + **/ + uint getMotDHash() const; + + /** + * @brief allows direct access to the wrapped QSettings object + * @return the wrapped QSettings object + */ + QSettings &directInterface() { return m_Settings; } + + /** + * @brief retrieve a setting for one of the installed plugins + * @param pluginName name of the plugin + * @param key name of the setting to retrieve + * @return the requested value as a QVariant + * @note an invalid QVariant is returned if the the plugin/setting is not declared + */ + QVariant pluginSetting(const QString &pluginName, const QString &key) const; + + /** + * @brief set a setting for one of the installed mods + * @param pluginName name of the plugin + * @param key name of the setting to change + * @param value the new value to set + * @throw an exception is thrown if pluginName is invalid + */ + void setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value); + + /** + * @brief retrieve a persistent value for a plugin + * @param pluginName name of the plugin to store data for + * @param key id of the value to retrieve + * @param def default value to return if the value is not set + * @return the requested value + */ + QVariant pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const; + + /** + * @brief set a persistent value for a plugin + * @param pluginName name of the plugin to store data for + * @param key id of the value to retrieve + * @param value value to set + * @throw an exception is thrown if pluginName is invalid + */ + void setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync); + + /** + * @return short code of the configured language (corresponding to the translation files) + */ + QString language(); + + /** + * @brief updates the list of known servers + * @param list of servers from a recent query + */ + void updateServers(const QList &servers); + + /** + * @brief add a plugin that is to be blacklisted + * @param fileName name of the plugin to blacklist + */ + void addBlacklistPlugin(const QString &fileName); + + /** + * @brief test if a plugin is blacklisted and shouldn't be loaded + * @param fileName name of the plugin + * @return true if the file is blacklisted + */ + bool pluginBlacklisted(const QString &fileName) const; + + /** + * @return all loaded MO plugins + */ + std::vector plugins() const { return m_Plugins; } + + bool usePrereleases() const; + + /** + * @brief register MO as the handler for nxm links + * @param force set to true to enforce the registration dialog to show up, + * even if the user said earlier not to + */ + void registerAsNXMHandler(bool force); public slots: - void managedGameChanged(MOBase::IPluginGame const* gamePlugin); + void managedGameChanged(MOBase::IPluginGame const *gamePlugin); private: - static QString obfuscate(const QString& password); - static QString deObfuscate(const QString& password); - - void addLanguages(QComboBox* languageBox); - void addStyles(QComboBox* styleBox); - void readPluginBlacklist(); - void writePluginBlacklist(); - QString getConfigurablePath(const QString& key, const QString& def, bool resolve) const; - - class SettingsTab { - public: - SettingsTab(Settings* m_parent, SettingsDialog& m_dialog); - virtual ~SettingsTab(); - - virtual void update() = 0; - - protected: - Settings* m_parent; - QSettings& m_Settings; - SettingsDialog& m_dialog; - }; - - /** Display/store the configuration in the 'general' tab of the settings dialogue */ - class GeneralTab : public SettingsTab { - public: - GeneralTab(Settings* m_parent, SettingsDialog& m_dialog); - - void update(); - - private: - QComboBox* m_languageBox; - QComboBox* m_styleBox; - QCheckBox* m_compactBox; - QCheckBox* m_showMetaBox; - QCheckBox* m_usePrereleaseBox; - }; - - class PathsTab : public SettingsTab { - public: - PathsTab(Settings* parent, SettingsDialog& dialog); - - void update(); - - private: - QLineEdit* m_baseDirEdit; - QLineEdit* m_downloadDirEdit; - QLineEdit* m_modDirEdit; - QLineEdit* m_cacheDirEdit; - QLineEdit* m_profilesDirEdit; - QLineEdit* m_overwriteDirEdit; - }; - - class DiagnosticsTab : public SettingsTab { - public: - DiagnosticsTab(Settings* parent, SettingsDialog& dialog); - - void update(); - - private: - QComboBox* m_logLevelBox; - QComboBox* m_dumpsTypeBox; - QSpinBox* m_dumpsMaxEdit; - QLabel* m_diagnosticsExplainedLabel; - }; - - /** Display/store the configuration in the 'nexus' tab of the settings dialogue */ - class NexusTab : public SettingsTab { - public: - NexusTab(Settings* m_parent, SettingsDialog& m_dialog); - - void update(); - - private: - QCheckBox* m_loginCheckBox; - QLineEdit* m_usernameEdit; - QLineEdit* m_passwordEdit; - QCheckBox* m_offlineBox; - QCheckBox* m_proxyBox; - QListWidget* m_knownServersList; - QListWidget* m_preferredServersList; - }; - - /** Display/store the configuration in the 'steam' tab of the settings dialogue */ - class SteamTab : public SettingsTab { - public: - SteamTab(Settings* m_parent, SettingsDialog& m_dialog); - - void update(); - - private: - QLineEdit* m_steamUserEdit; - QLineEdit* m_steamPassEdit; - }; - - /** Display/store the configuration in the 'plugins' tab of the settings dialogue */ - class PluginsTab : public SettingsTab { - public: - PluginsTab(Settings* m_parent, SettingsDialog& m_dialog); - - void update(); - - private: - QListWidget* m_pluginsList; - QListWidget* m_pluginBlacklistList; - }; - - /** Display/store the configuration in the 'workarounds' tab of the settings dialogue */ - class WorkaroundsTab : public SettingsTab { - public: - WorkaroundsTab(Settings* m_parent, SettingsDialog& m_dialog); - - void update(); - - private: - QLineEdit* m_appIDEdit; - QComboBox* m_mechanismBox; - QLineEdit* m_nmmVersionEdit; - QCheckBox* m_hideUncheckedBox; - QCheckBox* m_forceEnableBox; - QCheckBox* m_displayForeignBox; - }; + + static QString obfuscate(const QString &password); + static QString deObfuscate(const QString &password); + + void addLanguages(QComboBox *languageBox); + void addStyles(QComboBox *styleBox); + void readPluginBlacklist(); + void writePluginBlacklist(); + QString getConfigurablePath(const QString &key, const QString &def, bool resolve) const; + + class SettingsTab + { + public: + SettingsTab(Settings *m_parent, SettingsDialog &m_dialog); + virtual ~SettingsTab(); + + virtual void update() = 0; + + protected: + Settings *m_parent; + QSettings &m_Settings; + SettingsDialog &m_dialog; + + }; + + /** Display/store the configuration in the 'general' tab of the settings dialogue */ + class GeneralTab : public SettingsTab + { + public: + GeneralTab(Settings *m_parent, SettingsDialog &m_dialog); + + void update(); + + private: + QComboBox *m_languageBox; + QComboBox *m_styleBox; + QCheckBox *m_compactBox; + QCheckBox *m_showMetaBox; + QCheckBox *m_usePrereleaseBox; + }; + + class PathsTab : public SettingsTab + { + public: + PathsTab(Settings *parent, SettingsDialog &dialog); + + void update(); + + private: + QLineEdit *m_baseDirEdit; + QLineEdit *m_downloadDirEdit; + QLineEdit *m_modDirEdit; + QLineEdit *m_cacheDirEdit; + QLineEdit *m_profilesDirEdit; + QLineEdit *m_overwriteDirEdit; + }; + + class DiagnosticsTab : public SettingsTab + { + public: + DiagnosticsTab(Settings *parent, SettingsDialog &dialog); + + void update(); + + private: + QComboBox *m_logLevelBox; + QComboBox *m_dumpsTypeBox; + QSpinBox *m_dumpsMaxEdit; + QLabel *m_diagnosticsExplainedLabel; + }; + + /** Display/store the configuration in the 'nexus' tab of the settings dialogue */ + class NexusTab : public SettingsTab + { + public: + NexusTab(Settings *m_parent, SettingsDialog &m_dialog); + + void update(); + + private: + QCheckBox *m_loginCheckBox; + QLineEdit *m_usernameEdit; + QLineEdit *m_passwordEdit; + QCheckBox *m_offlineBox; + QCheckBox *m_proxyBox; + QListWidget *m_knownServersList; + QListWidget *m_preferredServersList; + }; + + /** Display/store the configuration in the 'steam' tab of the settings dialogue */ + class SteamTab : public SettingsTab + { + public: + SteamTab(Settings *m_parent, SettingsDialog &m_dialog); + + void update(); + + private: + QLineEdit *m_steamUserEdit; + QLineEdit *m_steamPassEdit; + }; + + /** Display/store the configuration in the 'plugins' tab of the settings dialogue */ + class PluginsTab : public SettingsTab + { + public: + PluginsTab(Settings *m_parent, SettingsDialog &m_dialog); + + void update(); + + private: + QListWidget *m_pluginsList; + QListWidget *m_pluginBlacklistList; + }; + + /** Display/store the configuration in the 'workarounds' tab of the settings dialogue */ + class WorkaroundsTab : public SettingsTab + { + public: + WorkaroundsTab(Settings *m_parent, SettingsDialog &m_dialog); + + void update(); + + private: + QLineEdit *m_appIDEdit; + QComboBox *m_mechanismBox; + QLineEdit *m_nmmVersionEdit; + QCheckBox *m_hideUncheckedBox; + QCheckBox *m_forceEnableBox; + QCheckBox *m_displayForeignBox; + }; private slots: - void resetDialogs(); + void resetDialogs(); signals: - 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; - MOBase::IPluginGame const* m_GamePlugin; + static Settings *s_Instance; + + MOBase::IPluginGame const *m_GamePlugin; + + QSettings m_Settings; - QSettings m_Settings; + LoadMechanism m_LoadMechanism; - LoadMechanism m_LoadMechanism; + std::vector m_Plugins; - std::vector m_Plugins; + QMap> m_PluginSettings; + QMap> m_PluginDescriptions; - QMap> m_PluginSettings; - QMap> m_PluginDescriptions; + QSet m_PluginBlacklist; - QSet m_PluginBlacklist; }; #endif // WORKAROUNDS_H diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index fd487c86..fb9433a6 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -19,14 +19,14 @@ along with Mod Organizer. If not, see . #include "settingsdialog.h" +#include "ui_settingsdialog.h" #include "categoriesdialog.h" #include "helper.h" -#include "instancemanager.h" -#include "iplugingame.h" -#include "nexusinterface.h" #include "noeditdelegate.h" +#include "iplugingame.h" #include "settings.h" -#include "ui_settingsdialog.h" +#include "instancemanager.h" +#include "nexusinterface.h" #include #include @@ -36,187 +36,222 @@ along with Mod Organizer. If not, see . #define WIN32_LEAN_AND_MEAN #include + using namespace MOBase; -SettingsDialog::SettingsDialog(QWidget* parent) - : TutorableDialog("SettingsDialog", parent), ui(new Ui::SettingsDialog) { - ui->setupUi(this); - QShortcut* delShortcut = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); - connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem())); -} +SettingsDialog::SettingsDialog(QWidget *parent) + : TutorableDialog("SettingsDialog", parent) + , ui(new Ui::SettingsDialog) +{ + ui->setupUi(this); -SettingsDialog::~SettingsDialog() { delete ui; } + QShortcut *delShortcut + = new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist); + connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem())); +} -void SettingsDialog::addPlugins(const std::vector& plugins) { - for (IPlugin* plugin : plugins) { - ui->pluginsList->addItem(plugin->name()); - } +SettingsDialog::~SettingsDialog() +{ + delete ui; } -void SettingsDialog::accept() { - QString newModPath = ui->modDirEdit->text(); - newModPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - - if ((QDir::fromNativeSeparators(newModPath) != - QDir::fromNativeSeparators(Settings::instance().getModDirectory(true))) && - (QMessageBox::question(nullptr, 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. " - "There is no way to undo this unless you backed up your " - "profiles manually. Proceed?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)) { - return; - } +void SettingsDialog::addPlugins(const std::vector &plugins) +{ + for (IPlugin *plugin : plugins) { + ui->pluginsList->addItem(plugin->name()); + } +} - storeSettings(ui->pluginsList->currentItem()); - TutorableDialog::accept(); +void SettingsDialog::accept() +{ + QString newModPath = ui->modDirEdit->text(); + newModPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); + + if ((QDir::fromNativeSeparators(newModPath) != + QDir::fromNativeSeparators( + Settings::instance().getModDirectory(true))) && + (QMessageBox::question( + nullptr, 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. " + "There is no way to undo this unless you backed up your " + "profiles manually. Proceed?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::No)) { + return; + } + + storeSettings(ui->pluginsList->currentItem()); + TutorableDialog::accept(); } -void SettingsDialog::on_loginCheckBox_toggled(bool checked) { - QLineEdit* usernameEdit = findChild("usernameEdit"); - QLineEdit* passwordEdit = findChild("passwordEdit"); - if (checked) { - passwordEdit->setEnabled(true); - usernameEdit->setEnabled(true); - } else { - passwordEdit->setEnabled(false); - usernameEdit->setEnabled(false); - } + +void SettingsDialog::on_loginCheckBox_toggled(bool checked) +{ + QLineEdit *usernameEdit = findChild("usernameEdit"); + QLineEdit *passwordEdit = findChild("passwordEdit"); + if (checked) { + passwordEdit->setEnabled(true); + usernameEdit->setEnabled(true); + } else { + passwordEdit->setEnabled(false); + usernameEdit->setEnabled(false); + } } -void SettingsDialog::on_categoriesBtn_clicked() { - CategoriesDialog dialog(this); - if (dialog.exec() == QDialog::Accepted) { - dialog.commitChanges(); - } +void SettingsDialog::on_categoriesBtn_clicked() +{ + CategoriesDialog dialog(this); + if (dialog.exec() == QDialog::Accepted) { + dialog.commitChanges(); + } } -void SettingsDialog::on_bsaDateBtn_clicked() { - IPluginGame const* game = qApp->property("managed_game").value(); - QDir dir = game->dataDirectory(); +void SettingsDialog::on_bsaDateBtn_clicked() +{ + IPluginGame const *game + = qApp->property("managed_game").value(); + QDir dir = game->dataDirectory(); - Helper::backdateBSAs(qApp->applicationDirPath().toStdWString(), dir.absolutePath().toStdWString()); + Helper::backdateBSAs(qApp->applicationDirPath().toStdWString(), + dir.absolutePath().toStdWString()); } -void SettingsDialog::on_browseBaseDirBtn_clicked() { - QString temp = QFileDialog::getExistingDirectory(this, tr("Select base directory"), ui->baseDirEdit->text()); - if (!temp.isEmpty()) { - ui->baseDirEdit->setText(temp); - } +void SettingsDialog::on_browseBaseDirBtn_clicked() +{ + QString temp = QFileDialog::getExistingDirectory( + this, tr("Select base directory"), ui->baseDirEdit->text()); + if (!temp.isEmpty()) { + ui->baseDirEdit->setText(temp); + } } -void SettingsDialog::on_browseDownloadDirBtn_clicked() { - QString searchPath = ui->downloadDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); +void SettingsDialog::on_browseDownloadDirBtn_clicked() +{ + QString searchPath = ui->downloadDirEdit->text(); + searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(this, tr("Select download directory"), searchPath); - if (!temp.isEmpty()) { - ui->downloadDirEdit->setText(temp); - } + QString temp = QFileDialog::getExistingDirectory(this, tr("Select download directory"), searchPath); + if (!temp.isEmpty()) { + ui->downloadDirEdit->setText(temp); + } } -void SettingsDialog::on_browseModDirBtn_clicked() { - QString searchPath = ui->modDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); +void SettingsDialog::on_browseModDirBtn_clicked() +{ + QString searchPath = ui->modDirEdit->text(); + searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), searchPath); - if (!temp.isEmpty()) { - ui->modDirEdit->setText(temp); - } + QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), searchPath); + if (!temp.isEmpty()) { + ui->modDirEdit->setText(temp); + } } -void SettingsDialog::on_browseCacheDirBtn_clicked() { - QString searchPath = ui->cacheDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); +void SettingsDialog::on_browseCacheDirBtn_clicked() +{ + QString searchPath = ui->cacheDirEdit->text(); + searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(this, tr("Select cache directory"), searchPath); - if (!temp.isEmpty()) { - ui->cacheDirEdit->setText(temp); - } + QString temp = QFileDialog::getExistingDirectory(this, tr("Select cache directory"), searchPath); + if (!temp.isEmpty()) { + ui->cacheDirEdit->setText(temp); + } } -void SettingsDialog::on_browseProfilesDirBtn_clicked() { - QString searchPath = ui->profilesDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); +void SettingsDialog::on_browseProfilesDirBtn_clicked() +{ + QString searchPath = ui->profilesDirEdit->text(); + searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(this, tr("Select profiles directory"), searchPath); - if (!temp.isEmpty()) { - ui->profilesDirEdit->setText(temp); - } + QString temp = QFileDialog::getExistingDirectory(this, tr("Select profiles directory"), searchPath); + if (!temp.isEmpty()) { + ui->profilesDirEdit->setText(temp); + } } -void SettingsDialog::on_browseOverwriteDirBtn_clicked() { - QString searchPath = ui->overwriteDirEdit->text(); - searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); +void SettingsDialog::on_browseOverwriteDirBtn_clicked() +{ + QString searchPath = ui->overwriteDirEdit->text(); + searchPath.replace("%BASE_DIR%", ui->baseDirEdit->text()); - QString temp = QFileDialog::getExistingDirectory(this, tr("Select overwrite directory"), searchPath); - if (!temp.isEmpty()) { - ui->overwriteDirEdit->setText(temp); - } + QString temp = QFileDialog::getExistingDirectory(this, tr("Select overwrite directory"), searchPath); + if (!temp.isEmpty()) { + ui->overwriteDirEdit->setText(temp); + } } -void SettingsDialog::on_resetDialogsButton_clicked() { - if (QMessageBox::question( - this, tr("Confirm?"), - tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - emit resetDialogs(); - } +void SettingsDialog::on_resetDialogsButton_clicked() +{ + if (QMessageBox::question(this, tr("Confirm?"), + tr("This will make all dialogs show up again where you checked the \"Remember selection\"-box. Continue?"), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + emit resetDialogs(); + } } -void SettingsDialog::storeSettings(QListWidgetItem* pluginItem) { - if (pluginItem != nullptr) { - QMap settings = pluginItem->data(Qt::UserRole + 1).toMap(); +void SettingsDialog::storeSettings(QListWidgetItem *pluginItem) +{ + if (pluginItem != nullptr) { + QMap settings = pluginItem->data(Qt::UserRole + 1).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); - } - - pluginItem->setData(Qt::UserRole + 1, settings); + 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); } + + pluginItem->setData(Qt::UserRole + 1, settings); + } } -void SettingsDialog::on_pluginsList_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous) { - storeSettings(previous); - - ui->pluginSettingsList->clear(); - IPlugin* plugin = static_cast(current->data(Qt::UserRole).value()); - ui->authorLabel->setText(plugin->author()); - ui->versionLabel->setText(plugin->version().canonicalString()); - ui->descriptionLabel->setText(plugin->description()); - - QMap settings = current->data(Qt::UserRole + 1).toMap(); - QMap descriptions = current->data(Qt::UserRole + 2).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; - QString description; - { - auto descriptionIter = descriptions.find(iter.key()); - if (descriptionIter != descriptions.end()) { - description = descriptionIter->toString(); - } - } - - ui->pluginSettingsList->setItemDelegateForColumn(0, new NoEditDelegate()); - newItem->setData(1, Qt::DisplayRole, value); - newItem->setData(1, Qt::EditRole, value); - newItem->setToolTip(1, description); - - newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); - ui->pluginSettingsList->addTopLevelItem(newItem); +void SettingsDialog::on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) +{ + storeSettings(previous); + + ui->pluginSettingsList->clear(); + IPlugin *plugin = static_cast(current->data(Qt::UserRole).value()); + ui->authorLabel->setText(plugin->author()); + ui->versionLabel->setText(plugin->version().canonicalString()); + ui->descriptionLabel->setText(plugin->description()); + + QMap settings = current->data(Qt::UserRole + 1).toMap(); + QMap descriptions = current->data(Qt::UserRole + 2).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; + QString description; + { + auto descriptionIter = descriptions.find(iter.key()); + if (descriptionIter != descriptions.end()) { + description = descriptionIter->toString(); + } } + + ui->pluginSettingsList->setItemDelegateForColumn(0, new NoEditDelegate()); + newItem->setData(1, Qt::DisplayRole, value); + newItem->setData(1, Qt::EditRole, value); + newItem->setToolTip(1, description); + + newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); + ui->pluginSettingsList->addTopLevelItem(newItem); + } } -void SettingsDialog::deleteBlacklistItem() { ui->pluginBlacklist->takeItem(ui->pluginBlacklist->currentIndex().row()); } +void SettingsDialog::deleteBlacklistItem() +{ + ui->pluginBlacklist->takeItem(ui->pluginBlacklist->currentIndex().row()); +} -void SettingsDialog::on_associateButton_clicked() { Settings::instance().registerAsNXMHandler(false); } +void SettingsDialog::on_associateButton_clicked() +{ + Settings::instance().registerAsNXMHandler(false); +} -void SettingsDialog::on_clearCacheButton_clicked() { - QDir(Settings::instance().getCacheDirectory()).removeRecursively(); - NexusInterface::instance()->clearCache(); +void SettingsDialog::on_clearCacheButton_clicked() +{ + QDir(Settings::instance().getCacheDirectory()).removeRecursively(); + NexusInterface::instance()->clearCache(); } + diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 6b95dde2..e9d995d3 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -21,12 +21,12 @@ along with Mod Organizer. If not, see . #define WORKAROUNDDIALOG_H #include "tutorabledialog.h" +#include #include #include -#include namespace Ui { -class SettingsDialog; + class SettingsDialog; } /** @@ -34,57 +34,62 @@ class SettingsDialog; * settings managed by the "Settings" class, this offers a button to open the * CategoriesDialog **/ -class SettingsDialog : public MOBase::TutorableDialog { +class SettingsDialog : public MOBase::TutorableDialog +{ Q_OBJECT public: - explicit SettingsDialog(QWidget* parent = 0); - ~SettingsDialog(); + explicit SettingsDialog(QWidget *parent = 0); + ~SettingsDialog(); - void addPlugins(const std::vector& plugins); + void addPlugins(const std::vector &plugins); public slots: - virtual void accept(); + virtual void accept(); signals: - void resetDialogs(); + void resetDialogs(); private: - void storeSettings(QListWidgetItem* pluginItem); + + void storeSettings(QListWidgetItem *pluginItem); private slots: - void on_loginCheckBox_toggled(bool checked); + void on_loginCheckBox_toggled(bool checked); - void on_categoriesBtn_clicked(); + void on_categoriesBtn_clicked(); - void on_bsaDateBtn_clicked(); + void on_bsaDateBtn_clicked(); - void on_browseDownloadDirBtn_clicked(); + void on_browseDownloadDirBtn_clicked(); - void on_browseModDirBtn_clicked(); + void on_browseModDirBtn_clicked(); - void on_browseCacheDirBtn_clicked(); + void on_browseCacheDirBtn_clicked(); - void on_resetDialogsButton_clicked(); + void on_resetDialogsButton_clicked(); - void on_pluginsList_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous); + void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - void deleteBlacklistItem(); + void deleteBlacklistItem(); - void on_associateButton_clicked(); + void on_associateButton_clicked(); - void on_clearCacheButton_clicked(); + void on_clearCacheButton_clicked(); - void on_browseBaseDirBtn_clicked(); + void on_browseBaseDirBtn_clicked(); - void on_browseOverwriteDirBtn_clicked(); + void on_browseOverwriteDirBtn_clicked(); - void on_browseProfilesDirBtn_clicked(); + void on_browseProfilesDirBtn_clicked(); private: - Ui::SettingsDialog* ui; + Ui::SettingsDialog *ui; + }; + + #endif // WORKAROUNDDIALOG_H diff --git a/src/shared/appconfig.cpp b/src/shared/appconfig.cpp index ebb72aba..49edcbcc 100644 --- a/src/shared/appconfig.cpp +++ b/src/shared/appconfig.cpp @@ -22,12 +22,12 @@ along with Mod Organizer. If not, see . namespace AppConfig { #define PARWSTRING wstring -#define APPPARAM(partype, parid, value) \ - partype parid() { return value; } +#define APPPARAM(partype, parid, value) partype parid () { return value; } #include "appconfig.inc" namespace MOShared { #undef PARWSTRING #undef APPPARAM + } } // namespace MOShared diff --git a/src/shared/appconfig.h b/src/shared/appconfig.h index b2dda14a..b32043e5 100644 --- a/src/shared/appconfig.h +++ b/src/shared/appconfig.h @@ -24,13 +24,15 @@ along with Mod Organizer. If not, see . namespace AppConfig { + #define PARWSTRING wstring -#define APPPARAM(partype, parid, value) partype parid(); +#define APPPARAM(partype, parid, value) partype parid (); #include "appconfig.inc" namespace MOShared { #undef PARWSTRING #undef APPPARAM + } } // namespace MOShared diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp index 2e25a1a3..cebf270e 100644 --- a/src/shared/directoryentry.cpp +++ b/src/shared/directoryentry.cpp @@ -18,19 +18,20 @@ along with Mod Organizer. If not, see . */ #include "directoryentry.h" -#include "error_report.h" -#include "leaktrace.h" #include "windows_error.h" +#include "leaktrace.h" +#include "error_report.h" +#include #include #include -#include #define WIN32_LEAN_AND_MEAN #include -#include -#include +#include #include +#include #include -#include +#include + namespace MOShared { @@ -39,576 +40,671 @@ static const int MAXPATH_UNICODE = 32767; class OriginConnection { public: - typedef int Index; - static const int INVALID_INDEX = INT_MIN; + + typedef int Index; + static const int INVALID_INDEX = INT_MIN; public: - OriginConnection() : m_NextID(0) { LEAK_TRACE; } - - ~OriginConnection() { LEAK_UNTRACE; } - - FilesOrigin& createOrigin(const std::wstring& originName, const std::wstring& directory, int priority, - boost::shared_ptr fileRegister, - boost::shared_ptr originConnection) { - int newID = createID(); - m_Origins[newID] = FilesOrigin(newID, originName, directory, priority, fileRegister, originConnection); - m_OriginsNameMap[originName] = newID; - m_OriginsPriorityMap[priority] = newID; - return m_Origins[newID]; - } - bool exists(const std::wstring& name) { return m_OriginsNameMap.find(name) != m_OriginsNameMap.end(); } + OriginConnection() + : m_NextID(0) + { + LEAK_TRACE; + } - FilesOrigin& getByID(Index ID) { return m_Origins[ID]; } + ~OriginConnection() + { + LEAK_UNTRACE; + } - FilesOrigin& getByName(const std::wstring& name) { - std::map::iterator iter = m_OriginsNameMap.find(name); - if (iter != m_OriginsNameMap.end()) { - return m_Origins[iter->second]; - } else { - std::ostringstream stream; - stream << "invalid origin name: " << ToString(name, false); - throw std::runtime_error(stream.str()); - } + FilesOrigin& createOrigin(const std::wstring &originName, const std::wstring &directory, int priority, + boost::shared_ptr fileRegister, boost::shared_ptr originConnection) { + int newID = createID(); + m_Origins[newID] = FilesOrigin(newID, originName, directory, priority, fileRegister, originConnection); + m_OriginsNameMap[originName] = newID; + m_OriginsPriorityMap[priority] = newID; + return m_Origins[newID]; + } + + bool exists(const std::wstring &name) { + return m_OriginsNameMap.find(name) != m_OriginsNameMap.end(); + } + + FilesOrigin &getByID(Index ID) { + return m_Origins[ID]; + } + + FilesOrigin &getByName(const std::wstring &name) { + std::map::iterator iter = m_OriginsNameMap.find(name); + if (iter != m_OriginsNameMap.end()) { + return m_Origins[iter->second]; + } else { + std::ostringstream stream; + stream << "invalid origin name: " << ToString(name, false); + throw std::runtime_error(stream.str()); } + } - void changePriorityLookup(int oldPriority, int newPriority) { - auto iter = m_OriginsPriorityMap.find(oldPriority); - if (iter != m_OriginsPriorityMap.end()) { - Index idx = iter->second; - m_OriginsPriorityMap.erase(iter); - m_OriginsPriorityMap[newPriority] = idx; - } + void changePriorityLookup(int oldPriority, int newPriority) + { + auto iter = m_OriginsPriorityMap.find(oldPriority); + if (iter != m_OriginsPriorityMap.end()) { + Index idx = iter->second; + m_OriginsPriorityMap.erase(iter); + m_OriginsPriorityMap[newPriority] = idx; } + } - void changeNameLookup(const std::wstring& oldName, const std::wstring& newName) { - auto iter = m_OriginsNameMap.find(oldName); - if (iter != m_OriginsNameMap.end()) { - Index idx = iter->second; - m_OriginsNameMap.erase(iter); - m_OriginsNameMap[newName] = idx; - } else { - log("failed to change name lookup from %ls to %ls", oldName.c_str(), newName.c_str()); - } + void changeNameLookup(const std::wstring &oldName, const std::wstring &newName) + { + auto iter = m_OriginsNameMap.find(oldName); + if (iter != m_OriginsNameMap.end()) { + Index idx = iter->second; + m_OriginsNameMap.erase(iter); + m_OriginsNameMap[newName] = idx; + } else { + log("failed to change name lookup from %ls to %ls", oldName.c_str(), newName.c_str()); } + } private: - Index createID() { return m_NextID++; } + + Index createID() { + return m_NextID++; + } private: - Index m_NextID; - std::map m_Origins; - std::map m_OriginsNameMap; - std::map m_OriginsPriorityMap; + Index m_NextID; + + std::map m_Origins; + std::map m_OriginsNameMap; + std::map m_OriginsPriorityMap; + }; + // // FilesOrigin // -void FilesOrigin::enable(bool enabled, time_t notAfter) { - if (!enabled) { - std::set copy = m_Files; - m_FileRegister.lock()->removeOriginMulti(copy, m_ID, notAfter); - m_Files.clear(); - } - m_Disabled = !enabled; + +void FilesOrigin::enable(bool enabled, time_t notAfter) +{ + if (!enabled) { + std::set copy = m_Files; + m_FileRegister.lock()->removeOriginMulti(copy, m_ID, notAfter); + m_Files.clear(); + } + m_Disabled = !enabled; } -void FilesOrigin::removeFile(FileEntry::Index index) { - auto iter = m_Files.find(index); - if (iter != m_Files.end()) { - m_Files.erase(iter); - } + +void FilesOrigin::removeFile(FileEntry::Index index) +{ + auto iter = m_Files.find(index); + if (iter != m_Files.end()) { + m_Files.erase(iter); + } } -static std::wstring tail(const std::wstring& source, const size_t count) { - if (count >= source.length()) { - return source; - } - return source.substr(source.length() - count); + +static std::wstring tail(const std::wstring &source, const size_t count) +{ + if (count >= source.length()) { + return source; + } + + return source.substr(source.length() - count); } -FilesOrigin::FilesOrigin() : m_ID(0), m_Disabled(false), m_Name(), m_Path(), m_Priority(0) { LEAK_TRACE; } -FilesOrigin::FilesOrigin(const FilesOrigin& reference) - : m_ID(reference.m_ID), m_Disabled(reference.m_Disabled), m_Name(reference.m_Name), m_Path(reference.m_Path), - m_Priority(reference.m_Priority), m_FileRegister(reference.m_FileRegister), - m_OriginConnection(reference.m_OriginConnection) { - LEAK_TRACE; +FilesOrigin::FilesOrigin() + : m_ID(0), m_Disabled(false), m_Name(), m_Path(), m_Priority(0) +{ + LEAK_TRACE; } -FilesOrigin::FilesOrigin(int 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) { - LEAK_TRACE; +FilesOrigin::FilesOrigin(const FilesOrigin &reference) + : m_ID(reference.m_ID) + , m_Disabled(reference.m_Disabled) + , m_Name(reference.m_Name) + , m_Path(reference.m_Path) + , m_Priority(reference.m_Priority) + , m_FileRegister(reference.m_FileRegister) + , m_OriginConnection(reference.m_OriginConnection) +{ + LEAK_TRACE; +} + + +FilesOrigin::FilesOrigin(int 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) +{ + LEAK_TRACE; } -FilesOrigin::~FilesOrigin() { LEAK_UNTRACE; } +FilesOrigin::~FilesOrigin() +{ + LEAK_UNTRACE; +} -void FilesOrigin::setPriority(int priority) { - m_OriginConnection.lock()->changePriorityLookup(m_Priority, priority); - m_Priority = priority; +void FilesOrigin::setPriority(int priority) +{ + m_OriginConnection.lock()->changePriorityLookup(m_Priority, priority); + + m_Priority = priority; } -void FilesOrigin::setName(const std::wstring& name) { - m_OriginConnection.lock()->changeNameLookup(m_Name, name); - // change path too - if (tail(m_Path, m_Name.length()) == m_Name) { - m_Path = m_Path.substr(0, m_Path.length() - m_Name.length()).append(name); - } - m_Name = name; + +void FilesOrigin::setName(const std::wstring &name) +{ + m_OriginConnection.lock()->changeNameLookup(m_Name, name); + // change path too + if (tail(m_Path, m_Name.length()) == m_Name) { + m_Path = m_Path.substr(0, m_Path.length() - m_Name.length()).append(name); + } + m_Name = name; } -std::vector FilesOrigin::getFiles() const { - std::vector result; - for (FileEntry::Index fileIdx : m_Files) - if (FileEntry::Ptr p = m_FileRegister.lock()->getFile(fileIdx)) - result.push_back(p); +std::vector FilesOrigin::getFiles() const +{ + std::vector result; + for (FileEntry::Index fileIdx : m_Files) + if (FileEntry::Ptr p = m_FileRegister.lock()->getFile(fileIdx)) + result.push_back(p); - return result; + return result; } + // // FileEntry // -void FileEntry::addOrigin(int origin, FILETIME fileTime, const std::wstring& archive) { - m_LastAccessed = time(nullptr); - if (m_Parent != nullptr) { - m_Parent->propagateOrigin(origin); - } - if (m_Origin == -1) { - m_Origin = origin; - m_FileTime = fileTime; - m_Archive = archive; - } else if ((m_Parent != nullptr) && - (m_Parent->getOriginByID(origin).getPriority() > m_Parent->getOriginByID(m_Origin).getPriority()) && - (archive.size() == 0 || m_Archive.size() > 0)) { - if (std::find_if(m_Alternatives.begin(), m_Alternatives.end(), - [&](const std::pair& i) -> bool { return i.first == m_Origin; }) == - m_Alternatives.end()) { - m_Alternatives.push_back(std::pair(m_Origin, m_Archive)); - } - m_Origin = origin; - m_FileTime = fileTime; - m_Archive = archive; - } else { - bool found = false; - if (m_Origin == origin) { - // already an origin - return; - } - for (std::vector>::iterator iter = m_Alternatives.begin(); - iter != m_Alternatives.end(); ++iter) { - if (iter->first == origin) { - // already an origin - return; - } - if ((m_Parent != nullptr) && - (m_Parent->getOriginByID(iter->first).getPriority() < m_Parent->getOriginByID(origin).getPriority())) { - m_Alternatives.insert(iter, std::pair(origin, archive)); - found = true; - break; - } - } - if (!found) { - m_Alternatives.push_back(std::pair(origin, archive)); - } +void FileEntry::addOrigin(int origin, FILETIME fileTime, const std::wstring &archive) +{ + m_LastAccessed = time(nullptr); + if (m_Parent != nullptr) { + m_Parent->propagateOrigin(origin); + } + if (m_Origin == -1) { + m_Origin = origin; + m_FileTime = fileTime; + m_Archive = archive; + } else if ((m_Parent != nullptr) + && (m_Parent->getOriginByID(origin).getPriority() > m_Parent->getOriginByID(m_Origin).getPriority()) + && (archive.size() == 0 || m_Archive.size() > 0 )) { + if (std::find_if(m_Alternatives.begin(), m_Alternatives.end(), [&](const std::pair &i) -> bool { return i.first == m_Origin; }) == m_Alternatives.end()) { + m_Alternatives.push_back(std::pair(m_Origin, m_Archive)); + } + m_Origin = origin; + m_FileTime = fileTime; + m_Archive = archive; + } else { + bool found = false; + if (m_Origin == origin) { + // already an origin + return; + } + for (std::vector>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) { + if (iter->first == origin) { + // already an origin + return; + } + if ((m_Parent != nullptr) + && (m_Parent->getOriginByID(iter->first).getPriority() < m_Parent->getOriginByID(origin).getPriority())) { + m_Alternatives.insert(iter, std::pair(origin, archive)); + found = true; + break; + } + } + if (!found) { + m_Alternatives.push_back(std::pair(origin, archive)); } + } } -bool FileEntry::removeOrigin(int origin) { - if (m_Origin == origin) { - if (!m_Alternatives.empty()) { - // find alternative with the highest priority - std::vector>::iterator currentIter = m_Alternatives.begin(); - for (std::vector>::iterator iter = m_Alternatives.begin(); - iter != m_Alternatives.end(); ++iter) { - if ((m_Parent->getOriginByID(iter->first).getPriority() > - m_Parent->getOriginByID(currentIter->first).getPriority()) && - (iter->first != origin)) { - currentIter = iter; - } - } - int currentID = currentIter->first; - m_Alternatives.erase(currentIter); - - m_Origin = currentID; - - // now we need to update the file time... - std::wstring filePath = getFullPath(); - HANDLE file = ::CreateFile(filePath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL, nullptr); - if (!::GetFileTime(file, nullptr, nullptr, &m_FileTime)) { - // maybe this file is in a bsa, but there is no easy way to find out which. User should refresh - // the view to find out - m_Archive = L"bsa?"; - } else { - m_Archive = L""; - } - - ::CloseHandle(file); - - } else { - m_Origin = -1; - return true; +bool FileEntry::removeOrigin(int origin) +{ + if (m_Origin == origin) { + if (!m_Alternatives.empty()) { + // find alternative with the highest priority + std::vector>::iterator currentIter = m_Alternatives.begin(); + for (std::vector>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) { + if ((m_Parent->getOriginByID(iter->first).getPriority() > m_Parent->getOriginByID(currentIter->first).getPriority()) && + (iter->first != origin)) { + currentIter = iter; } + } + int currentID = currentIter->first; + m_Alternatives.erase(currentIter); + + m_Origin = currentID; + + // now we need to update the file time... + std::wstring filePath = getFullPath(); + HANDLE file = ::CreateFile(filePath.c_str(), GENERIC_READ | GENERIC_WRITE, + 0, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr); + if (!::GetFileTime(file, nullptr, nullptr, &m_FileTime)) { + // maybe this file is in a bsa, but there is no easy way to find out which. User should refresh + // the view to find out + m_Archive = L"bsa?"; + } else { + m_Archive = L""; + } + + ::CloseHandle(file); + } else { - std::vector>::iterator newEnd = - std::find_if(m_Alternatives.begin(), m_Alternatives.end(), - [&](const std::pair& i) -> bool { return i.first == origin; }); - if (newEnd != m_Alternatives.end()) - m_Alternatives.erase(newEnd, m_Alternatives.end()); + m_Origin = -1; + return true; } - return false; + } else { + std::vector>::iterator newEnd = std::find_if(m_Alternatives.begin(), m_Alternatives.end(), [&](const std::pair &i) -> bool { return i.first == origin; }); + if (newEnd != m_Alternatives.end()) + m_Alternatives.erase(newEnd, m_Alternatives.end()); + } + return false; } -FileEntry::FileEntry() : m_Index(UINT_MAX), m_Name(), m_Origin(-1), m_Parent(nullptr), m_LastAccessed(time(nullptr)) { - LEAK_TRACE; +FileEntry::FileEntry() + : m_Index(UINT_MAX), m_Name(), m_Origin(-1), m_Parent(nullptr), m_LastAccessed(time(nullptr)) +{ + LEAK_TRACE; } -FileEntry::FileEntry(Index index, const std::wstring& name, DirectoryEntry* parent) - : m_Index(index), m_Name(name), m_Origin(-1), m_Archive(L""), m_Parent(parent), m_LastAccessed(time(nullptr)) { - LEAK_TRACE; +FileEntry::FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent) + : m_Index(index), m_Name(name), m_Origin(-1), m_Archive(L""), m_Parent(parent), m_LastAccessed(time(nullptr)) +{ + LEAK_TRACE; } -FileEntry::~FileEntry() { LEAK_UNTRACE; } +FileEntry::~FileEntry() +{ + LEAK_UNTRACE; +} -void FileEntry::sortOrigins() { - m_Alternatives.push_back(std::pair(m_Origin, m_Archive)); - std::sort(m_Alternatives.begin(), m_Alternatives.end(), - [&](const std::pair& LHS, const std::pair& RHS) -> bool { - if ((!LHS.second.size() && !RHS.second.size()) || (LHS.second.size() && RHS.second.size())) { - int l = m_Parent->getOriginByID(LHS.first).getPriority(); - if (l < 0) - l = INT_MAX; - int r = m_Parent->getOriginByID(RHS.first).getPriority(); - if (r < 0) - r = INT_MAX; +void FileEntry::sortOrigins() +{ + m_Alternatives.push_back(std::pair(m_Origin, m_Archive)); + std::sort(m_Alternatives.begin(), m_Alternatives.end(), [&](const std::pair &LHS, const std::pair &RHS) -> bool { + if ((!LHS.second.size() && !RHS.second.size()) || (LHS.second.size() && RHS.second.size())) { + int l = m_Parent->getOriginByID(LHS.first).getPriority(); if (l < 0) l = INT_MAX; + int r = m_Parent->getOriginByID(RHS.first).getPriority(); if (r < 0) r = INT_MAX; + + return l < r; + } + + if (RHS.second.size()) return false; + return true; + }); + if (!m_Alternatives.empty()) { + m_Origin = m_Alternatives.back().first; + m_Archive = m_Alternatives.back().second; + m_Alternatives.pop_back(); + } +} - return l < r; - } - if (RHS.second.size()) - return false; - return true; - }); - if (!m_Alternatives.empty()) { - m_Origin = m_Alternatives.back().first; - m_Archive = m_Alternatives.back().second; - m_Alternatives.pop_back(); +bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) const +{ + if (parent == nullptr) { + return false; + } else { + // don't append the topmost parent because it is the virtual data-root + if (recurseParents(path, parent->getParent())) { + path.append(L"\\").append(parent->getName()); } + return true; + } } -bool FileEntry::recurseParents(std::wstring& path, const DirectoryEntry* parent) const { - if (parent == nullptr) { - return false; - } else { - // don't append the topmost parent because it is the virtual data-root - if (recurseParents(path, parent->getParent())) { - path.append(L"\\").append(parent->getName()); - } - return true; - } +std::wstring FileEntry::getFullPath() const +{ + std::wstring result; + bool ignore = false; + result = m_Parent->getOriginByID(getOrigin(ignore)).getPath(); //base directory for origin + recurseParents(result, m_Parent); // all intermediate directories + return result + L"\\" + m_Name; } -std::wstring FileEntry::getFullPath() const { - std::wstring result; - bool ignore = false; - result = m_Parent->getOriginByID(getOrigin(ignore)).getPath(); // base directory for origin - recurseParents(result, m_Parent); // all intermediate directories - return result + L"\\" + m_Name; +std::wstring FileEntry::getRelativePath() const +{ + std::wstring result; + recurseParents(result, m_Parent); // all intermediate directories + return result + L"\\" + m_Name; } -std::wstring FileEntry::getRelativePath() const { - std::wstring result; - recurseParents(result, m_Parent); // all intermediate directories - return result + L"\\" + m_Name; -} // // DirectoryEntry // -DirectoryEntry::DirectoryEntry(const std::wstring& name, DirectoryEntry* parent, int originID) - : m_OriginConnection(new OriginConnection), m_Name(name), m_Parent(parent), m_Populated(false), m_TopLevel(true) { - m_FileRegister.reset(new FileRegister(m_OriginConnection)); - m_Origins.insert(originID); - LEAK_TRACE; +DirectoryEntry::DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID) + : m_OriginConnection(new OriginConnection), + m_Name(name), m_Parent(parent), m_Populated(false), m_TopLevel(true) +{ + m_FileRegister.reset(new FileRegister(m_OriginConnection)); + m_Origins.insert(originID); + LEAK_TRACE; } -DirectoryEntry::DirectoryEntry(const std::wstring& name, DirectoryEntry* parent, int originID, - boost::shared_ptr fileRegister, - boost::shared_ptr originConnection) - : m_FileRegister(fileRegister), m_OriginConnection(originConnection), m_Name(name), m_Parent(parent), - m_Populated(false), m_TopLevel(false) { - LEAK_TRACE; - m_Origins.insert(originID); +DirectoryEntry::DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID, + boost::shared_ptr fileRegister, boost::shared_ptr originConnection) + : m_FileRegister(fileRegister), m_OriginConnection(originConnection), + m_Name(name), m_Parent(parent), m_Populated(false), m_TopLevel(false) +{ + LEAK_TRACE; + m_Origins.insert(originID); } -DirectoryEntry::~DirectoryEntry() { - LEAK_UNTRACE; - clear(); + +DirectoryEntry::~DirectoryEntry() +{ + LEAK_UNTRACE; + clear(); } -const std::wstring& DirectoryEntry::getName() const { return m_Name; } -void DirectoryEntry::clear() { - m_Files.clear(); - for (DirectoryEntry* entry : m_SubDirectories) { - delete entry; - } - m_SubDirectories.clear(); +const std::wstring &DirectoryEntry::getName() const +{ + return m_Name; } -FilesOrigin& DirectoryEntry::createOrigin(const std::wstring& originName, const std::wstring& directory, int priority) { - if (m_OriginConnection->exists(originName)) { - FilesOrigin& origin = m_OriginConnection->getByName(originName); - origin.enable(true); - return origin; - } else { - return m_OriginConnection->createOrigin(originName, directory, priority, m_FileRegister, m_OriginConnection); - } + +void DirectoryEntry::clear() +{ + m_Files.clear(); + for (DirectoryEntry *entry : m_SubDirectories) { + delete entry; + } + m_SubDirectories.clear(); } -void DirectoryEntry::addFromOrigin(const std::wstring& originName, const std::wstring& directory, int priority) { - FilesOrigin& origin = createOrigin(originName, directory, priority); - if (directory.length() != 0) { - boost::scoped_array buffer(new wchar_t[MAXPATH_UNICODE + 1]); - memset(buffer.get(), L'\0', MAXPATH_UNICODE + 1); - int offset = _snwprintf(buffer.get(), MAXPATH_UNICODE, L"%ls", directory.c_str()); - buffer.get()[offset] = L'\0'; - addFiles(origin, buffer.get(), offset); - } - m_Populated = true; + +FilesOrigin &DirectoryEntry::createOrigin(const std::wstring &originName, const std::wstring &directory, int priority) +{ + if (m_OriginConnection->exists(originName)) { + FilesOrigin &origin = m_OriginConnection->getByName(originName); + origin.enable(true); + return origin; + } else { + return m_OriginConnection->createOrigin(originName, directory, priority, m_FileRegister, m_OriginConnection); + } } -void DirectoryEntry::addFromBSA(const std::wstring& originName, std::wstring& directory, const std::wstring& fileName, - int priority) { - FilesOrigin& origin = createOrigin(originName, directory, priority); - WIN32_FILE_ATTRIBUTE_DATA fileData; - if (::GetFileAttributesExW(fileName.c_str(), GetFileExInfoStandard, &fileData) == 0) { - throw windows_error("failed to determine file time"); - } +void DirectoryEntry::addFromOrigin(const std::wstring &originName, const std::wstring &directory, int priority) +{ + FilesOrigin &origin = createOrigin(originName, directory, priority); + if (directory.length() != 0) { + boost::scoped_array buffer(new wchar_t[MAXPATH_UNICODE + 1]); + memset(buffer.get(), L'\0', MAXPATH_UNICODE + 1); + int offset = _snwprintf(buffer.get(), MAXPATH_UNICODE, L"%ls", directory.c_str()); + buffer.get()[offset] = L'\0'; + addFiles(origin, buffer.get(), offset); + } + m_Populated = true; +} - BSA::Archive archive; - BSA::EErrorCode res = archive.read(ToString(fileName, false).c_str(), false); - if ((res != BSA::ERROR_NONE) && (res != BSA::ERROR_INVALIDHASHES)) { - std::ostringstream stream; - stream << "invalid bsa file: " << ToString(fileName, false) << " errorcode " << res << " - " - << ::GetLastError(); - throw std::runtime_error(stream.str()); - } - size_t namePos = fileName.find_last_of(L"\\/"); - if (namePos == std::wstring::npos) { - namePos = 0; - } else { - ++namePos; - } - addFiles(origin, archive.getRoot(), fileData.ftLastWriteTime, fileName.substr(namePos)); - m_Populated = true; +void DirectoryEntry::addFromBSA(const std::wstring &originName, std::wstring &directory, const std::wstring &fileName, int priority) +{ + FilesOrigin &origin = createOrigin(originName, directory, priority); + + WIN32_FILE_ATTRIBUTE_DATA fileData; + if (::GetFileAttributesExW(fileName.c_str(), GetFileExInfoStandard, &fileData) == 0) { + throw windows_error("failed to determine file time"); + } + + BSA::Archive archive; + BSA::EErrorCode res = archive.read(ToString(fileName, false).c_str(), false); + if ((res != BSA::ERROR_NONE) && (res != BSA::ERROR_INVALIDHASHES)) { + std::ostringstream stream; + stream << "invalid bsa file: " << ToString(fileName, false) << " errorcode " << res << " - " << ::GetLastError(); + throw std::runtime_error(stream.str()); + } + size_t namePos = fileName.find_last_of(L"\\/"); + if (namePos == std::wstring::npos) { + namePos = 0; + } else { + ++namePos; + } + + addFiles(origin, archive.getRoot(), fileData.ftLastWriteTime, fileName.substr(namePos)); + m_Populated = true; } -void DirectoryEntry::propagateOrigin(int origin) { - m_Origins.insert(origin); - if (m_Parent != nullptr) { - m_Parent->propagateOrigin(origin); - } +void DirectoryEntry::propagateOrigin(int origin) +{ + m_Origins.insert(origin); + if (m_Parent != nullptr) { + m_Parent->propagateOrigin(origin); + } } -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); +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); - bool res = ::VerifyVersionInfo(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, mask) == TRUE; - return res; + bool res = ::VerifyVersionInfo(&versionInfo, VER_MAJORVERSION | VER_MINORVERSION, mask) == TRUE; + return res; } -static bool DirCompareByName(const DirectoryEntry* lhs, const DirectoryEntry* rhs) { - return _wcsicmp(lhs->getName().c_str(), rhs->getName().c_str()) < 0; + +static bool DirCompareByName(const DirectoryEntry *lhs, const DirectoryEntry *rhs) +{ + return _wcsicmp(lhs->getName().c_str(), rhs->getName().c_str()) < 0; } -void DirectoryEntry::addFiles(FilesOrigin& origin, wchar_t* buffer, int bufferOffset) { - WIN32_FIND_DATAW findData; - _snwprintf_s(buffer + bufferOffset, MAXPATH_UNICODE - bufferOffset, _TRUNCATE, L"\\*"); +void DirectoryEntry::addFiles(FilesOrigin &origin, wchar_t *buffer, int bufferOffset) +{ + WIN32_FIND_DATAW findData; - HANDLE searchHandle = nullptr; + _snwprintf_s(buffer + bufferOffset, MAXPATH_UNICODE - bufferOffset, _TRUNCATE, L"\\*"); - if (SupportOptimizedFind()) { - searchHandle = ::FindFirstFileExW(buffer, FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, - FIND_FIRST_EX_LARGE_FETCH); - } else { - searchHandle = ::FindFirstFileExW(buffer, FindExInfoStandard, &findData, FindExSearchNameMatch, nullptr, 0); - } + HANDLE searchHandle = nullptr; - if (searchHandle != INVALID_HANDLE_VALUE) { - BOOL result = true; - while (result) { - if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { - if ((wcscmp(findData.cFileName, L".") != 0) && (wcscmp(findData.cFileName, L"..") != 0)) { - int offset = _snwprintf(buffer + bufferOffset, MAXPATH_UNICODE, L"\\%ls", findData.cFileName); - // recurse into subdirectories - getSubDirectory(findData.cFileName, true, origin.getID()) - ->addFiles(origin, buffer, bufferOffset + offset); - } - } else { - insert(findData.cFileName, origin, findData.ftLastWriteTime, L""); - } - result = ::FindNextFileW(searchHandle, &findData); + if (SupportOptimizedFind()) { + searchHandle = ::FindFirstFileExW(buffer, FindExInfoBasic, &findData, FindExSearchNameMatch, nullptr, + FIND_FIRST_EX_LARGE_FETCH); + } else { + searchHandle = ::FindFirstFileExW(buffer, FindExInfoStandard, &findData, FindExSearchNameMatch, nullptr, 0); + } + + if (searchHandle != INVALID_HANDLE_VALUE) { + BOOL result = true; + while (result) { + if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) { + if ((wcscmp(findData.cFileName, L".") != 0) && + (wcscmp(findData.cFileName, L"..") != 0)) { + int offset = _snwprintf(buffer + bufferOffset, MAXPATH_UNICODE, L"\\%ls", findData.cFileName); + // recurse into subdirectories + getSubDirectory(findData.cFileName, true, origin.getID())->addFiles(origin, buffer, bufferOffset + offset); } + } else { + insert(findData.cFileName, origin, findData.ftLastWriteTime, L""); + } + result = ::FindNextFileW(searchHandle, &findData); } - std::sort(m_SubDirectories.begin(), m_SubDirectories.end(), &DirCompareByName); - ::FindClose(searchHandle); + } + std::sort(m_SubDirectories.begin(), m_SubDirectories.end(), &DirCompareByName); + ::FindClose(searchHandle); } -void DirectoryEntry::addFiles(FilesOrigin& origin, BSA::Folder::Ptr archiveFolder, FILETIME& fileTime, - const std::wstring& archiveName) { - // add files - for (unsigned int fileIdx = 0; fileIdx < archiveFolder->getNumFiles(); ++fileIdx) { - BSA::File::Ptr file = archiveFolder->getFile(fileIdx); - insert(ToWString(file->getName(), true), origin, fileTime, archiveName); - } - // recurse into subdirectories - for (unsigned int folderIdx = 0; folderIdx < archiveFolder->getNumSubFolders(); ++folderIdx) { - BSA::Folder::Ptr folder = archiveFolder->getSubFolder(folderIdx); - DirectoryEntry* folderEntry = - getSubDirectoryRecursive(ToWString(folder->getName(), true), true, origin.getID()); +void DirectoryEntry::addFiles(FilesOrigin &origin, BSA::Folder::Ptr archiveFolder, FILETIME &fileTime, const std::wstring &archiveName) +{ + // add files + for (unsigned int fileIdx = 0; fileIdx < archiveFolder->getNumFiles(); ++fileIdx) { + BSA::File::Ptr file = archiveFolder->getFile(fileIdx); + insert(ToWString(file->getName(), true), origin, fileTime, archiveName); + } - folderEntry->addFiles(origin, folder, fileTime, archiveName); - } + // recurse into subdirectories + for (unsigned int folderIdx = 0; folderIdx < archiveFolder->getNumSubFolders(); ++folderIdx) { + BSA::Folder::Ptr folder = archiveFolder->getSubFolder(folderIdx); + DirectoryEntry *folderEntry = getSubDirectoryRecursive(ToWString(folder->getName(), true), true, origin.getID()); + + folderEntry->addFiles(origin, folder, fileTime, archiveName); + } } -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); + +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); + } else { + std::wstring dirName = filePath.substr(0, pos); + std::wstring rest = filePath.substr(pos + 1); + DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false); + if (entry != nullptr) { + return entry->removeFile(rest, origin); } else { - std::wstring dirName = filePath.substr(0, pos); - std::wstring rest = filePath.substr(pos + 1); - DirectoryEntry* entry = getSubDirectoryRecursive(dirName, false); - if (entry != nullptr) { - return entry->removeFile(rest, origin); - } else { - return false; - } + return false; } + } } -void DirectoryEntry::removeDirRecursive() { - while (!m_Files.empty()) { - m_FileRegister->removeFile(m_Files.begin()->second); - } +void DirectoryEntry::removeDirRecursive() +{ + while (!m_Files.empty()) { + m_FileRegister->removeFile(m_Files.begin()->second); + } + + for (DirectoryEntry *entry : m_SubDirectories) { + entry->removeDirRecursive(); + delete entry; + } + m_SubDirectories.clear(); +} - for (DirectoryEntry* entry : m_SubDirectories) { +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(); + m_SubDirectories.erase(iter); delete entry; + break; + } } - m_SubDirectories.clear(); -} - -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(); - m_SubDirectories.erase(iter); - delete entry; - break; - } - } - } else { - std::wstring dirName = path.substr(0, pos); - std::wstring rest = path.substr(pos + 1); - DirectoryEntry* entry = getSubDirectoryRecursive(dirName, false); - if (entry != nullptr) { - entry->removeDir(rest); - } + } else { + std::wstring dirName = path.substr(0, pos); + std::wstring rest = path.substr(pos + 1); + DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false); + if (entry != nullptr) { + entry->removeDir(rest); } + } } -bool DirectoryEntry::hasContentsFromOrigin(int originID) const { return m_Origins.find(originID) != m_Origins.end(); } +bool DirectoryEntry::hasContentsFromOrigin(int originID) const +{ + return m_Origins.find(originID) != m_Origins.end(); +} -void DirectoryEntry::insertFile(const std::wstring& filePath, FilesOrigin& origin, FILETIME fileTime) { - size_t pos = filePath.find_first_of(L"\\/"); - if (pos == std::string::npos) { - this->insert(filePath, origin, fileTime, std::wstring()); - } else { - std::wstring dirName = filePath.substr(0, pos); - std::wstring rest = filePath.substr(pos + 1); - getSubDirectoryRecursive(dirName, true, origin.getID())->insertFile(rest, origin, fileTime); - } +void DirectoryEntry::insertFile(const std::wstring &filePath, FilesOrigin &origin, FILETIME fileTime) +{ + size_t pos = filePath.find_first_of(L"\\/"); + if (pos == std::string::npos) { + this->insert(filePath, origin, fileTime, std::wstring()); + } else { + std::wstring dirName = filePath.substr(0, pos); + std::wstring rest = filePath.substr(pos + 1); + getSubDirectoryRecursive(dirName, true, origin.getID())->insertFile(rest, origin, fileTime); + } } -void DirectoryEntry::removeFile(FileEntry::Index index) { - if (!m_Files.empty()) { - auto iter = std::find_if( - m_Files.begin(), m_Files.end(), - [&index](const std::pair& iter) -> bool { return iter.second == index; }); - if (iter != m_Files.end()) { - m_Files.erase(iter); - } else { - log("file \"%ls\" not in directory \"%ls\"", m_FileRegister->getFile(index)->getName().c_str(), - this->getName().c_str()); - } + +void DirectoryEntry::removeFile(FileEntry::Index index) +{ + if (!m_Files.empty()) { + auto iter = std::find_if(m_Files.begin(), m_Files.end(), + [&index](const std::pair &iter) -> bool { + return iter.second == index; } ); + if (iter != m_Files.end()) { + m_Files.erase(iter); } else { - log("file \"%ls\" not in directory \"%ls\", directory empty", m_FileRegister->getFile(index)->getName().c_str(), - this->getName().c_str()); + log("file \"%ls\" not in directory \"%ls\"", + m_FileRegister->getFile(index)->getName().c_str(), + this->getName().c_str()); } + } else { + log("file \"%ls\" not in directory \"%ls\", directory empty", + m_FileRegister->getFile(index)->getName().c_str(), + this->getName().c_str()); + } } -void DirectoryEntry::removeFiles(const std::set& indices) { - for (auto iter = m_Files.begin(); iter != m_Files.end();) { - if (indices.find(iter->second) != indices.end()) { - m_Files.erase(iter++); - } else { - ++iter; - } + +void DirectoryEntry::removeFiles(const std::set &indices) +{ + for (auto iter = m_Files.begin(); iter != m_Files.end();) { + if (indices.find(iter->second) != indices.end()) { + m_Files.erase(iter++); + } else { + ++iter; } + } } -int DirectoryEntry::anyOrigin() const { - bool ignore; - for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { - FileEntry::Ptr entry = m_FileRegister->getFile(iter->second); - if ((entry.get() != nullptr) && !entry->isFromArchive()) { - return entry->getOrigin(ignore); - } + +int DirectoryEntry::anyOrigin() const +{ + bool ignore; + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + FileEntry::Ptr 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 != -1) { - return res; - } + // 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 != -1){ + return res; } - return *(m_Origins.begin()); + } + return *(m_Origins.begin()); +} + + +bool DirectoryEntry::originExists(const std::wstring &name) const +{ + return m_OriginConnection->exists(name); } -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::getOriginByID(int ID) const +{ + return m_OriginConnection->getByID(ID); +} + -FilesOrigin& DirectoryEntry::getOriginByName(const std::wstring& name) const { - return m_OriginConnection->getByName(name); +FilesOrigin &DirectoryEntry::getOriginByName(const std::wstring &name) const +{ + return m_OriginConnection->getByName(name); } /* @@ -627,219 +723,251 @@ int DirectoryEntry::getOrigin(const std::wstring &path, bool &archive) } }*/ -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; +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; } -const FileEntry::Ptr DirectoryEntry::searchFile(const std::wstring& path, const DirectoryEntry** directory) const { + +const FileEntry::Ptr 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 = nullptr; + *directory = this; } + return FileEntry::Ptr(); + } - if ((path.length() == 0) || (path == L"*")) { - // no file name -> the path ended on a (back-)slash - if (directory != nullptr) { - *directory = this; - } - return FileEntry::Ptr(); - } + size_t len = path.find_first_of(L"\\/"); - size_t len = path.find_first_of(L"\\/"); - - if (len == std::string::npos) { - // no more path components - auto iter = m_Files.find(ToLower(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 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("unexpected end of path"); - return FileEntry::Ptr(); - } - return temp->searchFile(path.substr(len + 1), directory); - } + if (len == std::string::npos) { + // no more path components + auto iter = m_Files.find(ToLower(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; + } } - return FileEntry::Ptr(); + } else { + // file is in 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("unexpected end of path"); + return FileEntry::Ptr(); + } + return temp->searchFile(path.substr(len + 1), directory); + } + } + return FileEntry::Ptr(); } -DirectoryEntry* DirectoryEntry::findSubDirectory(const std::wstring& name) const { - for (DirectoryEntry* entry : m_SubDirectories) { - if (CaseInsensitiveEqual(entry->getName(), name)) { - return entry; - } + +DirectoryEntry *DirectoryEntry::findSubDirectory(const std::wstring &name) const +{ + for (DirectoryEntry *entry : m_SubDirectories) { + if (CaseInsensitiveEqual(entry->getName(), name)) { + return entry; } - return nullptr; + } + return nullptr; } -DirectoryEntry* DirectoryEntry::findSubDirectoryRecursive(const std::wstring& path) { - return getSubDirectoryRecursive(path, false, -1); + +DirectoryEntry *DirectoryEntry::findSubDirectoryRecursive(const std::wstring &path) +{ + return getSubDirectoryRecursive(path, false, -1); } -const FileEntry::Ptr DirectoryEntry::findFile(const std::wstring& name) const { - auto iter = m_Files.find(ToLower(name)); - if (iter != m_Files.end()) { - return m_FileRegister->getFile(iter->second); - } else { - return FileEntry::Ptr(); - } + +const FileEntry::Ptr DirectoryEntry::findFile(const std::wstring &name) const +{ + auto iter = m_Files.find(ToLower(name)); + if (iter != m_Files.end()) { + return m_FileRegister->getFile(iter->second); + } else { + return FileEntry::Ptr(); + } } -DirectoryEntry* DirectoryEntry::getSubDirectory(const std::wstring& name, bool create, int originID) { - for (DirectoryEntry* entry : m_SubDirectories) { - if (CaseInsensitiveEqual(entry->getName(), name)) { - return entry; - } - } - if (create) { - std::vector::iterator iter = m_SubDirectories.insert( - m_SubDirectories.end(), new DirectoryEntry(name, this, originID, m_FileRegister, m_OriginConnection)); - return *iter; - } else { - return nullptr; +DirectoryEntry *DirectoryEntry::getSubDirectory(const std::wstring &name, bool create, int originID) +{ + for (DirectoryEntry *entry : m_SubDirectories) { + if (CaseInsensitiveEqual(entry->getName(), name)) { + return entry; } + } + if (create) { + std::vector::iterator iter = m_SubDirectories.insert(m_SubDirectories.end(), + new DirectoryEntry(name, this, originID, m_FileRegister, m_OriginConnection)); + return *iter; + } else { + return nullptr; + } } -DirectoryEntry* DirectoryEntry::getSubDirectoryRecursive(const std::wstring& path, bool create, int originID) { - if (path.length() == 0) { - // path ended with a backslash? - return this; - } - size_t pos = path.find_first_of(L"\\/"); - if (pos == std::wstring::npos) { - return getSubDirectory(path, create); +DirectoryEntry *DirectoryEntry::getSubDirectoryRecursive(const std::wstring &path, bool create, int originID) +{ + if (path.length() == 0) { + // path ended with a backslash? + return this; + } + + size_t pos = path.find_first_of(L"\\/"); + if (pos == std::wstring::npos) { + return getSubDirectory(path, create); + } else { + DirectoryEntry *nextChild = getSubDirectory(path.substr(0, pos), create, originID); + if (nextChild == nullptr) { + return nullptr; } else { - DirectoryEntry* nextChild = getSubDirectory(path.substr(0, pos), create, originID); - if (nextChild == nullptr) { - return nullptr; - } else { - return nextChild->getSubDirectoryRecursive(path.substr(pos + 1), create, originID); - } + return nextChild->getSubDirectoryRecursive(path.substr(pos + 1), create, originID); } + } } + + FileRegister::FileRegister(boost::shared_ptr originConnection) - : m_OriginConnection(originConnection) { - LEAK_TRACE; + : m_OriginConnection(originConnection) +{ + LEAK_TRACE; } -FileRegister::~FileRegister() { - LEAK_UNTRACE; - m_Files.clear(); +FileRegister::~FileRegister() +{ + LEAK_UNTRACE; + m_Files.clear(); } -FileEntry::Index FileRegister::generateIndex() { - static std::atomic sIndex(0); - return sIndex++; + +FileEntry::Index FileRegister::generateIndex() +{ + static std::atomic sIndex(0); + return sIndex++; } -bool FileRegister::indexValid(FileEntry::Index index) const { return m_Files.find(index) != m_Files.end(); } +bool FileRegister::indexValid(FileEntry::Index index) const +{ + return m_Files.find(index) != m_Files.end(); +} -FileEntry::Ptr FileRegister::createFile(const std::wstring& name, DirectoryEntry* parent) { - FileEntry::Index index = generateIndex(); - m_Files[index] = FileEntry::Ptr(new FileEntry(index, name, parent)); - return m_Files[index]; +FileEntry::Ptr FileRegister::createFile(const std::wstring &name, DirectoryEntry *parent) +{ + FileEntry::Index index = generateIndex(); + m_Files[index] = FileEntry::Ptr(new FileEntry(index, name, parent)); + return m_Files[index]; } -FileEntry::Ptr FileRegister::getFile(FileEntry::Index index) const { - auto iter = m_Files.find(index); - if (iter != m_Files.end()) { - return iter->second; - } else { - return FileEntry::Ptr(); - } + +FileEntry::Ptr FileRegister::getFile(FileEntry::Index index) const +{ + auto iter = m_Files.find(index); + if (iter != m_Files.end()) { + return iter->second; + } else { + return FileEntry::Ptr(); + } } -void FileRegister::unregisterFile(FileEntry::Ptr file) { - bool ignore; - // unregister from origin - int originID = file->getOrigin(ignore); - m_OriginConnection->getByID(originID).removeFile(file->getIndex()); - const std::vector>& alternatives = file->getAlternatives(); - for (auto iter = alternatives.begin(); iter != alternatives.end(); ++iter) { - m_OriginConnection->getByID(iter->first).removeFile(file->getIndex()); - } +void FileRegister::unregisterFile(FileEntry::Ptr file) +{ + bool ignore; + // unregister from origin + int originID = file->getOrigin(ignore); + m_OriginConnection->getByID(originID).removeFile(file->getIndex()); + const std::vector> &alternatives = file->getAlternatives(); + for (auto iter = alternatives.begin(); iter != alternatives.end(); ++iter) { + m_OriginConnection->getByID(iter->first).removeFile(file->getIndex()); + } - // unregister from directory - if (file->getParent() != nullptr) { - file->getParent()->removeFile(file->getIndex()); - } + // unregister from directory + if (file->getParent() != nullptr) { + file->getParent()->removeFile(file->getIndex()); + } } -bool FileRegister::removeFile(FileEntry::Index index) { - auto iter = m_Files.find(index); - if (iter != m_Files.end()) { - unregisterFile(iter->second); - m_Files.erase(index); - return true; - } else { - log("invalid file index for remove: %lu", index); - return false; - } + +bool FileRegister::removeFile(FileEntry::Index index) +{ + auto iter = m_Files.find(index); + if (iter != m_Files.end()) { + unregisterFile(iter->second); + m_Files.erase(index); + return true; + } else { + log("invalid file index for remove: %lu", index); + return false; + } } -void FileRegister::removeOrigin(FileEntry::Index index, int originID) { - auto iter = m_Files.find(index); - if (iter != m_Files.end()) { - if (iter->second->removeOrigin(originID)) { - unregisterFile(iter->second); - m_Files.erase(iter); - } - } else { - log("invalid file index for remove (for origin): %lu", index); +void FileRegister::removeOrigin(FileEntry::Index index, int originID) +{ + auto iter = m_Files.find(index); + if (iter != m_Files.end()) { + if (iter->second->removeOrigin(originID)) { + unregisterFile(iter->second); + m_Files.erase(iter); } + } else { + log("invalid file index for remove (for origin): %lu", index); + } } -void FileRegister::removeOriginMulti(std::set indices, int originID, time_t notAfter) { - std::vector removedFiles; - for (auto iter = indices.begin(); iter != indices.end();) { - auto pos = m_Files.find(*iter); - if (pos != m_Files.end() && (pos->second->lastAccessed() < notAfter) && pos->second->removeOrigin(originID)) { - removedFiles.push_back(pos->second); - m_Files.erase(pos); - ++iter; - } else { - indices.erase(iter++); - } +void FileRegister::removeOriginMulti(std::set indices, int originID, time_t notAfter) +{ + std::vector removedFiles; + for (auto iter = indices.begin(); iter != indices.end();) { + auto pos = m_Files.find(*iter); + if (pos != m_Files.end() + && (pos->second->lastAccessed() < notAfter) + && pos->second->removeOrigin(originID)) { + removedFiles.push_back(pos->second); + m_Files.erase(pos); + ++iter; + } else { + indices.erase(iter++); } + } - // optimization: this is only called when disabling an origin and in this case we don't have - // to remove the file from the origin - - // need to remove files from their parent directories. multiple ways to go about this: - // a) for each file, search its parents file-list (preferably by name) and remove what is found - // b) gather the parent directories, go through the file list for each once and remove all files that have been - // removed the latter should be faster when there are many files in few directories. since this is called only when - // disabling an origin that is probably frequently the case - std::set parents; - for (const FileEntry::Ptr& file : removedFiles) { - if (file->getParent() != nullptr) { - parents.insert(file->getParent()); - } - } - for (DirectoryEntry* parent : parents) { - parent->removeFiles(indices); + // optimization: this is only called when disabling an origin and in this case we don't have + // to remove the file from the origin + + // need to remove files from their parent directories. multiple ways to go about this: + // a) for each file, search its parents file-list (preferably by name) and remove what is found + // b) gather the parent directories, go through the file list for each once and remove all files that have been removed + // the latter should be faster when there are many files in few directories. since this is called + // only when disabling an origin that is probably frequently the case + std::set parents; + for (const FileEntry::Ptr &file : removedFiles) { + if (file->getParent() != nullptr) { + parents.insert(file->getParent()); } + } + for (DirectoryEntry *parent : parents) { + parent->removeFiles(indices); + } } -void FileRegister::sortOrigins() { - for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { - iter->second->sortOrigins(); - } +void FileRegister::sortOrigins() +{ + for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) { + iter->second->sortOrigins(); + } } } // namespace MOShared diff --git a/src/shared/directoryentry.h b/src/shared/directoryentry.h index c26fb433..8dbedaf4 100644 --- a/src/shared/directoryentry.h +++ b/src/shared/directoryentry.h @@ -20,11 +20,12 @@ along with Mod Organizer. If not, see . #ifndef DIRECTORYENTRY_H #define DIRECTORYENTRY_H -#include -#include -#include + #include +#include #include +#include +#include #define WIN32_MEAN_AND_LEAN #include #include @@ -34,291 +35,311 @@ along with Mod Organizer. If not, see . #endif #include "util.h" + namespace MOShared { + class DirectoryEntry; class OriginConnection; class FileRegister; + class FileEntry { public: - typedef unsigned int Index; - typedef boost::shared_ptr Ptr; + typedef unsigned int Index; + + typedef boost::shared_ptr Ptr; public: - FileEntry(); - FileEntry(Index index, const std::wstring& name, DirectoryEntry* parent); + FileEntry(); - ~FileEntry(); + FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent); - Index getIndex() const { return m_Index; } + ~FileEntry(); - time_t lastAccessed() const { return m_LastAccessed; } + Index getIndex() const { return m_Index; } - void addOrigin(int origin, FILETIME fileTime, const std::wstring& archive); - // 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 returned. otherwise, false is returned - bool removeOrigin(int origin); - void sortOrigins(); + time_t lastAccessed() const { return m_LastAccessed; } - // 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 std::vector>& getAlternatives() const { return m_Alternatives; } + void addOrigin(int origin, FILETIME fileTime, const std::wstring &archive); + // 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 returned. otherwise, false is returned + bool removeOrigin(int origin); + void sortOrigins(); - const std::wstring& getName() const { return m_Name; } - int getOrigin() const { return m_Origin; } - int getOrigin(bool& archive) const { - archive = (m_Archive.length() != 0); - return m_Origin; - } - const std::wstring& getArchive() const { return m_Archive; } - bool isFromArchive() const { return m_Archive.length() != 0; } - std::wstring getFullPath() const; - std::wstring getRelativePath() const; - DirectoryEntry* getParent() { return m_Parent; } + // 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 std::vector> &getAlternatives() const { return m_Alternatives; } + + const std::wstring &getName() const { return m_Name; } + int getOrigin() const { return m_Origin; } + int getOrigin(bool &archive) const { archive = (m_Archive.length() != 0); return m_Origin; } + const std::wstring &getArchive() const { return m_Archive; } + bool isFromArchive() const { return m_Archive.length() != 0; } + std::wstring getFullPath() const; + std::wstring getRelativePath() const; + DirectoryEntry *getParent() { return m_Parent; } - void setFileTime(FILETIME fileTime) const { m_FileTime = fileTime; } - FILETIME getFileTime() const { return m_FileTime; } + void setFileTime(FILETIME fileTime) const { m_FileTime = fileTime; } + FILETIME getFileTime() const { return m_FileTime; } private: - bool recurseParents(std::wstring& path, const DirectoryEntry* parent) const; - void determineTime(); + bool recurseParents(std::wstring &path, const DirectoryEntry *parent) const; + + void determineTime(); private: - Index m_Index; - std::wstring m_Name; - int m_Origin = -1; - std::wstring m_Archive; - std::vector> m_Alternatives; - DirectoryEntry* m_Parent; - mutable FILETIME m_FileTime; - - time_t m_LastAccessed; - - friend bool operator<(const FileEntry& lhs, const FileEntry& rhs) { - return _wcsicmp(lhs.m_Name.c_str(), rhs.m_Name.c_str()) < 0; - } - friend bool operator==(const FileEntry& lhs, const FileEntry& rhs) { - return _wcsicmp(lhs.m_Name.c_str(), rhs.m_Name.c_str()) == 0; - } + + Index m_Index; + std::wstring m_Name; + int m_Origin = -1; + std::wstring m_Archive; + std::vector> m_Alternatives; + DirectoryEntry *m_Parent; + mutable FILETIME m_FileTime; + + time_t m_LastAccessed; + + friend bool operator<(const FileEntry &lhs, const FileEntry &rhs) { + return _wcsicmp(lhs.m_Name.c_str(), rhs.m_Name.c_str()) < 0; + } + friend bool operator==(const FileEntry &lhs, const FileEntry &rhs) { + return _wcsicmp(lhs.m_Name.c_str(), rhs.m_Name.c_str()) == 0; + } }; + // represents a mod or the data directory, providing files to the tree class FilesOrigin { - friend class OriginConnection; - + friend class OriginConnection; public: - FilesOrigin(); - FilesOrigin(const FilesOrigin& reference); - ~FilesOrigin(); - // sets priority for this origin, but it will overwrite the exisiting mapping for this priority, - // the previous origin will no longer be referenced - void setPriority(int priority); + FilesOrigin(); + FilesOrigin(const FilesOrigin &reference); + ~FilesOrigin(); + + // sets priority for this origin, but it will overwrite the exisiting mapping for this priority, + // the previous origin will no longer be referenced + 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; } - int getID() const { return m_ID; } - const std::wstring& getPath() const { return m_Path; } + int getID() const { return m_ID; } + const std::wstring &getPath() const { return m_Path; } - std::vector getFiles() const; + std::vector getFiles() const; - void enable(bool enabled, time_t notAfter = LONG_MAX); - bool isDisabled() const { return m_Disabled; } + void enable(bool enabled, time_t notAfter = LONG_MAX); + bool isDisabled() const { return m_Disabled; } - void addFile(FileEntry::Index index) { m_Files.insert(index); } - void removeFile(FileEntry::Index index); + void addFile(FileEntry::Index index) { m_Files.insert(index); } + void removeFile(FileEntry::Index index); private: - FilesOrigin(int ID, const std::wstring& name, const std::wstring& path, int priority, - boost::shared_ptr fileRegister, boost::shared_ptr originConnection); + + FilesOrigin(int ID, const std::wstring &name, const std::wstring &path, int priority, + boost::shared_ptr fileRegister, boost::shared_ptr originConnection); + private: - int m_ID; - bool m_Disabled; + int m_ID; + + bool m_Disabled; + + std::set m_Files; + std::wstring m_Name; + std::wstring m_Path; + int m_Priority; + boost::weak_ptr m_FileRegister; + boost::weak_ptr m_OriginConnection; - std::set m_Files; - std::wstring m_Name; - std::wstring m_Path; - int m_Priority; - boost::weak_ptr m_FileRegister; - boost::weak_ptr m_OriginConnection; }; -class FileRegister { + +class FileRegister +{ public: - FileRegister(boost::shared_ptr originConnection); - ~FileRegister(); - bool indexValid(FileEntry::Index index) const; + FileRegister(boost::shared_ptr originConnection); + ~FileRegister(); - FileEntry::Ptr createFile(const std::wstring& name, DirectoryEntry* parent); - FileEntry::Ptr getFile(FileEntry::Index index) const; + bool indexValid(FileEntry::Index index) const; - size_t size() const { return m_Files.size(); } + FileEntry::Ptr createFile(const std::wstring &name, DirectoryEntry *parent); + FileEntry::Ptr getFile(FileEntry::Index index) const; - bool removeFile(FileEntry::Index index); - void removeOrigin(FileEntry::Index index, int originID); - void removeOriginMulti(std::set indices, int originID, time_t notAfter); + size_t size() const { return m_Files.size(); } - void sortOrigins(); + bool removeFile(FileEntry::Index index); + void removeOrigin(FileEntry::Index index, int originID); + void removeOriginMulti(std::set indices, int originID, time_t notAfter); + + void sortOrigins(); private: - FileEntry::Index generateIndex(); - void unregisterFile(FileEntry::Ptr file); + FileEntry::Index generateIndex(); + + void unregisterFile(FileEntry::Ptr file); private: - std::map m_Files; - boost::shared_ptr m_OriginConnection; + std::map m_Files; + + boost::shared_ptr m_OriginConnection; + }; -class DirectoryEntry { + +class DirectoryEntry +{ public: - DirectoryEntry(const std::wstring& name, DirectoryEntry* parent, int originID); - DirectoryEntry(const std::wstring& name, DirectoryEntry* parent, int originID, - boost::shared_ptr fileRegister, boost::shared_ptr originConnection); + DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID); - ~DirectoryEntry(); + DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID, + boost::shared_ptr fileRegister, + boost::shared_ptr originConnection); - void clear(); - bool isPopulated() const { return m_Populated; } + ~DirectoryEntry(); - bool isEmpty() const { return m_Files.empty() && m_SubDirectories.empty(); } + void clear(); + bool isPopulated() const { return m_Populated; } - const DirectoryEntry* getParent() const { return m_Parent; } + bool isEmpty() const { return m_Files.empty() && m_SubDirectories.empty(); } - // 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); - void addFromBSA(const std::wstring& originName, std::wstring& directory, const std::wstring& fileName, - int priority); + const DirectoryEntry *getParent() const { return m_Parent; } - void propagateOrigin(int origin); + // 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); + void addFromBSA(const std::wstring &originName, std::wstring &directory, const std::wstring &fileName, int priority); - const std::wstring& getName() const; + void propagateOrigin(int origin); - boost::shared_ptr getFileRegister() { return m_FileRegister; } + const std::wstring &getName() const; - bool originExists(const std::wstring& name) const; - FilesOrigin& getOriginByID(int ID) const; - FilesOrigin& getOriginByName(const std::wstring& name) const; + boost::shared_ptr getFileRegister() { return m_FileRegister; } - int anyOrigin() const; + bool originExists(const std::wstring &name) const; + FilesOrigin &getOriginByID(int ID) const; + FilesOrigin &getOriginByName(const std::wstring &name) const; - // int getOrigin(const std::wstring &path, bool &archive); + int anyOrigin() const; - std::vector getFiles() const; + //int getOrigin(const std::wstring &path, bool &archive); - void getSubDirectories(std::vector::const_iterator& begin, - std::vector::const_iterator& end) const { - begin = m_SubDirectories.begin(); - end = m_SubDirectories.end(); - } + std::vector getFiles() const; + + void getSubDirectories(std::vector::const_iterator &begin + , std::vector::const_iterator &end) const { + begin = m_SubDirectories.begin(); end = m_SubDirectories.end(); + } + + DirectoryEntry *findSubDirectory(const std::wstring &name) const; + DirectoryEntry *findSubDirectoryRecursive(const std::wstring &path); - DirectoryEntry* findSubDirectory(const std::wstring& name) 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 FileEntry::Ptr findFile(const std::wstring& name) const; - - /** 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 FileEntry::Ptr searchFile(const std::wstring& path, const DirectoryEntry** directory) const; - - void insertFile(const std::wstring& filePath, FilesOrigin& origin, FILETIME fileTime); - - void removeFile(FileEntry::Index 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, int* origin = nullptr); - - /** - * @brief remove the specified directory - * @param path directory to remove - */ - void removeDir(const std::wstring& path); - - bool remove(const std::wstring& fileName, int* origin) { - auto iter = m_Files.find(ToLower(fileName)); - if (iter != m_Files.end()) { - if (origin != nullptr) { - FileEntry::Ptr entry = m_FileRegister->getFile(iter->second); - if (entry.get() != nullptr) { - bool ignore; - *origin = entry->getOrigin(ignore); - } - } - return m_FileRegister->removeFile(iter->second); - } else { - return false; + /** 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 FileEntry::Ptr findFile(const std::wstring &name) const; + + /** 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 FileEntry::Ptr searchFile(const std::wstring &path, const DirectoryEntry **directory) const; + + void insertFile(const std::wstring &filePath, FilesOrigin &origin, FILETIME fileTime); + + void removeFile(FileEntry::Index 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, int *origin = nullptr); + + /** + * @brief remove the specified directory + * @param path directory to remove + */ + void removeDir(const std::wstring &path); + + bool remove(const std::wstring &fileName, int *origin) { + auto iter = m_Files.find(ToLower(fileName)); + if (iter != m_Files.end()) { + if (origin != nullptr) { + FileEntry::Ptr entry = m_FileRegister->getFile(iter->second); + if (entry.get() != nullptr) { + bool ignore; + *origin = entry->getOrigin(ignore); } + } + return m_FileRegister->removeFile(iter->second); + } else { + return false; } + } - bool hasContentsFromOrigin(int originID) const; + bool hasContentsFromOrigin(int originID) const; - FilesOrigin& createOrigin(const std::wstring& originName, const std::wstring& directory, int priority); + FilesOrigin &createOrigin(const std::wstring &originName, const std::wstring &directory, int priority); - void removeFiles(const std::set& indices); + void removeFiles(const std::set &indices); private: - DirectoryEntry(const DirectoryEntry& reference); - DirectoryEntry& operator=(const DirectoryEntry& reference); - - void insert(const std::wstring& fileName, FilesOrigin& origin, FILETIME fileTime, const std::wstring& archive) { - std::wstring fileNameLower = ToLower(fileName); - auto iter = m_Files.find(fileNameLower); - FileEntry::Ptr file; - if (iter != m_Files.end()) { - file = m_FileRegister->getFile(iter->second); - } else { - file = m_FileRegister->createFile(fileName, this); - // TODO this has been observed to cause a crash, no clue why - m_Files[fileNameLower] = file->getIndex(); - } - file->addOrigin(origin.getID(), fileTime, archive); - origin.addFile(file->getIndex()); + + DirectoryEntry(const DirectoryEntry &reference); + DirectoryEntry &operator=(const DirectoryEntry &reference); + + void insert(const std::wstring &fileName, FilesOrigin &origin, FILETIME fileTime, const std::wstring &archive) { + std::wstring fileNameLower = ToLower(fileName); + auto iter = m_Files.find(fileNameLower); + FileEntry::Ptr file; + if (iter != m_Files.end()) { + file = m_FileRegister->getFile(iter->second); + } else { + file = m_FileRegister->createFile(fileName, this); + // TODO this has been observed to cause a crash, no clue why + m_Files[fileNameLower] = file->getIndex(); } + file->addOrigin(origin.getID(), fileTime, archive); + origin.addFile(file->getIndex()); + } - void addFiles(FilesOrigin& origin, wchar_t* buffer, int bufferOffset); - void addFiles(FilesOrigin& origin, BSA::Folder::Ptr archiveFolder, FILETIME& fileTime, - const std::wstring& archiveName); + void addFiles(FilesOrigin &origin, wchar_t *buffer, int bufferOffset); + void addFiles(FilesOrigin &origin, BSA::Folder::Ptr archiveFolder, FILETIME &fileTime, const std::wstring &archiveName); - DirectoryEntry* getSubDirectory(const std::wstring& name, bool create, int originID = -1); + DirectoryEntry *getSubDirectory(const std::wstring &name, bool create, int originID = -1); - DirectoryEntry* getSubDirectoryRecursive(const std::wstring& path, bool create, int originID = -1); + DirectoryEntry *getSubDirectoryRecursive(const std::wstring &path, bool create, int originID = -1); - void removeDirRecursive(); + void removeDirRecursive(); private: - boost::shared_ptr m_FileRegister; - boost::shared_ptr m_OriginConnection; - std::wstring m_Name; - std::map m_Files; - std::vector m_SubDirectories; + boost::shared_ptr m_FileRegister; + boost::shared_ptr m_OriginConnection; + + std::wstring m_Name; + std::map m_Files; + std::vector m_SubDirectories; - DirectoryEntry* m_Parent; - std::set m_Origins; + DirectoryEntry *m_Parent; + std::set m_Origins; - bool m_Populated; + bool m_Populated; + + bool m_TopLevel; - bool m_TopLevel; }; + } // namespace MOShared #endif // DIRECTORYENTRY_H diff --git a/src/shared/error_report.cpp b/src/shared/error_report.cpp index fffd77b9..6d091630 100644 --- a/src/shared/error_report.cpp +++ b/src/shared/error_report.cpp @@ -23,71 +23,77 @@ along with Mod Organizer. If not, see . namespace MOShared { -void reportError(LPCSTR format, ...) { - char buffer[1025]; - memset(buffer, 0, sizeof(char) * 1025); - va_list argList; - va_start(argList, format); +void reportError(LPCSTR format, ...) +{ + char buffer[1025]; + memset(buffer, 0, sizeof(char) * 1025); - vsnprintf(buffer, 1024, format, argList); - va_end(argList); + va_list argList; + va_start(argList, format); - MessageBoxA(nullptr, buffer, "Error", MB_OK | MB_ICONERROR); + vsnprintf(buffer, 1024, format, argList); + va_end(argList); + + MessageBoxA(nullptr, buffer, "Error", MB_OK | MB_ICONERROR); } -void reportError(LPCWSTR format, ...) { - WCHAR buffer[1025]; - memset(buffer, 0, sizeof(WCHAR) * 1025); +void reportError(LPCWSTR format, ...) +{ + WCHAR buffer[1025]; + memset(buffer, 0, sizeof(WCHAR) * 1025); - va_list argList; - va_start(argList, format); + va_list argList; + va_start(argList, format); - _vsnwprintf_s(buffer, 1024, format, argList); - va_end(argList); + _vsnwprintf_s(buffer, 1024, format, argList); + va_end(argList); - MessageBoxW(nullptr, buffer, L"Error", MB_OK | MB_ICONERROR); + MessageBoxW(nullptr, buffer, L"Error", MB_OK | MB_ICONERROR); } -std::string getCurrentErrorStringA() { - LPSTR buffer = nullptr; - DWORD errorCode = ::GetLastError(); +std::string getCurrentErrorStringA() +{ + LPSTR buffer = nullptr; + + DWORD errorCode = ::GetLastError(); - if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, errorCode, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, 0, nullptr) == 0) { - ::SetLastError(errorCode); - return std::string(); - } else { - LPSTR lastChar = buffer + strlen(buffer) - 2; - *lastChar = '\0'; + if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPSTR)&buffer, 0, nullptr) == 0) { + ::SetLastError(errorCode); + return std::string(); + } else { + LPSTR lastChar = buffer + strlen(buffer) - 2; + *lastChar = '\0'; - std::string result(buffer); + std::string result(buffer); - LocalFree(buffer); - ::SetLastError(errorCode); - return result; - } + LocalFree(buffer); + ::SetLastError(errorCode); + return result; + } } -std::wstring getCurrentErrorStringW() { - LPWSTR buffer = nullptr; +std::wstring getCurrentErrorStringW() +{ + LPWSTR buffer = nullptr; - DWORD errorCode = ::GetLastError(); + DWORD errorCode = ::GetLastError(); - if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, nullptr, errorCode, - MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&buffer, 0, nullptr) == 0) { - ::SetLastError(errorCode); - return std::wstring(); - } else { - LPWSTR lastChar = buffer + wcslen(buffer) - 2; - *lastChar = '\0'; + if (FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, + nullptr, errorCode, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPWSTR)&buffer, 0, nullptr) == 0) { + ::SetLastError(errorCode); + return std::wstring(); + } else { + LPWSTR lastChar = buffer + wcslen(buffer) - 2; + *lastChar = '\0'; - std::wstring result(buffer); + std::wstring result(buffer); - LocalFree(buffer); - ::SetLastError(errorCode); - return result; - } + LocalFree(buffer); + ::SetLastError(errorCode); + return result; + } } } // namespace MOShared diff --git a/src/shared/leaktrace.cpp b/src/shared/leaktrace.cpp index dd85c4b4..1ff1fa9d 100644 --- a/src/shared/leaktrace.cpp +++ b/src/shared/leaktrace.cpp @@ -1,47 +1,57 @@ #include "leaktrace.h" #include "stackdata.h" -#include #include -#include -#include +#include #include -#include +#include #include +#include +#include + using namespace MOShared; + static struct __TraceData { - void regTrace(void* pointer, const char* functionName, int line) { - m_Traces[reinterpret_cast(pointer)] = StackData(functionName, line); - } - void deregTrace(void* pointer) { - auto iter = m_Traces.find(reinterpret_cast(pointer)); - if (iter != m_Traces.end()) { - m_Traces.erase(iter); - } + void regTrace(void *pointer, const char *functionName, int line) { + m_Traces[reinterpret_cast(pointer)] = StackData(functionName, line); + } + void deregTrace(void *pointer) { + auto iter = m_Traces.find(reinterpret_cast(pointer)); + if (iter != m_Traces.end()) { + m_Traces.erase(iter); } + } - ~__TraceData() { - std::map> result; - for (auto iter = m_Traces.begin(); iter != m_Traces.end(); ++iter) { - result[iter->second].push_back(iter->first); - } - for (auto iter = result.begin(); iter != result.end(); ++iter) { - printf("-----------------------------------\n" - "%d objects not freed, allocated at:\n%s", - iter->second.size(), iter->first.toString().c_str()); - printf("Addresses: "); - for (int i = 0; i < (std::min)(5, static_cast(iter->second.size())); ++i) { - printf("%p, ", reinterpret_cast(iter->second[i])); - } - printf("\n"); - } + ~__TraceData() { + std::map > result; + for (auto iter = m_Traces.begin(); iter != m_Traces.end(); ++iter) { + result[iter->second].push_back(iter->first); + } + for (auto iter = result.begin(); iter != result.end(); ++iter) { + printf("-----------------------------------\n" + "%d objects not freed, allocated at:\n%s", + iter->second.size(), iter->first.toString().c_str()); + printf("Addresses: "); + for (int i = 0; + i < (std::min)(5, static_cast(iter->second.size())); ++i) { + printf("%p, ", reinterpret_cast(iter->second[i])); + } + printf("\n"); } + } - std::map m_Traces; + std::map m_Traces; } __trace; -void LeakTrace::TraceAlloc(void* ptr, const char* functionName, int line) { __trace.regTrace(ptr, functionName, line); } -void LeakTrace::TraceDealloc(void* ptr) { __trace.deregTrace(ptr); } +void LeakTrace::TraceAlloc(void *ptr, const char *functionName, int line) +{ + __trace.regTrace(ptr, functionName, line); +} + +void LeakTrace::TraceDealloc(void *ptr) +{ + __trace.deregTrace(ptr); +} diff --git a/src/shared/leaktrace.h b/src/shared/leaktrace.h index b0d04129..4985925e 100644 --- a/src/shared/leaktrace.h +++ b/src/shared/leaktrace.h @@ -1,12 +1,13 @@ #ifndef LEAKTRACE_H #define LEAKTRACE_H + namespace LeakTrace { -void TraceAlloc(void* ptr, const char* functionName, int line); -void TraceDealloc(void* ptr); +void TraceAlloc(void *ptr, const char *functionName, int line); +void TraceDealloc(void *ptr); -}; // namespace LeakTrace +}; #ifdef TRACE_LEAKS diff --git a/src/shared/stackdata.cpp b/src/shared/stackdata.cpp index 5f77bbfa..b336593a 100644 --- a/src/shared/stackdata.cpp +++ b/src/shared/stackdata.cpp @@ -1,150 +1,169 @@ #include "stackdata.h" -#include "error_report.h" #include "util.h" #include +#include #include -#include #include -#include +#include "error_report.h" +#include + using namespace MOShared; #if defined _MSC_VER -static void initDbgIfNecess() { - HANDLE process = ::GetCurrentProcess(); - static std::set initialized; - if (initialized.find(::GetCurrentProcessId()) == initialized.end()) { - static bool firstCall = true; - if (firstCall) { - ::SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS); - firstCall = false; - } - if (!::SymInitialize(process, NULL, TRUE)) { - printf("failed to initialize symbols: %lu", ::GetLastError()); - } - initialized.insert(::GetCurrentProcessId()); +static void initDbgIfNecess() +{ + HANDLE process = ::GetCurrentProcess(); + static std::set initialized; + if (initialized.find(::GetCurrentProcessId()) == initialized.end()) { + static bool firstCall = true; + if (firstCall) { + ::SymSetOptions(SYMOPT_UNDNAME | SYMOPT_DEFERRED_LOADS); + firstCall = false; } + if (!::SymInitialize(process, NULL, TRUE)) { + printf("failed to initialize symbols: %lu", ::GetLastError()); + } + initialized.insert(::GetCurrentProcessId()); + } +} + + + +StackData::StackData() + : m_Count(0) + , m_Function() + , m_Line(-1) +{ + initTrace(); } -StackData::StackData() : m_Count(0), m_Function(), m_Line(-1) { initTrace(); } +StackData::StackData(const char *function, int line) + : m_Count(0) + , m_Function(function) + , m_Line(line) +{ -StackData::StackData(const char* function, int line) : m_Count(0), m_Function(function), m_Line(line) {} +} std::string StackData::toString() const { - initDbgIfNecess(); + initDbgIfNecess(); - char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; - PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer; - symbol->SizeOfStruct = sizeof(SYMBOL_INFO); - symbol->MaxNameLen = MAX_SYM_NAME; + char buffer[sizeof(SYMBOL_INFO) + MAX_SYM_NAME * sizeof(TCHAR)]; + PSYMBOL_INFO symbol = (PSYMBOL_INFO)buffer; + symbol->SizeOfStruct = sizeof(SYMBOL_INFO); + symbol->MaxNameLen = MAX_SYM_NAME; - std::ostringstream stackStream; + std::ostringstream stackStream; - if (m_Function.length() > 0) { - stackStream << "[" << m_Function << ":" << m_Line << "]\n"; - } + if (m_Function.length() > 0) { + stackStream << "[" << m_Function << ":" << m_Line << "]\n"; + } - for (unsigned int i = 0; i < m_Count; ++i) { - DWORD64 displacement = 0; - if (!::SymFromAddr(::GetCurrentProcess(), (DWORD64)m_Stack[i], &displacement, symbol)) { - stackStream << m_Count - i - 1 << ": [" << m_Stack[i] << "]\n"; - } else { - stackStream << m_Count - i - 1 << ": " << symbol->Name << "\n"; - } + for(unsigned int i = 0; i < m_Count; ++i) { + DWORD64 displacement = 0; + if (!::SymFromAddr(::GetCurrentProcess(), (DWORD64)m_Stack[i], &displacement, symbol)) { + stackStream << m_Count - i - 1 << ": [" << m_Stack[i] << "]\n"; + } else { + stackStream << m_Count - i - 1 << ": " << symbol->Name << "\n"; } - return stackStream.str(); + } + return stackStream.str(); } void StackData::load_modules(HANDLE process, DWORD processID) { - HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processID); - if (snap == INVALID_HANDLE_VALUE) - return; - - MODULEENTRY32 entry; - entry.dwSize = sizeof(entry); - - if (Module32First(snap, &entry)) { - do { - std::string fileName = ToString(entry.szExePath, false); - std::string moduleName = ToString(entry.szModule, false); - SymLoadModule64(process, NULL, fileName.c_str(), moduleName.c_str(), (DWORD64)entry.modBaseAddr, - entry.modBaseSize); - } while (Module32Next(snap, &entry)); - } - CloseHandle(snap); + HANDLE snap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, processID); + if (snap == INVALID_HANDLE_VALUE) + return; + + MODULEENTRY32 entry; + entry.dwSize = sizeof(entry); + + if (Module32First(snap, &entry)) { + do { + std::string fileName = ToString(entry.szExePath, false); + std::string moduleName = ToString(entry.szModule, false); + SymLoadModule64(process, NULL, fileName.c_str(), moduleName.c_str(), (DWORD64) entry.modBaseAddr, entry.modBaseSize); + } while (Module32Next(snap, &entry)); + } + CloseHandle(snap); } -#pragma warning(disable : 4748) +#pragma warning( disable : 4748 ) void StackData::initTrace() { #ifdef _X86_ - load_modules(::GetCurrentProcess(), ::GetCurrentProcessId()); - CONTEXT context; - std::memset(&context, 0, sizeof(CONTEXT)); - context.ContextFlags = CONTEXT_CONTROL; - // Why only for 64 bit? + load_modules(::GetCurrentProcess(), ::GetCurrentProcessId()); + CONTEXT context; + std::memset(&context, 0, sizeof(CONTEXT)); + context.ContextFlags = CONTEXT_CONTROL; + //Why only for 64 bit? #if BOOST_ARCH_X86_64 || defined(__clang__) - ::RtlCaptureContext(&context); + ::RtlCaptureContext(&context); #else - __asm { + __asm + { Label: mov [context.Ebp], ebp; mov [context.Esp], esp; mov eax, [Label]; mov [context.Eip], eax; - } + } #endif - STACKFRAME64 stackFrame; - ::ZeroMemory(&stackFrame, sizeof(STACKFRAME64)); - stackFrame.AddrPC.Offset = context.Eip; - stackFrame.AddrPC.Mode = AddrModeFlat; - stackFrame.AddrFrame.Offset = context.Ebp; - stackFrame.AddrFrame.Mode = AddrModeFlat; - stackFrame.AddrStack.Offset = context.Esp; - stackFrame.AddrStack.Mode = AddrModeFlat; - m_Count = 0; - while (m_Count < FRAMES_TO_CAPTURE) { - if (!StackWalk64(IMAGE_FILE_MACHINE_I386, ::GetCurrentProcess(), ::GetCurrentThread(), &stackFrame, &context, - NULL, &SymFunctionTableAccess64, &SymGetModuleBase64, NULL)) { - break; - } - - if (stackFrame.AddrPC.Offset == 0) { - continue; - break; - } - - m_Stack[m_Count++] = reinterpret_cast(stackFrame.AddrPC.Offset); + STACKFRAME64 stackFrame; + ::ZeroMemory(&stackFrame, sizeof(STACKFRAME64)); + stackFrame.AddrPC.Offset = context.Eip; + stackFrame.AddrPC.Mode = AddrModeFlat; + stackFrame.AddrFrame.Offset = context.Ebp; + stackFrame.AddrFrame.Mode = AddrModeFlat; + stackFrame.AddrStack.Offset = context.Esp; + stackFrame.AddrStack.Mode = AddrModeFlat; + m_Count = 0; + while (m_Count < FRAMES_TO_CAPTURE) { + if (!StackWalk64(IMAGE_FILE_MACHINE_I386, ::GetCurrentProcess(), + ::GetCurrentThread(), &stackFrame, &context, NULL, + &SymFunctionTableAccess64, &SymGetModuleBase64, NULL)) { + break; } + + if (stackFrame.AddrPC.Offset == 0) { + continue; + break; + } + + m_Stack[m_Count++] = reinterpret_cast(stackFrame.AddrPC.Offset); + } #endif } -bool MOShared::operator==(const StackData& LHS, const StackData& RHS) { - if (LHS.m_Count != RHS.m_Count) { + +bool MOShared::operator==(const StackData &LHS, const StackData &RHS) { + if (LHS.m_Count != RHS.m_Count) { + return false; + } else { + for (int i = 0; i < LHS.m_Count; ++i) { + if (LHS.m_Stack[i] != RHS.m_Stack[i]) { return false; - } else { - for (int i = 0; i < LHS.m_Count; ++i) { - if (LHS.m_Stack[i] != RHS.m_Stack[i]) { - return false; - } - } + } } - return true; + } + return true; } -bool MOShared::operator<(const StackData& LHS, const StackData& RHS) { - if (LHS.m_Count != RHS.m_Count) { - return LHS.m_Count < RHS.m_Count; - } else { - for (int i = 0; i < LHS.m_Count; ++i) { - if (LHS.m_Stack[i] != RHS.m_Stack[i]) { - return LHS.m_Stack[i] < RHS.m_Stack[i]; - } - } + +bool MOShared::operator<(const StackData &LHS, const StackData &RHS) { + if (LHS.m_Count != RHS.m_Count) { + return LHS.m_Count < RHS.m_Count; + } else { + for (int i = 0; i < LHS.m_Count; ++i) { + if (LHS.m_Stack[i] != RHS.m_Stack[i]) { + return LHS.m_Stack[i] < RHS.m_Stack[i]; + } } - return false; + } + return false; } #endif diff --git a/src/shared/stackdata.h b/src/shared/stackdata.h index 252b5ba4..7151699c 100644 --- a/src/shared/stackdata.h +++ b/src/shared/stackdata.h @@ -1,41 +1,50 @@ #ifndef STACKDATA_H #define STACKDATA_H -#include + #include +#include + namespace MOShared { -class StackData { - friend bool operator==(const StackData& LHS, const StackData& RHS); - friend bool operator<(const StackData& LHS, const StackData& RHS); +class StackData { + friend bool operator==(const StackData &LHS, const StackData &RHS); + friend bool operator<(const StackData &LHS, const StackData &RHS); public: - StackData(); - StackData(const char* function, int line); - std::string toString() const; + StackData(); + StackData(const char *function, int line); + + std::string toString() const; private: - void load_modules(HANDLE process, DWORD processID); - void initTrace(); + void load_modules(HANDLE process, DWORD processID); + + void initTrace(); private: - static const int FRAMES_TO_SKIP = 1; - static const int FRAMES_TO_CAPTURE = 20; + + static const int FRAMES_TO_SKIP = 1; + static const int FRAMES_TO_CAPTURE = 20; private: - LPVOID m_Stack[FRAMES_TO_CAPTURE]; - USHORT m_Count; - std::string m_Function; - int m_Line; + + LPVOID m_Stack[FRAMES_TO_CAPTURE]; + USHORT m_Count; + std::string m_Function; + int m_Line; + }; -bool operator==(const StackData& LHS, const StackData& RHS); -bool operator<(const StackData& LHS, const StackData& RHS); +bool operator==(const StackData &LHS, const StackData &RHS); + +bool operator<(const StackData &LHS, const StackData &RHS); } // namespace MOShared + #endif // STACKDATA_H diff --git a/src/shared/util.cpp b/src/shared/util.cpp index 0243bb88..5491a9e6 100644 --- a/src/shared/util.cpp +++ b/src/shared/util.cpp @@ -18,136 +18,161 @@ along with Mod Organizer. If not, see . */ #include "util.h" -#include "error_report.h" #include "windows_error.h" +#include "error_report.h" -#include -#include -#include +#include #include +#include +#include #include -#include +#include namespace MOShared { -bool FileExists(const std::string& filename) { - DWORD dwAttrib = ::GetFileAttributesA(filename.c_str()); - return (dwAttrib != INVALID_FILE_ATTRIBUTES); +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()); +bool FileExists(const std::wstring &filename) +{ + DWORD dwAttrib = ::GetFileAttributesW(filename.c_str()); - return (dwAttrib != INVALID_FILE_ATTRIBUTES); + 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()); +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); +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; + 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); +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; + 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& ToLower(std::string& text) { - std::transform(text.begin(), text.end(), text.begin(), locToLower); - return text; +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 &ToLower(std::string &text) +{ + std::transform(text.begin(), text.end(), text.begin(), locToLower); + return text; } -std::string ToLower(const std::string& text) { - std::string result(text); - std::transform(result.begin(), result.end(), result.begin(), locToLower); - return result; +std::string ToLower(const std::string &text) +{ + std::string result(text); + std::transform(result.begin(), result.end(), result.begin(), locToLower); + return result; } -std::wstring& ToLower(std::wstring& text) { - std::transform(text.begin(), text.end(), text.begin(), locToLowerW); - return text; +std::wstring &ToLower(std::wstring &text) +{ + std::transform(text.begin(), text.end(), text.begin(), locToLowerW); + return text; } -std::wstring ToLower(const std::wstring& text) { - std::wstring result(text); - std::transform(result.begin(), result.end(), result.begin(), locToLowerW); - return result; +std::wstring ToLower(const std::wstring &text) +{ + std::wstring result(text); + std::transform(result.begin(), result.end(), result.begin(), locToLowerW); + return result; } -bool CaseInsenstiveComparePred(wchar_t lhs, wchar_t rhs) { return std::tolower(lhs, loc) == std::tolower(rhs, loc); } +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); - }); +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"); +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"); } - 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; + 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; + } } + } // namespace MOShared diff --git a/src/shared/util.h b/src/shared/util.h index 4b390cf3..1e498059 100644 --- a/src/shared/util.h +++ b/src/shared/util.h @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #ifndef UTIL_H #define UTIL_H + #include #define WIN32_LEAN_AND_MEAN #include @@ -27,23 +28,23 @@ along with Mod Organizer. If not, see . 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::string &filename); +bool FileExists(const std::wstring &filename); -bool FileExists(const std::wstring& searchPath, 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 ToString(const std::wstring &source, bool utf8); +std::wstring ToWString(const std::string &source, bool utf8); -std::string& ToLower(std::string& text); -std::string ToLower(const std::string& text); +std::string &ToLower(std::string &text); +std::string ToLower(const std::string &text); -std::wstring& ToLower(std::wstring& text); -std::wstring ToLower(const std::wstring& text); +std::wstring &ToLower(std::wstring &text); +std::wstring ToLower(const std::wstring &text); -bool CaseInsensitiveEqual(const std::wstring& lhs, const std::wstring& rhs); +bool CaseInsensitiveEqual(const std::wstring &lhs, const std::wstring &rhs); -VS_FIXEDFILEINFO GetFileVersion(const std::wstring& fileName); +VS_FIXEDFILEINFO GetFileVersion(const std::wstring &fileName); } // namespace MOShared diff --git a/src/shared/windows_error.cpp b/src/shared/windows_error.cpp index ab5c7b8e..97a58a20 100644 --- a/src/shared/windows_error.cpp +++ b/src/shared/windows_error.cpp @@ -22,27 +22,28 @@ along with Mod Organizer. If not, see . 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(); +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 66c512f6..03ce0abd 100644 --- a/src/shared/windows_error.h +++ b/src/shared/windows_error.h @@ -20,6 +20,7 @@ along with Mod Organizer. If not, see . #ifndef WINDOWS_ERROR_H #define WINDOWS_ERROR_H + #include #define WIN32_LEAN_AND_MEAN #include @@ -28,15 +29,14 @@ 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; } - + 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); - + std::string constructMessage(const std::string& input, int errorcode); private: - int m_ErrorCode; + int m_ErrorCode; }; } // namespace MOShared diff --git a/src/singleinstance.cpp b/src/singleinstance.cpp index b681317c..89332f9b 100644 --- a/src/singleinstance.cpp +++ b/src/singleinstance.cpp @@ -19,85 +19,92 @@ along with Mod Organizer. If not, see . #include "singleinstance.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; -SingleInstance::SingleInstance(bool forcePrimary, QObject* parent) : QObject(parent), m_PrimaryInstance(false) { - m_SharedMem.setKey(s_Key); - if (!m_SharedMem.create(1)) { - if (forcePrimary) { - while (m_SharedMem.error() == QSharedMemory::AlreadyExists) { - Sleep(500); - if (m_SharedMem.create(1)) { - m_PrimaryInstance = true; - break; - } - } +SingleInstance::SingleInstance(bool forcePrimary, QObject *parent) : + QObject(parent), m_PrimaryInstance(false) +{ + m_SharedMem.setKey(s_Key); + if (!m_SharedMem.create(1)) { + if (forcePrimary) { + while (m_SharedMem.error() == QSharedMemory::AlreadyExists) { + Sleep(500); + if (m_SharedMem.create(1)) { + m_PrimaryInstance = true; + break; } + } + } - if (m_SharedMem.error() == QSharedMemory::AlreadyExists) { - m_SharedMem.attach(); - m_PrimaryInstance = false; - } - if ((m_SharedMem.error() != QSharedMemory::NoError) && (m_SharedMem.error() != QSharedMemory::AlreadyExists)) { - throw MOBase::MyException(tr("SHM error: %1").arg(m_SharedMem.errorString())); - } - } else { - m_PrimaryInstance = true; + if (m_SharedMem.error() == QSharedMemory::AlreadyExists) { + m_SharedMem.attach(); + m_PrimaryInstance = false; } - if (m_PrimaryInstance) { - connect(&m_Server, SIGNAL(newConnection()), this, SLOT(receiveMessage())); - // has to be called before listen - m_Server.setSocketOptions(QLocalServer::WorldAccessOption); - m_Server.listen(s_Key); + if ((m_SharedMem.error() != QSharedMemory::NoError) && + (m_SharedMem.error() != QSharedMemory::AlreadyExists)) { + throw MOBase::MyException(tr("SHM error: %1").arg(m_SharedMem.errorString())); } + } else { + m_PrimaryInstance = true; + } + if (m_PrimaryInstance) { + connect(&m_Server, SIGNAL(newConnection()), this, SLOT(receiveMessage())); + // has to be called before listen + m_Server.setSocketOptions(QLocalServer::WorldAccessOption); + m_Server.listen(s_Key); + } } -void SingleInstance::sendMessage(const QString& message) { - if (m_PrimaryInstance) { - // 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); - } +void SingleInstance::sendMessage(const QString &message) +{ + if (m_PrimaryInstance) { + // nobody there to receive the message + return; + } + QLocalSocket socket(this); - // other instance may be just starting up - socket.connectToServer(s_Key, QIODevice::WriteOnly); - connected = socket.waitForConnected(s_Timeout); + bool connected = false; + for(int i = 0; i < 2 && !connected; ++i) { + if (i > 0) { + Sleep(250); } - if (!connected) { - reportError(tr("failed to connect to running instance: %1").arg(socket.errorString())); - return; - } + // other instance may be just starting up + socket.connectToServer(s_Key, QIODevice::WriteOnly); + connected = socket.waitForConnected(s_Timeout); + } - socket.write(message.toUtf8()); - if (!socket.waitForBytesWritten(s_Timeout)) { - reportError(tr("failed to communicate with running instance: %1").arg(socket.errorString())); - return; - } + if (!connected) { + reportError(tr("failed to connect to running instance: %1").arg(socket.errorString())); + return; + } - socket.disconnectFromServer(); + socket.write(message.toUtf8()); + if (!socket.waitForBytesWritten(s_Timeout)) { + reportError(tr("failed to communicate with running instance: %1").arg(socket.errorString())); + return; + } + + socket.disconnectFromServer(); } -void SingleInstance::receiveMessage() { - QLocalSocket* socket = m_Server.nextPendingConnection(); - if (!socket->waitForReadyRead(s_Timeout)) { - reportError(tr("failed to receive data from secondary instance: %1").arg(socket->errorString())); - return; - } - QString message = QString::fromUtf8(socket->readAll().constData()); - emit messageSent(message); - socket->disconnectFromServer(); +void SingleInstance::receiveMessage() +{ + QLocalSocket *socket = m_Server.nextPendingConnection(); + if (!socket->waitForReadyRead(s_Timeout)) { + reportError(tr("failed to receive data from secondary instance: %1").arg(socket->errorString())); + return; + } + + QString message = QString::fromUtf8(socket->readAll().constData()); + emit messageSent(message); + socket->disconnectFromServer(); } diff --git a/src/singleinstance.h b/src/singleinstance.h index 9011d93a..14d49d6d 100644 --- a/src/singleinstance.h +++ b/src/singleinstance.h @@ -20,64 +20,69 @@ along with Mod Organizer. If not, see . #ifndef SINGLEINSTANCE_H #define SINGLEINSTANCE_H -#include #include #include +#include + /** * used to ensure only a single instance of Mod Organizer is started and to * allow secondary instances to send messages to the primary (visible) one. This way, * secondary instances can start a download in the primary one **/ -class SingleInstance : public QObject { +class SingleInstance : public QObject +{ - Q_OBJECT + Q_OBJECT public: - /** - * @brief constructor - * - * @param forcePrimary if true, this will be treated as the primary instance even - * if another instance is running. This is used after an update since - * the other instance is assumed to be in the process of quitting - * @param parent parent object - * @todo the forcePrimary parameter makes no sense. The second instance after an update - * needs to delete the files from before the update so the first instance needs to quit - * first anyway - **/ - explicit SingleInstance(bool forcePrimary, QObject* parent = 0); - - /** - * @return true if this is the primary instance (the one that gets to display a UI) - **/ - bool primaryInstance() const { return m_PrimaryInstance; } - - /** - * send a message to the primary instance. This can be used to transmit download urls - * - * @param message message to send - **/ - void sendMessage(const QString& message); + + /** + * @brief constructor + * + * @param forcePrimary if true, this will be treated as the primary instance even + * if another instance is running. This is used after an update since + * the other instance is assumed to be in the process of quitting + * @param parent parent object + * @todo the forcePrimary parameter makes no sense. The second instance after an update + * needs to delete the files from before the update so the first instance needs to quit + * first anyway + **/ + explicit SingleInstance(bool forcePrimary, QObject *parent = 0); + + /** + * @return true if this is the primary instance (the one that gets to display a UI) + **/ + bool primaryInstance() const { return m_PrimaryInstance; } + + /** + * send a message to the primary instance. This can be used to transmit download urls + * + * @param message message to send + **/ + void sendMessage(const QString &message); signals: - /** - * @brief emitted when a secondary instance has sent a message (to us) - * - * @param message the message we received - **/ - void messageSent(const QString& message); + /** + * @brief emitted when a secondary instance has sent a message (to us) + * + * @param message the message we received + **/ + void messageSent(const QString &message); public slots: private slots: - void receiveMessage(); + void receiveMessage(); private: - bool m_PrimaryInstance; - QSharedMemory m_SharedMem; - QLocalServer m_Server; + + bool m_PrimaryInstance; + QSharedMemory m_SharedMem; + QLocalServer m_Server; + }; #endif // SINGLEINSTANCE_H diff --git a/src/spawn.cpp b/src/spawn.cpp index f5b0bdb2..ac8ccf30 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -21,10 +21,10 @@ along with Mod Organizer. If not, see . #include "report.h" #include "utility.h" -#include -#include #include #include +#include +#include #include #include @@ -37,127 +37,137 @@ along with Mod Organizer. If not, see . using namespace MOBase; using namespace MOShared; -static const int BUFSIZE = 4096; -static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool suspended, bool hooked, - HANDLE stdOut, HANDLE stdErr, HANDLE& processHandle, HANDLE& threadHandle) { - BOOL inheritHandles = FALSE; - STARTUPINFO si; - ::ZeroMemory(&si, sizeof(si)); - if (stdOut != INVALID_HANDLE_VALUE) { - si.hStdOutput = stdOut; - inheritHandles = TRUE; - si.dwFlags |= STARTF_USESTDHANDLES; - } - if (stdErr != INVALID_HANDLE_VALUE) { - si.hStdError = stdErr; - inheritHandles = TRUE; - si.dwFlags |= STARTF_USESTDHANDLES; - } - si.cb = sizeof(si); - size_t length = wcslen(binary) + wcslen(arguments) + 4; - wchar_t* commandLine = nullptr; - if (arguments[0] != L'\0') { - commandLine = new wchar_t[length]; - _snwprintf(commandLine, length, L"\"%ls\" %ls", binary, arguments); - } else { - commandLine = new wchar_t[length]; - _snwprintf_s(commandLine, length, _TRUNCATE, L"\"%ls\"", binary); - } - - QString moPath = QCoreApplication::applicationDirPath(); +static const int BUFSIZE = 4096; - boost::scoped_array oldPath(new TCHAR[BUFSIZE]); - DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE); - if (offset > BUFSIZE) { - oldPath.reset(new TCHAR[offset]); - ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset); - } +static bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, + bool suspended, bool hooked, + HANDLE stdOut, HANDLE stdErr, + HANDLE& processHandle, HANDLE& threadHandle) +{ + BOOL inheritHandles = FALSE; + STARTUPINFO si; + ::ZeroMemory(&si, sizeof(si)); + if (stdOut != INVALID_HANDLE_VALUE) { + si.hStdOutput = stdOut; + inheritHandles = TRUE; + si.dwFlags |= STARTF_USESTDHANDLES; + } + if (stdErr != INVALID_HANDLE_VALUE) { + si.hStdError = stdErr; + inheritHandles = TRUE; + si.dwFlags |= STARTF_USESTDHANDLES; + } + si.cb = sizeof(si); + size_t length = wcslen(binary) + wcslen(arguments) + 4; + wchar_t *commandLine = nullptr; + if (arguments[0] != L'\0') { + commandLine = new wchar_t[length]; + _snwprintf(commandLine, length, L"\"%ls\" %ls", binary, arguments); + } else { + commandLine = new wchar_t[length]; + _snwprintf_s(commandLine, length, _TRUNCATE, L"\"%ls\"", binary); + } + + QString moPath = QCoreApplication::applicationDirPath(); + + boost::scoped_array oldPath(new TCHAR[BUFSIZE]); + DWORD offset = ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), BUFSIZE); + if (offset > BUFSIZE) { + oldPath.reset(new TCHAR[offset]); + ::GetEnvironmentVariable(TEXT("PATH"), oldPath.get(), offset); + } + + { + boost::scoped_array newPath(new TCHAR[offset + moPath.length() + 2]); + _tcsncpy(newPath.get(), oldPath.get(), offset); + newPath.get()[offset] = '\0'; + _tcsncat(newPath.get(), TEXT(";"), 1); + _tcsncat(newPath.get(), ToWString(QDir::toNativeSeparators(moPath)).c_str(), moPath.length()); + + ::SetEnvironmentVariable(TEXT("PATH"), newPath.get()); + } + + PROCESS_INFORMATION pi; + BOOL success = FALSE; + if (hooked) { + success = ::CreateProcessHooked(nullptr, + commandLine, + nullptr, nullptr, // no special process or thread attributes + inheritHandles, // inherit handles if we plan to use stdout or stderr reroute + CREATE_BREAKAWAY_FROM_JOB, + nullptr, // same environment as parent + currentDirectory, // current directory + &si, &pi // startup and process information + ); + } else { + success = ::CreateProcess(nullptr, + commandLine, + nullptr, nullptr, // no special process or thread attributes + inheritHandles, // inherit handles if we plan to use stdout or stderr reroute + CREATE_BREAKAWAY_FROM_JOB, + nullptr, // same environment as parent + currentDirectory, // current directory + &si, &pi // startup and process information + ); + } + + ::SetEnvironmentVariable(TEXT("PATH"), oldPath.get()); + + delete [] commandLine; + + if (!success) { + throw windows_error("failed to start process"); + } + + processHandle = pi.hProcess; + threadHandle = pi.hThread; + return true; +} - { - boost::scoped_array newPath(new TCHAR[offset + moPath.length() + 2]); - _tcsncpy(newPath.get(), oldPath.get(), offset); - newPath.get()[offset] = '\0'; - _tcsncat(newPath.get(), TEXT(";"), 1); - _tcsncat(newPath.get(), ToWString(QDir::toNativeSeparators(moPath)).c_str(), moPath.length()); - ::SetEnvironmentVariable(TEXT("PATH"), newPath.get()); +HANDLE startBinary(const QFileInfo &binary, + const QString &arguments, + const QDir ¤tDirectory, + bool hooked, + HANDLE stdOut, + HANDLE stdErr) +{ + HANDLE processHandle, threadHandle; + std::wstring binaryName = ToWString(QDir::toNativeSeparators(binary.absoluteFilePath())); + std::wstring currentDirectoryName = ToWString(QDir::toNativeSeparators(currentDirectory.absolutePath())); + + try { + if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), + true, hooked, stdOut, stdErr, processHandle, threadHandle)) { + reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName())); + return INVALID_HANDLE_VALUE; } - - PROCESS_INFORMATION pi; - BOOL success = FALSE; - if (hooked) { - success = - ::CreateProcessHooked(nullptr, commandLine, nullptr, nullptr, // no special process or thread attributes - inheritHandles, // inherit handles if we plan to use stdout or stderr reroute - CREATE_BREAKAWAY_FROM_JOB, - nullptr, // same environment as parent - currentDirectory, // current directory - &si, &pi // startup and process information - ); + } catch (const windows_error &e) { + if (e.getErrorCode() == ERROR_ELEVATION_REQUIRED) { + // TODO: check if this is really correct. Are all settings updated that the secondary instance may use? + + if (QMessageBox::question(nullptr, QObject::tr("Elevation required"), + QObject::tr("This process requires elevation to run.\n" + "This is a potential security risk so I highly advice you to investigate if\n" + "\"%1\"\n" + "can be installed to work without elevation.\n\n" + "Start elevated anyway? " + "(you will be asked if you want to allow ModOrganizer.exe to make changes to the system)").arg( + QDir::toNativeSeparators(binary.absoluteFilePath())), + QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { + ::ShellExecuteW(nullptr, L"runas", ToWString(QCoreApplication::applicationFilePath()).c_str(), + (std::wstring(L"\"") + binaryName + L"\" " + ToWString(arguments)).c_str(), currentDirectoryName.c_str(), SW_SHOWNORMAL); + return INVALID_HANDLE_VALUE; + } else { + return INVALID_HANDLE_VALUE; + } } else { - success = ::CreateProcess(nullptr, commandLine, nullptr, nullptr, // no special process or thread attributes - inheritHandles, // inherit handles if we plan to use stdout or stderr reroute - CREATE_BREAKAWAY_FROM_JOB, - nullptr, // same environment as parent - currentDirectory, // current directory - &si, &pi // startup and process information - ); - } - - ::SetEnvironmentVariable(TEXT("PATH"), oldPath.get()); - - delete[] commandLine; - - if (!success) { - throw windows_error("failed to start process"); - } - - processHandle = pi.hProcess; - threadHandle = pi.hThread; - return true; -} - -HANDLE startBinary(const QFileInfo& binary, const QString& arguments, const QDir& currentDirectory, bool hooked, - HANDLE stdOut, HANDLE stdErr) { - HANDLE processHandle, threadHandle; - std::wstring binaryName = ToWString(QDir::toNativeSeparators(binary.absoluteFilePath())); - std::wstring currentDirectoryName = ToWString(QDir::toNativeSeparators(currentDirectory.absolutePath())); - - try { - if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), true, hooked, stdOut, - stdErr, processHandle, threadHandle)) { - reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName())); - return INVALID_HANDLE_VALUE; - } - } catch (const windows_error& e) { - if (e.getErrorCode() == ERROR_ELEVATION_REQUIRED) { - // TODO: check if this is really correct. Are all settings updated that the secondary instance may use? - - if (QMessageBox::question( - nullptr, QObject::tr("Elevation required"), - QObject::tr( - "This process requires elevation to run.\n" - "This is a potential security risk so I highly advice you to investigate if\n" - "\"%1\"\n" - "can be installed to work without elevation.\n\n" - "Start elevated anyway? " - "(you will be asked if you want to allow ModOrganizer.exe to make changes to the system)") - .arg(QDir::toNativeSeparators(binary.absoluteFilePath())), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - ::ShellExecuteW(nullptr, L"runas", ToWString(QCoreApplication::applicationFilePath()).c_str(), - (std::wstring(L"\"") + binaryName + L"\" " + ToWString(arguments)).c_str(), - currentDirectoryName.c_str(), SW_SHOWNORMAL); - return INVALID_HANDLE_VALUE; - } else { - return INVALID_HANDLE_VALUE; - } - } else { - reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(e.what())); - return INVALID_HANDLE_VALUE; - } + reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(e.what())); + return INVALID_HANDLE_VALUE; } + } - ::CloseHandle(threadHandle); - return processHandle; + ::CloseHandle(threadHandle); + return processHandle; } diff --git a/src/spawn.h b/src/spawn.h index d2e3d99c..c2d99bdb 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -21,10 +21,11 @@ along with Mod Organizer. If not, see . #define SPAWN_H #define WIN32_LEAN_AND_MEAN -#include -#include -#include #include +#include +#include +#include + /** * @brief a dirty little trick so we can issue a clean restart from startBinary @@ -43,6 +44,7 @@ private: static ExitProxy *s_Instance; };*/ + /** * @brief spawn a binary with Mod Organizer injected * @@ -58,7 +60,9 @@ private: * @todo is the profile name even used any more? * @todo is the hooked parameter used? **/ -HANDLE startBinary(const QFileInfo& binary, const QString& arguments, const QDir& currentDirectory, bool hooked, +HANDLE startBinary(const QFileInfo &binary, const QString &arguments, + const QDir ¤tDirectory, bool hooked, HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE); #endif // SPAWN_H + diff --git a/src/syncoverwritedialog.cpp b/src/syncoverwritedialog.cpp index 3220f356..f03e29c5 100644 --- a/src/syncoverwritedialog.cpp +++ b/src/syncoverwritedialog.cpp @@ -20,133 +20,148 @@ along with Mod Organizer. If not, see . #include "syncoverwritedialog.h" #include "ui_syncoverwritedialog.h" -#include #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); +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); + 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); - } +SyncOverwriteDialog::~SyncOverwriteDialog() +{ + delete ui; } -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 { - qCritical("no directory structure for %s?", file.toUtf8().constData()); - delete newItem; - newItem = nullptr; - } - } else { - const FileEntry::Ptr 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 std::vector>& alternatives = entry->getAlternatives(); - for (std::vector>::const_iterator iter = alternatives.begin(); - iter != alternatives.end(); ++iter) { - addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(iter->first).getName()), - iter->first); - } - combo->setCurrentIndex(combo->count() - 1); - } else { - combo->setCurrentIndex(0); - } - ui->syncTree->setItemWidget(newItem, 1, combo); - } - if (newItem != nullptr) { - subTree->addChild(newItem); +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 { + qCritical("no directory structure for %s?", file.toUtf8().constData()); + delete newItem; + newItem = nullptr; + } + } else { + const FileEntry::Ptr 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 std::vector> &alternatives = entry->getAlternatives(); + for (std::vector>::const_iterator iter = alternatives.begin(); iter != alternatives.end(); ++iter) { + addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(iter->first).getName()), iter->first); } + 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::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)); - } - } - } + +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("."); - } + 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); + +void SyncOverwriteDialog::apply(const QString &modDirectory) +{ + applyTo(ui->syncTree->topLevelItem(0), "", modDirectory); } diff --git a/src/syncoverwritedialog.h b/src/syncoverwritedialog.h index d5952bda..79b30a8d 100644 --- a/src/syncoverwritedialog.h +++ b/src/syncoverwritedialog.h @@ -20,33 +20,36 @@ along with Mod Organizer. If not, see . #ifndef SYNCOVERWRITEDIALOG_H #define SYNCOVERWRITEDIALOG_H + #include "tutorabledialog.h" #include #include + namespace Ui { class SyncOverwriteDialog; } -class SyncOverwriteDialog : public MOBase::TutorableDialog { - Q_OBJECT - +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); + 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); + 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; + + Ui::SyncOverwriteDialog *ui; + QString m_SourcePath; + MOShared::DirectoryEntry *m_DirectoryStructure; + }; #endif // SYNCOVERWRITEDIALOG_H diff --git a/src/transfersavesdialog.cpp b/src/transfersavesdialog.cpp index dc139b39..b8dda056 100644 --- a/src/transfersavesdialog.cpp +++ b/src/transfersavesdialog.cpp @@ -19,12 +19,13 @@ along with Mod Organizer. If not, see . #include "transfersavesdialog.h" +#include "ui_transfersavesdialog.h" #include "iplugingame.h" #include "isavegame.h" #include "savegameinfo.h" -#include "ui_transfersavesdialog.h" #include +#include #include #include #include @@ -36,259 +37,324 @@ along with Mod Organizer. If not, see . #include #include #include -#include using namespace MOBase; using namespace MOShared; -// These two classes give the save-transfer box a smidgin of useful info even -// if save game isn't supported yet. +//These two classes give the save-transfer box a smidgin of useful info even +//if save game isn't supported yet. namespace { -class DummySave : public ISaveGame { +class DummySave : public ISaveGame +{ public: - DummySave(QString const& filename) : m_File(filename) {} + DummySave(QString const &filename) : + m_File(filename) + {} - ~DummySave() {} + ~DummySave() {} - virtual QString getFilename() const override { return m_File; } + virtual QString getFilename() const override + { + return m_File; + } - virtual QDateTime getCreationTime() const override { return QFileInfo(m_File).created(); } + virtual QDateTime getCreationTime() const override + { + return QFileInfo(m_File).created(); + } - virtual QString getSaveGroupIdentifier() const override { return m_File; } + virtual QString getSaveGroupIdentifier() const override + { + return m_File; + } - virtual QStringList allFiles() const override { return {m_File}; } + virtual QStringList allFiles() const override + { + return { m_File }; + } private: - QString m_File; + QString m_File; }; -class DummyInfo : public SaveGameInfo { +class DummyInfo : public SaveGameInfo +{ public: - virtual MOBase::ISaveGame const* getSaveGameInfo(QString const& file) const override { return new DummySave(file); } - - virtual MissingAssets getMissingAssets(QString const&) const override { return {}; } - - MOBase::ISaveGameInfoWidget* getSaveGameWidget(QWidget*) const override { return nullptr; } + virtual MOBase::ISaveGame const *getSaveGameInfo(QString const &file) const override + { + return new DummySave(file); + } + + virtual MissingAssets getMissingAssets(QString const &) const override + { + return {}; + } + + MOBase::ISaveGameInfoWidget *getSaveGameWidget(QWidget *) const override + { + return nullptr; + } }; -} // end anonymous namespace +} //end anonymous namespace + +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(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; } -TransferSavesDialog::~TransferSavesDialog() { delete ui; } +void TransferSavesDialog::refreshGlobalSaves() +{ + refreshSaves(m_GlobalSaves, m_GamePlugin->savesDirectory().absolutePath()); +} -void TransferSavesDialog::refreshGlobalSaves() { - refreshSaves(m_GlobalSaves, m_GamePlugin->savesDirectory().absolutePath()); + +void TransferSavesDialog::refreshLocalSaves() +{ + refreshSaves(m_LocalSaves, m_Profile.savePath()); } -void TransferSavesDialog::refreshLocalSaves() { refreshSaves(m_LocalSaves, m_Profile.savePath()); } -void TransferSavesDialog::refreshGlobalCharacters() { - refreshCharacters(m_GlobalSaves, ui->globalCharacterList, ui->copyToLocalBtn, ui->moveToLocalBtn); +void TransferSavesDialog::refreshGlobalCharacters() +{ + refreshCharacters(m_GlobalSaves, ui->globalCharacterList, ui->copyToLocalBtn, ui->moveToLocalBtn); } -void TransferSavesDialog::refreshLocalCharacters() { - refreshCharacters(m_LocalSaves, ui->localCharacterList, ui->copyToGlobalBtn, ui->moveToGlobalBtn); + +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; - } + +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; + } + 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 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_GlobalSaves[character], m_Profile.savePath(), - [this](const QString& source, const QString& destination) -> bool { - return shellMove(source, destination, this); - }, - "Failed to move %s to %s")) { - refreshGlobalSaves(); - refreshGlobalCharacters(); - refreshLocalSaves(); - refreshLocalCharacters(); - } +#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_GlobalSaves[character], + m_Profile.savePath(), + [this](const QString &source, const QString &destination) -> bool { + return shellMove(source, destination, this); + }, + "Failed to move %s to %s")) { + refreshGlobalSaves(); + refreshGlobalCharacters(); + refreshLocalSaves(); + refreshLocalCharacters(); + } } -void TransferSavesDialog::on_copyToLocalBtn_clicked() { - QString character = ui->globalCharacterList->currentItem()->text(); - if (transferCharacters(character, COPY_SAVES TO_PROFILE, m_GlobalSaves[character], m_Profile.savePath(), - [this](const QString& source, const QString& destination) -> bool { - return shellCopy(source, destination, this); - }, - "Failed to copy %s to %s")) { - refreshLocalSaves(); - refreshLocalCharacters(); - } +void TransferSavesDialog::on_copyToLocalBtn_clicked() +{ + QString character = ui->globalCharacterList->currentItem()->text(); + if (transferCharacters( + character, COPY_SAVES TO_PROFILE, m_GlobalSaves[character], + m_Profile.savePath(), + [this](const QString &source, const QString &destination) -> bool { + return shellCopy(source, destination, this); + }, + "Failed to copy %s to %s")) { + refreshLocalSaves(); + refreshLocalCharacters(); + } } -void TransferSavesDialog::on_moveToGlobalBtn_clicked() { - QString character = ui->localCharacterList->currentItem()->text(); - if (transferCharacters(character, MOVE_SAVES TO_GLOBAL, m_LocalSaves[character], - m_GamePlugin->savesDirectory().absolutePath(), - [this](const QString& source, const QString& destination) -> bool { - return shellMove(source, destination, this); - }, - "Failed to move %s to %s")) { - refreshGlobalSaves(); - refreshGlobalCharacters(); - refreshLocalSaves(); - refreshLocalCharacters(); - } +void TransferSavesDialog::on_moveToGlobalBtn_clicked() +{ + QString character = ui->localCharacterList->currentItem()->text(); + if (transferCharacters( + character, MOVE_SAVES TO_GLOBAL, m_LocalSaves[character], + m_GamePlugin->savesDirectory().absolutePath(), + [this](const QString &source, const QString &destination) -> bool { + return shellMove(source, destination, this); + }, + "Failed to move %s to %s")) { + refreshGlobalSaves(); + refreshGlobalCharacters(); + refreshLocalSaves(); + refreshLocalCharacters(); + } } -void TransferSavesDialog::on_copyToGlobalBtn_clicked() { - QString character = ui->localCharacterList->currentItem()->text(); - if (transferCharacters(character, COPY_SAVES TO_GLOBAL, m_LocalSaves[character], - m_GamePlugin->savesDirectory().absolutePath(), - [this](const QString& source, const QString& destination) -> bool { - return shellCopy(source, destination, this); - }, - "Failed to copy %s to %s")) { - refreshGlobalSaves(); - refreshGlobalCharacters(); - } +void TransferSavesDialog::on_copyToGlobalBtn_clicked() +{ + QString character = ui->localCharacterList->currentItem()->text(); + if (transferCharacters( + character, COPY_SAVES TO_GLOBAL, m_LocalSaves[character], + m_GamePlugin->savesDirectory().absolutePath(), + [this](const QString &source, const QString &destination) -> bool { + return shellCopy(source, destination, this); + }, + "Failed to copy %s to %s")) { + 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->getFilename()).fileName()); - } - } +void TransferSavesDialog::on_doneButton_clicked() +{ + close(); } -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->getFilename()).fileName()); - } +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->getFilename()).fileName()); } + } } -void TransferSavesDialog::refreshSaves(SaveCollection& saveCollection, QString const& savedir) { - saveCollection.clear(); - QDir savesDir(savedir); - savesDir.setNameFilters(QStringList() << QString("*.%1").arg(m_GamePlugin->savegameExtension())); - - SaveGameInfo const* info = m_GamePlugin->feature(); - if (info == nullptr) { - static DummyInfo dummyInfo; - info = &dummyInfo; +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->getFilename()).fileName()); } + } +} - QStringList files = savesDir.entryList(QDir::Files, QDir::Time); - for (const QString& filename : files) { - QString file = savesDir.absoluteFilePath(filename); - MOBase::ISaveGame const* save = info->getSaveGameInfo(file); - saveCollection[save->getSaveGroupIdentifier()].push_back(std::unique_ptr(save)); - } +void TransferSavesDialog::refreshSaves(SaveCollection &saveCollection, QString const &savedir) +{ + saveCollection.clear(); + QDir savesDir(savedir); + savesDir.setNameFilters(QStringList() << QString("*.%1").arg(m_GamePlugin->savegameExtension())); + + SaveGameInfo const *info = m_GamePlugin->feature(); + if (info == nullptr) { + static DummyInfo dummyInfo; + info = &dummyInfo; + } + + QStringList files = savesDir.entryList(QDir::Files, QDir::Time); + for (const QString &filename : files) { + QString file = savesDir.absoluteFilePath(filename); + MOBase::ISaveGame const *save = info->getSaveGameInfo(file); + saveCollection[save->getSaveGroupIdentifier()].push_back( + std::unique_ptr(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); - } +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, SaveList& saves, - QString const& dest, - 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; - } +bool TransferSavesDialog::transferCharacters( + QString const &character, char const *message, SaveList &saves, + QString const &dest, + 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; + + QDir destination(dest); + for (SaveListItem const &save : saves) { + for (QString source : save->allFiles()) { + QFileInfo sourceFile(source); + QString destinationFile(destination.absoluteFilePath(sourceFile.fileName())); + + //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)) { + qCritical(errmsg, + sourceFile.absoluteFilePath().toUtf8().constData(), + destinationFile.toUtf8().constData()); + } + + QFileInfo sourceFileSE(sourceFile.absolutePath() + "/" + sourceFile.completeBaseName() + "." + m_GamePlugin->savegameSEExtension()); + if (sourceFileSE.exists()) { + QString destinationFileSE(destination.absoluteFilePath(sourceFileSE.fileName())); + + //If the file is already there, let them skip (or not). + if (QFile::exists(destinationFileSE)) { + if (!testOverwrite(overwriteMode, destinationFileSE)) { + continue; + } + //OK, they want to remove it. + QFile::remove(destinationFileSE); + } - OverwriteMode overwriteMode = OVERWRITE_ASK; - - QDir destination(dest); - for (SaveListItem const& save : saves) { - for (QString source : save->allFiles()) { - QFileInfo sourceFile(source); - QString destinationFile(destination.absoluteFilePath(sourceFile.fileName())); - - // 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)) { - qCritical(errmsg, sourceFile.absoluteFilePath().toUtf8().constData(), - destinationFile.toUtf8().constData()); - } - - QFileInfo sourceFileSE(sourceFile.absolutePath() + "/" + sourceFile.completeBaseName() + "." + - m_GamePlugin->savegameSEExtension()); - if (sourceFileSE.exists()) { - QString destinationFileSE(destination.absoluteFilePath(sourceFileSE.fileName())); - - // If the file is already there, let them skip (or not). - if (QFile::exists(destinationFileSE)) { - if (!testOverwrite(overwriteMode, destinationFileSE)) { - continue; - } - // OK, they want to remove it. - QFile::remove(destinationFileSE); - } - - if (!method(sourceFileSE.absoluteFilePath(), destinationFileSE)) { - qCritical(errmsg, sourceFileSE.absoluteFilePath().toUtf8().constData(), - destinationFileSE.toUtf8().constData()); - } - } + if (!method(sourceFileSE.absoluteFilePath(), destinationFileSE)) { + qCritical(errmsg, + sourceFileSE.absoluteFilePath().toUtf8().constData(), + destinationFileSE.toUtf8().constData()); } + } } - return true; + } + return true; } diff --git a/src/transfersavesdialog.h b/src/transfersavesdialog.h index 81c44f57..dee41c72 100644 --- a/src/transfersavesdialog.h +++ b/src/transfersavesdialog.h @@ -20,8 +20,8 @@ along with Mod Organizer. If not, see . #ifndef TRANSFERSAVESDIALOG_H #define TRANSFERSAVESDIALOG_H -#include "profile.h" #include "tutorabledialog.h" +#include "profile.h" class QListWidget; #include @@ -29,72 +29,73 @@ class QPushButton; #include class QWidget; -#include #include +#include #include -namespace Ui { -class TransferSavesDialog; -} -namespace MOBase { -class IPluginGame; -} -namespace MOBase { -class ISaveGame; -} +namespace Ui { class TransferSavesDialog; } +namespace MOBase { class IPluginGame; } +namespace MOBase { class ISaveGame; } class TransferSavesDialog : public MOBase::TutorableDialog { - Q_OBJECT + Q_OBJECT public: - explicit TransferSavesDialog(const Profile& profile, MOBase::IPluginGame const* gamePlugin, QWidget* parent = 0); - ~TransferSavesDialog(); + explicit TransferSavesDialog(const Profile &profile, + MOBase::IPluginGame const *gamePlugin, + QWidget *parent = 0); + ~TransferSavesDialog(); private slots: - void on_moveToLocalBtn_clicked(); + void on_moveToLocalBtn_clicked(); - void on_doneButton_clicked(); + void on_doneButton_clicked(); - void on_globalCharacterList_currentTextChanged(const QString& currentText); + void on_globalCharacterList_currentTextChanged(const QString ¤tText); - void on_localCharacterList_currentTextChanged(const QString& currentText); + void on_localCharacterList_currentTextChanged(const QString ¤tText); - void on_copyToLocalBtn_clicked(); + void on_copyToLocalBtn_clicked(); - void on_moveToGlobalBtn_clicked(); + void on_moveToGlobalBtn_clicked(); - void on_copyToGlobalBtn_clicked(); + void on_copyToGlobalBtn_clicked(); private: - enum OverwriteMode { OVERWRITE_ASK, OVERWRITE_YES, OVERWRITE_NO }; + enum OverwriteMode { OVERWRITE_ASK, OVERWRITE_YES, OVERWRITE_NO }; private: - void refreshGlobalCharacters(); - void refreshLocalCharacters(); - void refreshGlobalSaves(); - void refreshLocalSaves(); - bool testOverwrite(OverwriteMode& overwriteMode, const QString& destinationFile); + void refreshGlobalCharacters(); + void refreshLocalCharacters(); + void refreshGlobalSaves(); + void refreshLocalSaves(); + bool testOverwrite(OverwriteMode &overwriteMode, + const QString &destinationFile); private: - Ui::TransferSavesDialog* ui; + Ui::TransferSavesDialog *ui; - Profile m_Profile; + Profile m_Profile; - MOBase::IPluginGame const* m_GamePlugin; + MOBase::IPluginGame const *m_GamePlugin; - typedef std::unique_ptr SaveListItem; - typedef std::vector SaveList; - typedef std::map SaveCollection; - SaveCollection m_GlobalSaves; - SaveCollection m_LocalSaves; + typedef std::unique_ptr SaveListItem; + typedef std::vector SaveList; + typedef std::map SaveCollection; + SaveCollection m_GlobalSaves; + SaveCollection m_LocalSaves; - void refreshSaves(SaveCollection& saveCollection, const QString& savedir); - void refreshCharacters(SaveCollection const& saveCollection, QListWidget* charList, QPushButton* copy, - QPushButton* move); + 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, SaveList& saves, QString const& dest, - const std::function& method, char const* errmsg); + bool transferCharacters( + QString const &character, char const *message, SaveList &saves, + QString const &dest, + const std::function &method, + char const *errmsg); }; #endif // TRANSFERSAVESDIALOG_H diff --git a/src/usvfsconnector.cpp b/src/usvfsconnector.cpp index 5504684e..ffbdf3aa 100644 --- a/src/usvfsconnector.cpp +++ b/src/usvfsconnector.cpp @@ -18,160 +18,183 @@ along with Mod Organizer. If not, see . */ #include "usvfsconnector.h" -#include "organizercore.h" #include "settings.h" +#include "organizercore.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"; -std::string to_hex(void* bufferIn, size_t bufferSize) { - unsigned char* buffer = static_cast(bufferIn); - std::ostringstream temp; - temp << std::hex; - for (size_t i = 0; i < bufferSize; ++i) { - temp << std::setfill('0') << std::setw(2) << (unsigned int)buffer[i]; - if ((i % 16) == 15) { - temp << "\n"; - } else { - temp << " "; - } + +std::string to_hex(void *bufferIn, size_t bufferSize) +{ + unsigned char *buffer = static_cast(bufferIn); + std::ostringstream temp; + temp << std::hex; + for (size_t i = 0; i < bufferSize; ++i) { + temp << std::setfill('0') << std::setw(2) << (unsigned int)buffer[i]; + if ((i % 16) == 15) { + temp << "\n"; + } else { + temp << " "; } - return temp.str(); + } + 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_LogFile.open(QIODevice::WriteOnly); - qDebug("usvfs log messages are written to %s", qPrintable(m_LogFile.fileName())); + : 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); + qDebug("usvfs log messages are written to %s", + qPrintable(m_LogFile.fileName())); +} + +LogWorker::~LogWorker() +{ } -LogWorker::~LogWorker() {} - -void LogWorker::process() { - int noLogCycles = 0; - while (!m_QuitRequested) { - if (GetLogMessages(&m_Buffer[0], m_Buffer.size(), false)) { - m_LogFile.write(m_Buffer.c_str()); - m_LogFile.write("\n"); - m_LogFile.flush(); - noLogCycles = 0; - } else { - QThread::msleep(std::min(40, noLogCycles) * 5); - ++noLogCycles; - } +void LogWorker::process() +{ + int noLogCycles = 0; + while (!m_QuitRequested) { + if (GetLogMessages(&m_Buffer[0], m_Buffer.size(), false)) { + m_LogFile.write(m_Buffer.c_str()); + m_LogFile.write("\n"); + m_LogFile.flush(); + noLogCycles = 0; + } else { + QThread::msleep(std::min(40, noLogCycles) * 5); + ++noLogCycles; } - emit finished(); + } + emit finished(); } -void LogWorker::exit() { m_QuitRequested = true; } +void LogWorker::exit() +{ + m_QuitRequested = true; +} -LogLevel logLevel(int level) { - switch (level) { +LogLevel logLevel(int level) +{ + switch (level) { case LogLevel::Info: - return LogLevel::Info; + return LogLevel::Info; case LogLevel::Warning: - return LogLevel::Warning; + return LogLevel::Warning; case LogLevel::Error: - return LogLevel::Error; + return LogLevel::Error; default: - return LogLevel::Debug; - } + return LogLevel::Debug; + } } -CrashDumpsType crashDumpsType(int type) { - switch (type) { - case CrashDumpsType::Mini: - return CrashDumpsType::Mini; - case CrashDumpsType::Data: - return CrashDumpsType::Data; - case CrashDumpsType::Full: - return CrashDumpsType::Full; - default: - return CrashDumpsType::None; - } +CrashDumpsType crashDumpsType(int type) +{ + switch (type) { + case CrashDumpsType::Mini: + return CrashDumpsType::Mini; + case CrashDumpsType::Data: + return CrashDumpsType::Data; + case CrashDumpsType::Full: + return CrashDumpsType::Full; + default: + return CrashDumpsType::None; + } } -UsvfsConnector::UsvfsConnector() { - USVFSParameters params; - LogLevel level = logLevel(Settings::instance().logLevel()); - CrashDumpsType dumpType = crashDumpsType(Settings::instance().crashDumpsType()); +UsvfsConnector::UsvfsConnector() +{ + USVFSParameters params; + LogLevel level = logLevel(Settings::instance().logLevel()); + CrashDumpsType dumpType = crashDumpsType(Settings::instance().crashDumpsType()); - std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true); - USVFSInitParameters(¶ms, SHMID, false, level, dumpType, dumpPath.c_str()); - InitLogging(false); + std::string dumpPath = MOShared::ToString(OrganizerCore::crashDumpsPath(), true); + USVFSInitParameters(¶ms, SHMID, false, level, dumpType, dumpPath.c_str()); + InitLogging(false); - qDebug("Initializing VFS <%s, %d, %d, %s>", params.instanceName, params.logLevel, params.crashDumpsType, - params.crashDumpsPath); + qDebug("Initializing VFS <%s, %d, %d, %s>", params.instanceName, params.logLevel, params.crashDumpsType, params.crashDumpsPath); - CreateVFS(¶ms); + CreateVFS(¶ms); - BlacklistExecutable(L"TSVNCache.exe"); + BlacklistExecutable(L"TSVNCache.exe"); - m_LogWorker.moveToThread(&m_WorkerThread); + m_LogWorker.moveToThread(&m_WorkerThread); - connect(&m_WorkerThread, SIGNAL(started()), &m_LogWorker, SLOT(process())); - connect(&m_LogWorker, SIGNAL(finished()), &m_WorkerThread, SLOT(quit())); + connect(&m_WorkerThread, SIGNAL(started()), &m_LogWorker, SLOT(process())); + connect(&m_LogWorker, SIGNAL(finished()), &m_WorkerThread, SLOT(quit())); - m_WorkerThread.start(QThread::LowestPriority); + m_WorkerThread.start(QThread::LowestPriority); } -UsvfsConnector::~UsvfsConnector() { - DisconnectVFS(); - m_LogWorker.exit(); - m_WorkerThread.quit(); - m_WorkerThread.wait(); +UsvfsConnector::~UsvfsConnector() +{ + DisconnectVFS(); + m_LogWorker.exit(); + m_WorkerThread.quit(); + m_WorkerThread.wait(); } -void UsvfsConnector::updateMapping(const MappingType& mapping) { - QProgressDialog progress; - progress.setLabelText(tr("Preparing vfs")); - progress.setMaximum(static_cast(mapping.size())); - progress.show(); - int value = 0; - int files = 0; - int dirs = 0; - - qDebug("Updating VFS mappings..."); - - ClearVirtualMappings(); - - for (auto map : mapping) { - progress.setValue(value++); - if (value % 10 == 0) { - QCoreApplication::processEvents(); - } - - if (map.isDirectory) { - 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(), map.destination.toStdWString().c_str(), 0); - ++files; - } + +void UsvfsConnector::updateMapping(const MappingType &mapping) +{ + QProgressDialog progress; + progress.setLabelText(tr("Preparing vfs")); + progress.setMaximum(static_cast(mapping.size())); + progress.show(); + int value = 0; + int files = 0; + int dirs = 0; + + qDebug("Updating VFS mappings..."); + + ClearVirtualMappings(); + + for (auto map : mapping) { + progress.setValue(value++); + if (value % 10 == 0) { + QCoreApplication::processEvents(); } - qDebug("VFS mappings updated ", dirs, files); - /* - size_t dumpSize = 0; - CreateVFSDump(nullptr, &dumpSize); - std::unique_ptr buffer(new char[dumpSize]); - CreateVFSDump(buffer.get(), &dumpSize); - qDebug(buffer.get()); - */ + if (map.isDirectory) { + 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(), + map.destination.toStdWString().c_str(), 0); + ++files; + } + } + + qDebug("VFS mappings updated ", dirs, files); + /* + size_t dumpSize = 0; + CreateVFSDump(nullptr, &dumpSize); + std::unique_ptr buffer(new char[dumpSize]); + CreateVFSDump(buffer.get(), &dumpSize); + qDebug(buffer.get()); + */ } void UsvfsConnector::updateParams(int logLevel, int crashDumpsType) { - USVFSUpdateParams(::logLevel(logLevel), ::crashDumpsType(crashDumpsType)); + USVFSUpdateParams(::logLevel(logLevel), ::crashDumpsType(crashDumpsType)); } diff --git a/src/usvfsconnector.h b/src/usvfsconnector.h index 937912bf..0935bac1 100644 --- a/src/usvfsconnector.h +++ b/src/usvfsconnector.h @@ -17,54 +17,64 @@ 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 +#include +#include #include + class LogWorker : public QThread { - Q_OBJECT + Q_OBJECT public: - LogWorker(); - ~LogWorker(); + + LogWorker(); + ~LogWorker(); public slots: - void process(); - void exit(); + void process(); + void exit(); signals: - void outputLog(const QString& message); - void finished(); + void outputLog(const QString &message); + void finished(); private: - std::string m_Buffer; - bool m_QuitRequested; - QFile m_LogFile; + + std::string m_Buffer; + bool m_QuitRequested; + QFile m_LogFile; + }; + class UsvfsConnector : public QObject { - Q_OBJECT + Q_OBJECT public: - UsvfsConnector(); - ~UsvfsConnector(); - void updateMapping(const MappingType& mapping); - void updateParams(int logLevel, int crashDumpsType); + UsvfsConnector(); + ~UsvfsConnector(); + + void updateMapping(const MappingType &mapping); + void updateParams(int logLevel, int crashDumpsType); private: - LogWorker m_LogWorker; - QThread m_WorkerThread; + + LogWorker m_LogWorker; + QThread m_WorkerThread; + }; CrashDumpsType crashDumpsType(int type); diff --git a/src/viewmarkingscrollbar.cpp b/src/viewmarkingscrollbar.cpp index 596af533..2452d0e3 100644 --- a/src/viewmarkingscrollbar.cpp +++ b/src/viewmarkingscrollbar.cpp @@ -1,38 +1,42 @@ #include "viewmarkingscrollbar.h" -#include #include #include +#include -ViewMarkingScrollBar::ViewMarkingScrollBar(QAbstractItemModel* model, QWidget* parent, int role) - : QScrollBar(parent), m_Model(model), m_Role(role) { - // not implemented for horizontal sliders - Q_ASSERT(this->orientation() == Qt::Vertical); +ViewMarkingScrollBar::ViewMarkingScrollBar(QAbstractItemModel *model, QWidget *parent, int role) + : QScrollBar(parent) + , m_Model(model) + , m_Role(role) +{ + // not implemented for horizontal sliders + Q_ASSERT(this->orientation() == Qt::Vertical); } -void ViewMarkingScrollBar::paintEvent(QPaintEvent* event) { - if (m_Model == nullptr) { - return; - } - QScrollBar::paintEvent(event); +void ViewMarkingScrollBar::paintEvent(QPaintEvent *event) +{ + if (m_Model == nullptr) { + return; + } + QScrollBar::paintEvent(event); - QStyleOptionSlider styleOption; - initStyleOption(&styleOption); + QStyleOptionSlider styleOption; + initStyleOption(&styleOption); - QPainter painter(this); + 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); + QRect handleRect = style()->subControlRect(QStyle::CC_ScrollBar, &styleOption, QStyle::SC_ScrollBarSlider, this); + QRect innerRect = style()->subControlRect(QStyle::CC_ScrollBar, &styleOption, QStyle::SC_ScrollBarGroove, this); - painter.translate(innerRect.topLeft() + QPoint(0, 3)); - qreal scale = static_cast(innerRect.height() - 3) / static_cast(m_Model->rowCount()); + painter.translate(innerRect.topLeft() + QPoint(0, 3)); + qreal scale = static_cast(innerRect.height() - 3) / static_cast(m_Model->rowCount()); - for (int i = 0; i < m_Model->rowCount(); ++i) { - QVariant data = m_Model->data(m_Model->index(i, 0), m_Role); - if (data.isValid()) { - QColor col = data.value(); - painter.setPen(col); - painter.setBrush(col); - painter.drawRect(QRect(2, i * scale - 2, handleRect.width() - 5, 3)); - } + for (int i = 0; i < m_Model->rowCount(); ++i) { + QVariant data = m_Model->data(m_Model->index(i, 0), m_Role); + if (data.isValid()) { + QColor col = data.value(); + painter.setPen(col); + painter.setBrush(col); + painter.drawRect(QRect(2, i * scale - 2, handleRect.width() - 5, 3)); } + } } diff --git a/src/viewmarkingscrollbar.h b/src/viewmarkingscrollbar.h index bcf8f21e..12a297d1 100644 --- a/src/viewmarkingscrollbar.h +++ b/src/viewmarkingscrollbar.h @@ -1,22 +1,22 @@ #ifndef VIEWMARKINGSCROLLBAR_H #define VIEWMARKINGSCROLLBAR_H -#include #include +#include -class ViewMarkingScrollBar : public QScrollBar { -public: - static const int DEFAULT_ROLE = Qt::UserRole + 42; +class ViewMarkingScrollBar : public QScrollBar +{ public: - ViewMarkingScrollBar(QAbstractItemModel* model, QWidget* parent = 0, int role = DEFAULT_ROLE); - + static const int DEFAULT_ROLE = Qt::UserRole + 42; +public: + ViewMarkingScrollBar(QAbstractItemModel *model, QWidget *parent = 0, int role = DEFAULT_ROLE); protected: - virtual void paintEvent(QPaintEvent* event); - + virtual void paintEvent(QPaintEvent *event); private: - QAbstractItemModel* m_Model; - int m_Role; + QAbstractItemModel *m_Model; + int m_Role; }; + #endif // VIEWMARKINGSCROLLBAR_H diff --git a/src/waitingonclosedialog.cpp b/src/waitingonclosedialog.cpp index 6ab328b8..565d0a36 100644 --- a/src/waitingonclosedialog.cpp +++ b/src/waitingonclosedialog.cpp @@ -23,36 +23,49 @@ along with Mod Organizer. If not, see . #include #include #include -#include // for Qt::FramelessWindowHint, etc - -WaitingOnCloseDialog::WaitingOnCloseDialog(QWidget* parent) - : LockedDialogBase(parent, true), ui(new Ui::WaitingOnCloseDialog) { - ui->setupUi(this); - - // Supposedly the Qt::CustomizeWindowHint should use a customized window - // allowing us to select if there is a close button. In practice this doesn't - // seem to work. We will ignore pressing the close button if unlockByButton == true - Qt::WindowFlags flags = - this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint; - if (m_allowClose) - flags |= Qt::WindowCloseButtonHint; - this->setWindowFlags(flags); +#include // for Qt::FramelessWindowHint, etc + +WaitingOnCloseDialog::WaitingOnCloseDialog(QWidget *parent) + : LockedDialogBase(parent,true) + , ui(new Ui::WaitingOnCloseDialog) +{ + ui->setupUi(this); + + // Supposedly the Qt::CustomizeWindowHint should use a customized window + // allowing us to select if there is a close button. In practice this doesn't + // seem to work. We will ignore pressing the close button if unlockByButton == true + Qt::WindowFlags flags = + this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint; + if (m_allowClose) + flags |= Qt::WindowCloseButtonHint; + this->setWindowFlags(flags); +} + +WaitingOnCloseDialog::~WaitingOnCloseDialog() +{ + delete ui; } -WaitingOnCloseDialog::~WaitingOnCloseDialog() { delete ui; } -void WaitingOnCloseDialog::setProcessName(const QString& name) { ui->processLabel->setText(name); } +void WaitingOnCloseDialog::setProcessName(const QString &name) +{ + ui->processLabel->setText(name); +} -void WaitingOnCloseDialog::on_closeButton_clicked() { unlock(); } +void WaitingOnCloseDialog::on_closeButton_clicked() +{ + unlock(); +} -void WaitingOnCloseDialog::on_cancelButton_clicked() { - cancel(); - unlock(); +void WaitingOnCloseDialog::on_cancelButton_clicked() +{ + cancel(); + unlock(); } void WaitingOnCloseDialog::unlock() { - LockedDialogBase::unlock(); - ui->label->setText("unlocking may take a few seconds"); - ui->closeButton->setEnabled(false); - ui->cancelButton->setEnabled(false); + LockedDialogBase::unlock(); + ui->label->setText("unlocking may take a few seconds"); + ui->closeButton->setEnabled(false); + ui->cancelButton->setEnabled(false); } diff --git a/src/waitingonclosedialog.h b/src/waitingonclosedialog.h index ca0f4d2b..6650c390 100644 --- a/src/waitingonclosedialog.h +++ b/src/waitingonclosedialog.h @@ -22,32 +22,35 @@ along with Mod Organizer. If not, see . #include "lockeddialogbase.h" namespace Ui { -class WaitingOnCloseDialog; + class WaitingOnCloseDialog; } /** * Similar to the LockedDialog but used for waiting on running process during * a process close request which requries a slightly different dialog. **/ -class WaitingOnCloseDialog : public LockedDialogBase { +class WaitingOnCloseDialog : public LockedDialogBase +{ Q_OBJECT public: - explicit WaitingOnCloseDialog(QWidget* parent = 0); - ~WaitingOnCloseDialog(); + explicit WaitingOnCloseDialog(QWidget *parent = 0); + ~WaitingOnCloseDialog(); - bool canceled() const { return m_Canceled; } + bool canceled() const { return m_Canceled; } - void setProcessName(const QString& name) override; + void setProcessName(const QString &name) override; protected: - void unlock() override; + + void unlock() override; private slots: - void on_closeButton_clicked(); - void on_cancelButton_clicked(); + void on_closeButton_clicked(); + void on_cancelButton_clicked(); private: - Ui::WaitingOnCloseDialog* ui; + + Ui::WaitingOnCloseDialog *ui; }; -- cgit v1.3.1