diff options
117 files changed, 31484 insertions, 31484 deletions
diff --git a/src/activatemodsdialog.cpp b/src/activatemodsdialog.cpp index 8d463fba..be6eef54 100644 --- a/src/activatemodsdialog.cpp +++ b/src/activatemodsdialog.cpp @@ -1,97 +1,97 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "activatemodsdialog.h" -#include "ui_activatemodsdialog.h" - -#include <QComboBox> -#include <QLabel> - -ActivateModsDialog::ActivateModsDialog(const std::map<QString, std::vector<QString> > &missingPlugins, QWidget *parent) - : TutorableDialog("ActivateMods", parent), ui(new Ui::ActivateModsDialog) -{ - ui->setupUi(this); - - QTableWidget *modsTable = findChild<QTableWidget*>("modsTable"); - QHeaderView *headerView = modsTable->horizontalHeader(); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - headerView->setSectionResizeMode(0, QHeaderView::Stretch); - headerView->setSectionResizeMode(1, QHeaderView::Interactive); -#else - headerView->setResizeMode(0, QHeaderView::Stretch); - headerView->setResizeMode(1, QHeaderView::Interactive); -#endif - - int row = 0; - - modsTable->setRowCount(missingPlugins.size()); - - for (std::map<QString, std::vector<QString> >::const_iterator espIter = missingPlugins.begin(); - espIter != missingPlugins.end(); ++espIter, ++row) { - modsTable->setCellWidget(row, 0, new QLabel(espIter->first)); - if (espIter->second.size() == 0) { - modsTable->setCellWidget(row, 1, new QLabel(tr("not found"))); - } else { - QComboBox* combo = new QComboBox(); - for (std::vector<QString>::const_iterator modIter = espIter->second.begin(); - modIter != espIter->second.end(); ++modIter) { - combo->addItem(*modIter); - } - modsTable->setCellWidget(row, 1, combo); - } - } -} - - -ActivateModsDialog::~ActivateModsDialog() -{ - delete ui; -} - - -std::set<QString> ActivateModsDialog::getModsToActivate() -{ - std::set<QString> result; - QTableWidget *modsTable = findChild<QTableWidget*>("modsTable"); - - for (int row = 0; row < modsTable->rowCount(); ++row) { - QComboBox *comboBox = dynamic_cast<QComboBox*>(modsTable->cellWidget(row, 1)); - if (comboBox != NULL) { - result.insert(comboBox->currentText()); - } - } - return result; -} - - -std::set<QString> ActivateModsDialog::getESPsToActivate() -{ - std::set<QString> result; - QTableWidget *modsTable = findChild<QTableWidget*>("modsTable"); - - for (int row = 0; row < modsTable->rowCount(); ++row) { - QComboBox *comboBox = dynamic_cast<QComboBox*>(modsTable->cellWidget(row, 1)); - if (comboBox != NULL) { - QLabel *espName = dynamic_cast<QLabel*>(modsTable->cellWidget(row, 0)); - - result.insert(espName->text()); - } - } - return result; -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "activatemodsdialog.h"
+#include "ui_activatemodsdialog.h"
+
+#include <QComboBox>
+#include <QLabel>
+
+ActivateModsDialog::ActivateModsDialog(const std::map<QString, std::vector<QString> > &missingPlugins, QWidget *parent)
+ : TutorableDialog("ActivateMods", parent), ui(new Ui::ActivateModsDialog)
+{
+ ui->setupUi(this);
+
+ QTableWidget *modsTable = findChild<QTableWidget*>("modsTable");
+ QHeaderView *headerView = modsTable->horizontalHeader();
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+ headerView->setSectionResizeMode(0, QHeaderView::Stretch);
+ headerView->setSectionResizeMode(1, QHeaderView::Interactive);
+#else
+ headerView->setResizeMode(0, QHeaderView::Stretch);
+ headerView->setResizeMode(1, QHeaderView::Interactive);
+#endif
+
+ int row = 0;
+
+ modsTable->setRowCount(missingPlugins.size());
+
+ for (std::map<QString, std::vector<QString> >::const_iterator espIter = missingPlugins.begin();
+ espIter != missingPlugins.end(); ++espIter, ++row) {
+ modsTable->setCellWidget(row, 0, new QLabel(espIter->first));
+ if (espIter->second.size() == 0) {
+ modsTable->setCellWidget(row, 1, new QLabel(tr("not found")));
+ } else {
+ QComboBox* combo = new QComboBox();
+ for (std::vector<QString>::const_iterator modIter = espIter->second.begin();
+ modIter != espIter->second.end(); ++modIter) {
+ combo->addItem(*modIter);
+ }
+ modsTable->setCellWidget(row, 1, combo);
+ }
+ }
+}
+
+
+ActivateModsDialog::~ActivateModsDialog()
+{
+ delete ui;
+}
+
+
+std::set<QString> ActivateModsDialog::getModsToActivate()
+{
+ std::set<QString> result;
+ QTableWidget *modsTable = findChild<QTableWidget*>("modsTable");
+
+ for (int row = 0; row < modsTable->rowCount(); ++row) {
+ QComboBox *comboBox = dynamic_cast<QComboBox*>(modsTable->cellWidget(row, 1));
+ if (comboBox != NULL) {
+ result.insert(comboBox->currentText());
+ }
+ }
+ return result;
+}
+
+
+std::set<QString> ActivateModsDialog::getESPsToActivate()
+{
+ std::set<QString> result;
+ QTableWidget *modsTable = findChild<QTableWidget*>("modsTable");
+
+ for (int row = 0; row < modsTable->rowCount(); ++row) {
+ QComboBox *comboBox = dynamic_cast<QComboBox*>(modsTable->cellWidget(row, 1));
+ if (comboBox != NULL) {
+ QLabel *espName = dynamic_cast<QLabel*>(modsTable->cellWidget(row, 0));
+
+ result.insert(espName->text());
+ }
+ }
+ return result;
+}
diff --git a/src/activatemodsdialog.h b/src/activatemodsdialog.h index 845167aa..08dbad8d 100644 --- a/src/activatemodsdialog.h +++ b/src/activatemodsdialog.h @@ -1,70 +1,70 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef ACTIVATEMODSDIALOG_H -#define ACTIVATEMODSDIALOG_H - -#include "tutorabledialog.h" -#include <map> -#include <set> - -namespace Ui { - class ActivateModsDialog; -} - -/** - * @brief Dialog that is used to batch activate/deactivate mods and plugins - **/ -class ActivateModsDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - /** - * @brief constructor - * - * @param missingPlugins a map containing missing plugins that need to be activated - * @param parent ... Defaults to 0. - **/ - explicit ActivateModsDialog(const std::map<QString, std::vector<QString> > &missingPlugins, 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<QString> 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<QString> getESPsToActivate(); - -private slots: - -private: - Ui::ActivateModsDialog *ui; -}; - -#endif // ACTIVATEMODSDIALOG_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef ACTIVATEMODSDIALOG_H
+#define ACTIVATEMODSDIALOG_H
+
+#include "tutorabledialog.h"
+#include <map>
+#include <set>
+
+namespace Ui {
+ class ActivateModsDialog;
+}
+
+/**
+ * @brief Dialog that is used to batch activate/deactivate mods and plugins
+ **/
+class ActivateModsDialog : public MOBase::TutorableDialog
+{
+ Q_OBJECT
+
+public:
+ /**
+ * @brief constructor
+ *
+ * @param missingPlugins a map containing missing plugins that need to be activated
+ * @param parent ... Defaults to 0.
+ **/
+ explicit ActivateModsDialog(const std::map<QString, std::vector<QString> > &missingPlugins, 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<QString> 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<QString> getESPsToActivate();
+
+private slots:
+
+private:
+ Ui::ActivateModsDialog *ui;
+};
+
+#endif // ACTIVATEMODSDIALOG_H
diff --git a/src/bbcode.cpp b/src/bbcode.cpp index f87b5d85..18f2beb1 100644 --- a/src/bbcode.cpp +++ b/src/bbcode.cpp @@ -1,234 +1,234 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "bbcode.h" - -#include <QRegExp> -#include <map> -#include <algorithm> -#include <boost/assign.hpp> - - -namespace BBCode { - - -class BBCodeMap { - - typedef std::map<QString, std::pair<QRegExp, QString> > TagMap; - -public: - - static BBCodeMap &instance() { - static BBCodeMap s_Instance; - return s_Instance; - } - - QString convertTag(const 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); - } - QString closeTag = tagName == "*" ? "<br/>" - : QString("[/%1]").arg(tagName); - - int closeTagPos = input.indexOf(closeTag, 0, Qt::CaseInsensitive); - //qDebug("close tag %s at %d", closeTag.toUtf8().constData(), closeTagPos); - - if (closeTagPos > -1) { - length = closeTagPos + closeTag.length(); - 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); - auto colIter = m_ColorMap.find(color.toLower()); - if (colIter != m_ColorMap.end()) { - color = colIter->second; - } - return temp.replace(tagIter->second.first, QString("<font style=\"color: #%1;\">%2</font>").arg(color, content)); - } else { - qWarning("don't know how to deal with tag %s", qPrintable(tagName)); - } - } else { - 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(); - } - -private: - BBCodeMap() - : m_TagNameExp("^[a-zA-Z*]*=?") - { - m_TagMap["b"] = std::make_pair(QRegExp("\\[b\\](.*)\\[/b\\]"), - "<b>\\1</b>"); - m_TagMap["i"] = std::make_pair(QRegExp("\\[i\\](.*)\\[/i\\]"), - "<i>\\1</i>"); - m_TagMap["u"] = std::make_pair(QRegExp("\\[u\\](.*)\\[/u\\]"), - "<u>\\1</u>"); - m_TagMap["s"] = std::make_pair(QRegExp("\\[s\\](.*)\\[/s\\]"), - "<s>\\1</s>"); - m_TagMap["sub"] = std::make_pair(QRegExp("\\[sub\\](.*)\\[/sub\\]"), - "<sub>\\1</sub>"); - m_TagMap["sup"] = std::make_pair(QRegExp("\\[sup\\](.*)\\[/sup\\]"), - "<sup>\\1</sup>"); - m_TagMap["size="] = std::make_pair(QRegExp("\\[size=([^\\]]*)\\](.*)\\[/size\\]"), - "<font size=\"\\1\">\\2</font>"); - m_TagMap["color="] = std::make_pair(QRegExp("\\[color=([^\\]]*)\\](.*)\\[/color\\]"), - ""); - m_TagMap["font="] = std::make_pair(QRegExp("\\[font=([^\\]]*)\\](.*)\\[/font\\]"), - "<font face=\\1>\\2</font>"); - m_TagMap["center"] = std::make_pair(QRegExp("\\[center\\](.*)\\[/center\\]"), - "<div align=\"center\">\\1</div>"); - m_TagMap["quote"] = std::make_pair(QRegExp("\\[quote\\](.*)\\[/quote\\]"), - "<blockquote>\"\\1\"</blockquote>"); - m_TagMap["quote="] = std::make_pair(QRegExp("\\[quote=([^\\]]*)\\](.*)\\[/quote\\]"), - "<blockquote>\"\\2\"<br/><span>--\\1</span></blockquote></p>"); - m_TagMap["code"] = std::make_pair(QRegExp("\\[code\\](.*)\\[/code\\]"), - "<pre>\\1</pre>"); - m_TagMap["heading"]= std::make_pair(QRegExp("\\[heading\\](.*)\\[/heading\\]"), - "<h2><strong>\\1</strong></h2>"); - - // lists - m_TagMap["list"] = std::make_pair(QRegExp("\\[list\\](.*)\\[/list\\]"), - "<ul>\\1</ul>"); - m_TagMap["list="] = std::make_pair(QRegExp("\\[list.*\\](.*)\\[/list\\]"), - "<ol>\\1</ol>"); - m_TagMap["ul"] = std::make_pair(QRegExp("\\[ul\\](.*)\\[/ul\\]"), - "<ul>\\1</ul>"); - m_TagMap["ol"] = std::make_pair(QRegExp("\\[ol\\](.*)\\[/ol\\]"), - "<ol>\\1</ol>"); - m_TagMap["li"] = std::make_pair(QRegExp("\\[li\\](.*)\\[/li\\]"), - "<li>\\1</li>"); - m_TagMap["*"] = std::make_pair(QRegExp("\\[\\*\\](.*)<br/>"), - "<li>\\1</li>"); - - // tables - m_TagMap["table"] = std::make_pair(QRegExp("\\[table\\](.*)\\[/table\\]"), - "<table>\\1</table>"); - m_TagMap["tr"] = std::make_pair(QRegExp("\\[tr\\](.*)\\[/tr\\]"), - "<tr>\\1</tr>"); - m_TagMap["th"] = std::make_pair(QRegExp("\\[th\\](.*)\\[/th\\]"), - "<th>\\1</th>"); - m_TagMap["td"] = std::make_pair(QRegExp("\\[td\\](.*)\\[/td\\]"), - "<td>\\1</td>"); - - // web content - m_TagMap["url"] = std::make_pair(QRegExp("\\[url\\](.*)\\[/url\\]"), - "<a href=\"\\1\">\\1</a>"); - m_TagMap["url="] = std::make_pair(QRegExp("\\[url=([^\\]]*)\\](.*)\\[/url\\]"), - "<a href=\"\\1\">\\2</a>"); - m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), " "); - m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), " "); - m_TagMap["email="] = std::make_pair(QRegExp("\\[email=\"?([^\\]]*)\"?\\](.*)\\[/email\\]"), - "<a href=\"mailto:\\1\">\\2</a>"); - m_TagMap["youtube"] = std::make_pair(QRegExp("\\[youtube\\](.*)\\[/youtube\\]"), - "<a href=\"http://www.youtube.com/v/\\1\">http://www.youtube.com/v/\\1</a>"); - - m_ColorMap.insert(std::make_pair<QString, QString>("red", "FF0000")); - m_ColorMap.insert(std::make_pair<QString, QString>("green", "00FF00")); - m_ColorMap.insert(std::make_pair<QString, QString>("blue", "0000FF")); - m_ColorMap.insert(std::make_pair<QString, QString>("black", "000000")); - m_ColorMap.insert(std::make_pair<QString, QString>("gray", "7F7F7F")); - m_ColorMap.insert(std::make_pair<QString, QString>("white", "FFFFFF")); - m_ColorMap.insert(std::make_pair<QString, QString>("yellow", "FFFF00")); - m_ColorMap.insert(std::make_pair<QString, QString>("cyan", "00FFFF")); - m_ColorMap.insert(std::make_pair<QString, QString>("magenta", "FF00FF")); - m_ColorMap.insert(std::make_pair<QString, QString>("brown", "A52A2A")); - m_ColorMap.insert(std::make_pair<QString, QString>("orange", "FFCC00")); - - // 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); - } - } - -private: - - QRegExp m_TagNameExp; - TagMap m_TagMap; - std::map<QString, QString> 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", "<br/>"); - 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)); - - // convert the tag and content if necessary - int length = -1; - QString replacement = BBCodeMap::instance().convertTag(input.mid(pos), length); - if (length != 0) { - QString temp = convertToHTML(replacement); - result.append(temp); - // 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; -} - -} // namespace BBCode - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "bbcode.h"
+
+#include <QRegExp>
+#include <map>
+#include <algorithm>
+#include <boost/assign.hpp>
+
+
+namespace BBCode {
+
+
+class BBCodeMap {
+
+ typedef std::map<QString, std::pair<QRegExp, QString> > TagMap;
+
+public:
+
+ static BBCodeMap &instance() {
+ static BBCodeMap s_Instance;
+ return s_Instance;
+ }
+
+ QString convertTag(const 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);
+ }
+ QString closeTag = tagName == "*" ? "<br/>"
+ : QString("[/%1]").arg(tagName);
+
+ int closeTagPos = input.indexOf(closeTag, 0, Qt::CaseInsensitive);
+ //qDebug("close tag %s at %d", closeTag.toUtf8().constData(), closeTagPos);
+
+ if (closeTagPos > -1) {
+ length = closeTagPos + closeTag.length();
+ 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);
+ auto colIter = m_ColorMap.find(color.toLower());
+ if (colIter != m_ColorMap.end()) {
+ color = colIter->second;
+ }
+ return temp.replace(tagIter->second.first, QString("<font style=\"color: #%1;\">%2</font>").arg(color, content));
+ } else {
+ qWarning("don't know how to deal with tag %s", qPrintable(tagName));
+ }
+ } else {
+ 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();
+ }
+
+private:
+ BBCodeMap()
+ : m_TagNameExp("^[a-zA-Z*]*=?")
+ {
+ m_TagMap["b"] = std::make_pair(QRegExp("\\[b\\](.*)\\[/b\\]"),
+ "<b>\\1</b>");
+ m_TagMap["i"] = std::make_pair(QRegExp("\\[i\\](.*)\\[/i\\]"),
+ "<i>\\1</i>");
+ m_TagMap["u"] = std::make_pair(QRegExp("\\[u\\](.*)\\[/u\\]"),
+ "<u>\\1</u>");
+ m_TagMap["s"] = std::make_pair(QRegExp("\\[s\\](.*)\\[/s\\]"),
+ "<s>\\1</s>");
+ m_TagMap["sub"] = std::make_pair(QRegExp("\\[sub\\](.*)\\[/sub\\]"),
+ "<sub>\\1</sub>");
+ m_TagMap["sup"] = std::make_pair(QRegExp("\\[sup\\](.*)\\[/sup\\]"),
+ "<sup>\\1</sup>");
+ m_TagMap["size="] = std::make_pair(QRegExp("\\[size=([^\\]]*)\\](.*)\\[/size\\]"),
+ "<font size=\"\\1\">\\2</font>");
+ m_TagMap["color="] = std::make_pair(QRegExp("\\[color=([^\\]]*)\\](.*)\\[/color\\]"),
+ "");
+ m_TagMap["font="] = std::make_pair(QRegExp("\\[font=([^\\]]*)\\](.*)\\[/font\\]"),
+ "<font face=\\1>\\2</font>");
+ m_TagMap["center"] = std::make_pair(QRegExp("\\[center\\](.*)\\[/center\\]"),
+ "<div align=\"center\">\\1</div>");
+ m_TagMap["quote"] = std::make_pair(QRegExp("\\[quote\\](.*)\\[/quote\\]"),
+ "<blockquote>\"\\1\"</blockquote>");
+ m_TagMap["quote="] = std::make_pair(QRegExp("\\[quote=([^\\]]*)\\](.*)\\[/quote\\]"),
+ "<blockquote>\"\\2\"<br/><span>--\\1</span></blockquote></p>");
+ m_TagMap["code"] = std::make_pair(QRegExp("\\[code\\](.*)\\[/code\\]"),
+ "<pre>\\1</pre>");
+ m_TagMap["heading"]= std::make_pair(QRegExp("\\[heading\\](.*)\\[/heading\\]"),
+ "<h2><strong>\\1</strong></h2>");
+
+ // lists
+ m_TagMap["list"] = std::make_pair(QRegExp("\\[list\\](.*)\\[/list\\]"),
+ "<ul>\\1</ul>");
+ m_TagMap["list="] = std::make_pair(QRegExp("\\[list.*\\](.*)\\[/list\\]"),
+ "<ol>\\1</ol>");
+ m_TagMap["ul"] = std::make_pair(QRegExp("\\[ul\\](.*)\\[/ul\\]"),
+ "<ul>\\1</ul>");
+ m_TagMap["ol"] = std::make_pair(QRegExp("\\[ol\\](.*)\\[/ol\\]"),
+ "<ol>\\1</ol>");
+ m_TagMap["li"] = std::make_pair(QRegExp("\\[li\\](.*)\\[/li\\]"),
+ "<li>\\1</li>");
+ m_TagMap["*"] = std::make_pair(QRegExp("\\[\\*\\](.*)<br/>"),
+ "<li>\\1</li>");
+
+ // tables
+ m_TagMap["table"] = std::make_pair(QRegExp("\\[table\\](.*)\\[/table\\]"),
+ "<table>\\1</table>");
+ m_TagMap["tr"] = std::make_pair(QRegExp("\\[tr\\](.*)\\[/tr\\]"),
+ "<tr>\\1</tr>");
+ m_TagMap["th"] = std::make_pair(QRegExp("\\[th\\](.*)\\[/th\\]"),
+ "<th>\\1</th>");
+ m_TagMap["td"] = std::make_pair(QRegExp("\\[td\\](.*)\\[/td\\]"),
+ "<td>\\1</td>");
+
+ // web content
+ m_TagMap["url"] = std::make_pair(QRegExp("\\[url\\](.*)\\[/url\\]"),
+ "<a href=\"\\1\">\\1</a>");
+ m_TagMap["url="] = std::make_pair(QRegExp("\\[url=([^\\]]*)\\](.*)\\[/url\\]"),
+ "<a href=\"\\1\">\\2</a>");
+ m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), " ");
+ m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), " ");
+ m_TagMap["email="] = std::make_pair(QRegExp("\\[email=\"?([^\\]]*)\"?\\](.*)\\[/email\\]"),
+ "<a href=\"mailto:\\1\">\\2</a>");
+ m_TagMap["youtube"] = std::make_pair(QRegExp("\\[youtube\\](.*)\\[/youtube\\]"),
+ "<a href=\"http://www.youtube.com/v/\\1\">http://www.youtube.com/v/\\1</a>");
+
+ m_ColorMap.insert(std::make_pair<QString, QString>("red", "FF0000"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("green", "00FF00"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("blue", "0000FF"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("black", "000000"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("gray", "7F7F7F"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("white", "FFFFFF"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("yellow", "FFFF00"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("cyan", "00FFFF"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("magenta", "FF00FF"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("brown", "A52A2A"));
+ m_ColorMap.insert(std::make_pair<QString, QString>("orange", "FFCC00"));
+
+ // 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);
+ }
+ }
+
+private:
+
+ QRegExp m_TagNameExp;
+ TagMap m_TagMap;
+ std::map<QString, QString> 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", "<br/>");
+ 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));
+
+ // convert the tag and content if necessary
+ int length = -1;
+ QString replacement = BBCodeMap::instance().convertTag(input.mid(pos), length);
+ if (length != 0) {
+ QString temp = convertToHTML(replacement);
+ result.append(temp);
+ // 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;
+}
+
+} // namespace BBCode
+
diff --git a/src/bbcode.h b/src/bbcode.h index 0d4d8003..f88e9a6c 100644 --- a/src/bbcode.h +++ b/src/bbcode.h @@ -1,40 +1,40 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef BBCODE_H -#define BBCODE_H - - -#include <QString> - - -namespace BBCode { - -/** - * @brief convert a string with BB Code-Tags to HTML - * @param input the input string with BB tags - * @param replaceOccured if not NULL, this parameter will be set to true if any bb tags were replaced - * @return the same string in html representation - **/ -QString convertToHTML(const QString &input); - -} - - -#endif // BBCODE_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef BBCODE_H
+#define BBCODE_H
+
+
+#include <QString>
+
+
+namespace BBCode {
+
+/**
+ * @brief convert a string with BB Code-Tags to HTML
+ * @param input the input string with BB tags
+ * @param replaceOccured if not NULL, this parameter will be set to true if any bb tags were replaced
+ * @return the same string in html representation
+ **/
+QString convertToHTML(const QString &input);
+
+}
+
+
+#endif // BBCODE_H
diff --git a/src/browserdialog.cpp b/src/browserdialog.cpp index 4aa4e969..4897ea4f 100644 --- a/src/browserdialog.cpp +++ b/src/browserdialog.cpp @@ -1,270 +1,270 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "browserdialog.h" -#include "ui_browserdialog.h" - -#include "messagedialog.h" -#include "report.h" -#include "json.h" -#include "persistentcookiejar.h" - -#include <gameinfo.h> - -#include <utility.h> -#include <gameinfo.h> -#include <QNetworkCookieJar> -#include <QNetworkCookie> -#include <QMenu> -#include <QInputDialog> -#include <QWebHistory> -#include <QDir> -#include <QWebFrame> -#include <QDesktopWidget> - - - -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(MOBase::ToQString(MOShared::GameInfo::instance().getCacheDir())) + "/cookies.dat", this)); - - Qt::WindowFlags flags = windowFlags() | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint; - Qt::WindowFlags helpFlag = Qt::WindowContextHelpButtonHint; - flags = flags & (~helpFlag); - setWindowFlags(flags); - - m_Tabs = this->findChild<QTabWidget*>("browserTabWidget"); - - connect(m_Tabs, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int))); -} - - -BrowserDialog::~BrowserDialog() -{ - delete ui; -} - -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); - - 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->page(), SIGNAL(downloadRequested(QNetworkRequest)), this, SLOT(downloadRequested(QNetworkRequest))); - connect(newView->page(), SIGNAL(unsupportedContent(QNetworkReply*)), this, SLOT(unsupportedContent(QNetworkReply*))); - - ui->backBtn->setEnabled(false); - ui->fwdBtn->setEnabled(false); - m_Tabs->addTab(newView, tr("new")); - newView->settings()->setAttribute(QWebSettings::PluginsEnabled, true); - newView->settings()->setAttribute(QWebSettings::AutoLoadImages, true); -} - - -void BrowserDialog::openInNewTab(const QUrl &url) -{ - BrowserView *newView = new BrowserView(this); - initTab(newView); - newView->setUrl(url); -} - - -BrowserView *BrowserDialog::getCurrentView() -{ - return qobject_cast<BrowserView*>(m_Tabs->currentWidget()); -} - - -void BrowserDialog::urlChanged(const QUrl&) -{ - BrowserView *currentView = getCurrentView(); - if (currentView != NULL) { - ui->backBtn->setEnabled(currentView->history()->canGoBack()); - ui->fwdBtn->setEnabled(currentView->history()->canGoForward()); - } -} - - -void BrowserDialog::openUrl(const QUrl &url) -{ - if (isHidden()) { - show(); - } - openInNewTab(url); -} - - -void BrowserDialog::maximizeWidth() -{ - int viewportWidth = getCurrentView()->page()->viewportSize().width(); - int frameWidth = width() - viewportWidth; - - int contentWidth = getCurrentView()->page()->mainFrame()->contentsSize().width(); - - QDesktopWidget screen; - int currentScreen = screen.screenNumber(this); - int screenWidth = screen.screenGeometry(currentScreen).size().width(); - - int targetWidth = std::min<int>(std::max<int>(viewportWidth, contentWidth) + frameWidth, screenWidth); - this->resize(targetWidth, height()); -} - - -void BrowserDialog::progress(int value) -{ - ui->loadProgress->setValue(value); - if (value == 100) { - maximizeWidth(); - ui->loadProgress->setVisible(false); - } else { - ui->loadProgress->setVisible(true); - } -} - - -void BrowserDialog::titleChanged(const QString &title) -{ - BrowserView *view = qobject_cast<BrowserView*>(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); - } - return "unknown"; -} - -void BrowserDialog::unsupportedContent(QNetworkReply *reply) -{ - try { - QWebPage *page = qobject_cast<QWebPage*>(sender()); - if (page == NULL) { - qCritical("sender not a page"); - return; - } - BrowserView *view = qobject_cast<BrowserView*>(page->view()); - if (view == NULL) { - qCritical("no view?"); - return; - } - - qDebug("unsupported: %s - %s", view->url().toString().toUtf8().constData(), reply->url().toString().toUtf8().constData()); - 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::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 != NULL) { - currentView->back(); - } -} - -void BrowserDialog::on_fwdBtn_clicked() -{ - BrowserView *currentView = getCurrentView(); - if (currentView != NULL) { - currentView->forward(); - } -} - - -void BrowserDialog::startSearch() -{ - ui->searchEdit->setFocus(); -} - - -void BrowserDialog::on_searchEdit_returnPressed() -{ - BrowserView *currentView = getCurrentView(); - if (currentView != NULL) { - currentView->findText(ui->searchEdit->text(), QWebPage::FindWrapsAroundDocument); - } -} - -void BrowserDialog::on_refreshBtn_clicked() -{ - getCurrentView()->reload(); -} - -void BrowserDialog::on_browserTabWidget_currentChanged(int index) -{ - BrowserView *currentView = qobject_cast<BrowserView*>(ui->browserTabWidget->widget(index)); - if (currentView != NULL) { - ui->backBtn->setEnabled(currentView->history()->canGoBack()); - ui->fwdBtn->setEnabled(currentView->history()->canGoForward()); - } -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "browserdialog.h"
+#include "ui_browserdialog.h"
+
+#include "messagedialog.h"
+#include "report.h"
+#include "json.h"
+#include "persistentcookiejar.h"
+
+#include <gameinfo.h>
+
+#include <utility.h>
+#include <gameinfo.h>
+#include <QNetworkCookieJar>
+#include <QNetworkCookie>
+#include <QMenu>
+#include <QInputDialog>
+#include <QWebHistory>
+#include <QDir>
+#include <QWebFrame>
+#include <QDesktopWidget>
+
+
+
+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(MOBase::ToQString(MOShared::GameInfo::instance().getCacheDir())) + "/cookies.dat", this));
+
+ Qt::WindowFlags flags = windowFlags() | Qt::WindowMaximizeButtonHint | Qt::WindowMinimizeButtonHint;
+ Qt::WindowFlags helpFlag = Qt::WindowContextHelpButtonHint;
+ flags = flags & (~helpFlag);
+ setWindowFlags(flags);
+
+ m_Tabs = this->findChild<QTabWidget*>("browserTabWidget");
+
+ connect(m_Tabs, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int)));
+}
+
+
+BrowserDialog::~BrowserDialog()
+{
+ delete ui;
+}
+
+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);
+
+ 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->page(), SIGNAL(downloadRequested(QNetworkRequest)), this, SLOT(downloadRequested(QNetworkRequest)));
+ connect(newView->page(), SIGNAL(unsupportedContent(QNetworkReply*)), this, SLOT(unsupportedContent(QNetworkReply*)));
+
+ ui->backBtn->setEnabled(false);
+ ui->fwdBtn->setEnabled(false);
+ m_Tabs->addTab(newView, tr("new"));
+ newView->settings()->setAttribute(QWebSettings::PluginsEnabled, true);
+ newView->settings()->setAttribute(QWebSettings::AutoLoadImages, true);
+}
+
+
+void BrowserDialog::openInNewTab(const QUrl &url)
+{
+ BrowserView *newView = new BrowserView(this);
+ initTab(newView);
+ newView->setUrl(url);
+}
+
+
+BrowserView *BrowserDialog::getCurrentView()
+{
+ return qobject_cast<BrowserView*>(m_Tabs->currentWidget());
+}
+
+
+void BrowserDialog::urlChanged(const QUrl&)
+{
+ BrowserView *currentView = getCurrentView();
+ if (currentView != NULL) {
+ ui->backBtn->setEnabled(currentView->history()->canGoBack());
+ ui->fwdBtn->setEnabled(currentView->history()->canGoForward());
+ }
+}
+
+
+void BrowserDialog::openUrl(const QUrl &url)
+{
+ if (isHidden()) {
+ show();
+ }
+ openInNewTab(url);
+}
+
+
+void BrowserDialog::maximizeWidth()
+{
+ int viewportWidth = getCurrentView()->page()->viewportSize().width();
+ int frameWidth = width() - viewportWidth;
+
+ int contentWidth = getCurrentView()->page()->mainFrame()->contentsSize().width();
+
+ QDesktopWidget screen;
+ int currentScreen = screen.screenNumber(this);
+ int screenWidth = screen.screenGeometry(currentScreen).size().width();
+
+ int targetWidth = std::min<int>(std::max<int>(viewportWidth, contentWidth) + frameWidth, screenWidth);
+ this->resize(targetWidth, height());
+}
+
+
+void BrowserDialog::progress(int value)
+{
+ ui->loadProgress->setValue(value);
+ if (value == 100) {
+ maximizeWidth();
+ ui->loadProgress->setVisible(false);
+ } else {
+ ui->loadProgress->setVisible(true);
+ }
+}
+
+
+void BrowserDialog::titleChanged(const QString &title)
+{
+ BrowserView *view = qobject_cast<BrowserView*>(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);
+ }
+ return "unknown";
+}
+
+void BrowserDialog::unsupportedContent(QNetworkReply *reply)
+{
+ try {
+ QWebPage *page = qobject_cast<QWebPage*>(sender());
+ if (page == NULL) {
+ qCritical("sender not a page");
+ return;
+ }
+ BrowserView *view = qobject_cast<BrowserView*>(page->view());
+ if (view == NULL) {
+ qCritical("no view?");
+ return;
+ }
+
+ qDebug("unsupported: %s - %s", view->url().toString().toUtf8().constData(), reply->url().toString().toUtf8().constData());
+ 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::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 != NULL) {
+ currentView->back();
+ }
+}
+
+void BrowserDialog::on_fwdBtn_clicked()
+{
+ BrowserView *currentView = getCurrentView();
+ if (currentView != NULL) {
+ currentView->forward();
+ }
+}
+
+
+void BrowserDialog::startSearch()
+{
+ ui->searchEdit->setFocus();
+}
+
+
+void BrowserDialog::on_searchEdit_returnPressed()
+{
+ BrowserView *currentView = getCurrentView();
+ if (currentView != NULL) {
+ currentView->findText(ui->searchEdit->text(), QWebPage::FindWrapsAroundDocument);
+ }
+}
+
+void BrowserDialog::on_refreshBtn_clicked()
+{
+ getCurrentView()->reload();
+}
+
+void BrowserDialog::on_browserTabWidget_currentChanged(int index)
+{
+ BrowserView *currentView = qobject_cast<BrowserView*>(ui->browserTabWidget->widget(index));
+ if (currentView != NULL) {
+ ui->backBtn->setEnabled(currentView->history()->canGoBack());
+ ui->fwdBtn->setEnabled(currentView->history()->canGoForward());
+ }
+}
diff --git a/src/browserdialog.h b/src/browserdialog.h index bd27661b..c6e0aab6 100644 --- a/src/browserdialog.h +++ b/src/browserdialog.h @@ -1,124 +1,124 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef BROWSERDIALOG_H -#define BROWSERDIALOG_H - -#include "browserview.h" -#include "tutorialcontrol.h" -#include <QDialog> -#include <QProgressBar> -#include <QNetworkRequest> -#include <QNetworkReply> -#include <QTimer> -#include <QWebView> -#include <QQueue> -#include <QTabWidget> -#include <QAtomicInt> - - -namespace Ui { - class BrowserDialog; -} - - -/** - * @brief a dialog containing a webbrowser that is intended to browse the nexus network - **/ -class BrowserDialog : public QDialog -{ - Q_OBJECT - -public: - - /** - * @brief constructor - * - * @param accessManager the access manager to use for network requests - * @param parent parent widget - **/ - explicit BrowserDialog(QWidget *parent = 0); - ~BrowserDialog(); - - /** - * @brief set the url to open. If automatic login is enabled, the url is opened after login - * - * @param url the url to open - **/ - void openUrl(const QUrl &url); - -signals: - - /** - * @brief emitted when the user starts a download - * @param pageUrl url of the current web site from which the download was started - * @param reply network reply of the started download - */ - void requestDownload(const QUrl &pageUrl, QNetworkReply *reply); - -protected: - - virtual void closeEvent(QCloseEvent *); - -private slots: - - void initTab(BrowserView *newView); - void openInNewTab(const QUrl &url); - - void progress(int value); - - void titleChanged(const QString &title); - void unsupportedContent(QNetworkReply *reply); - void downloadRequested(const QNetworkRequest &request); - - void tabCloseRequested(int index); - - void urlChanged(const QUrl &url); - - void on_backBtn_clicked(); - - void on_fwdBtn_clicked(); - - void on_searchEdit_returnPressed(); - - void startSearch(); - - void on_refreshBtn_clicked(); - - void on_browserTabWidget_currentChanged(int index); - -private: - - QString guessFileName(const QString &url); - - BrowserView *getCurrentView(); - - void maximizeWidth(); - -private: - - Ui::BrowserDialog *ui; - - QNetworkAccessManager *m_AccessManager; - - QTabWidget *m_Tabs; - -}; - -#endif // BROWSERDIALOG_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef BROWSERDIALOG_H
+#define BROWSERDIALOG_H
+
+#include "browserview.h"
+#include "tutorialcontrol.h"
+#include <QDialog>
+#include <QProgressBar>
+#include <QNetworkRequest>
+#include <QNetworkReply>
+#include <QTimer>
+#include <QWebView>
+#include <QQueue>
+#include <QTabWidget>
+#include <QAtomicInt>
+
+
+namespace Ui {
+ class BrowserDialog;
+}
+
+
+/**
+ * @brief a dialog containing a webbrowser that is intended to browse the nexus network
+ **/
+class BrowserDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * @brief constructor
+ *
+ * @param accessManager the access manager to use for network requests
+ * @param parent parent widget
+ **/
+ explicit BrowserDialog(QWidget *parent = 0);
+ ~BrowserDialog();
+
+ /**
+ * @brief set the url to open. If automatic login is enabled, the url is opened after login
+ *
+ * @param url the url to open
+ **/
+ void openUrl(const QUrl &url);
+
+signals:
+
+ /**
+ * @brief emitted when the user starts a download
+ * @param pageUrl url of the current web site from which the download was started
+ * @param reply network reply of the started download
+ */
+ void requestDownload(const QUrl &pageUrl, QNetworkReply *reply);
+
+protected:
+
+ virtual void closeEvent(QCloseEvent *);
+
+private slots:
+
+ void initTab(BrowserView *newView);
+ void openInNewTab(const QUrl &url);
+
+ void progress(int value);
+
+ void titleChanged(const QString &title);
+ void unsupportedContent(QNetworkReply *reply);
+ void downloadRequested(const QNetworkRequest &request);
+
+ void tabCloseRequested(int index);
+
+ void urlChanged(const QUrl &url);
+
+ void on_backBtn_clicked();
+
+ void on_fwdBtn_clicked();
+
+ void on_searchEdit_returnPressed();
+
+ void startSearch();
+
+ void on_refreshBtn_clicked();
+
+ void on_browserTabWidget_currentChanged(int index);
+
+private:
+
+ QString guessFileName(const QString &url);
+
+ BrowserView *getCurrentView();
+
+ void maximizeWidth();
+
+private:
+
+ Ui::BrowserDialog *ui;
+
+ QNetworkAccessManager *m_AccessManager;
+
+ QTabWidget *m_Tabs;
+
+};
+
+#endif // BROWSERDIALOG_H
diff --git a/src/browserview.cpp b/src/browserview.cpp index f1b61e66..bd43a18e 100644 --- a/src/browserview.cpp +++ b/src/browserview.cpp @@ -1,76 +1,76 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "browserview.h" - -#include <QEvent> -#include <QKeyEvent> -#include <QWebFrame> -#include <QWebElement> -#include <QNetworkDiskCache> -#include <QMenu> -#include <Shlwapi.h> -#include "utility.h" - -BrowserView::BrowserView(QWidget *parent) - : QWebView(parent) -{ - installEventFilter(this); - - page()->settings()->setMaximumPagesInCache(10); -} - - -QWebView *BrowserView::createWindow(QWebPage::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<QKeyEvent*>(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<QMouseEvent*>(event); - if (mouseEvent->button() == Qt::MidButton) { - mouseEvent->ignore(); - return true; - } - } else if (event->type() == QEvent::MouseButtonRelease) { - QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event); - if (mouseEvent->button() == Qt::MidButton) { - QWebHitTestResult hitTest = page()->frameAt(mouseEvent->pos())->hitTestContent(mouseEvent->pos()); - if (hitTest.linkUrl().isValid()) { - emit openUrlInNewTab(hitTest.linkUrl()); - } - mouseEvent->ignore(); - - return true; - } - } - return QWebView::eventFilter(obj, event); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "browserview.h"
+
+#include <QEvent>
+#include <QKeyEvent>
+#include <QWebFrame>
+#include <QWebElement>
+#include <QNetworkDiskCache>
+#include <QMenu>
+#include <Shlwapi.h>
+#include "utility.h"
+
+BrowserView::BrowserView(QWidget *parent)
+ : QWebView(parent)
+{
+ installEventFilter(this);
+
+ page()->settings()->setMaximumPagesInCache(10);
+}
+
+
+QWebView *BrowserView::createWindow(QWebPage::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<QKeyEvent*>(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<QMouseEvent*>(event);
+ if (mouseEvent->button() == Qt::MidButton) {
+ mouseEvent->ignore();
+ return true;
+ }
+ } else if (event->type() == QEvent::MouseButtonRelease) {
+ QMouseEvent *mouseEvent = static_cast<QMouseEvent*>(event);
+ if (mouseEvent->button() == Qt::MidButton) {
+ QWebHitTestResult hitTest = page()->frameAt(mouseEvent->pos())->hitTestContent(mouseEvent->pos());
+ if (hitTest.linkUrl().isValid()) {
+ emit openUrlInNewTab(hitTest.linkUrl());
+ }
+ mouseEvent->ignore();
+
+ return true;
+ }
+ }
+ return QWebView::eventFilter(obj, event);
+}
diff --git a/src/browserview.h b/src/browserview.h index 3468276b..4f002ffc 100644 --- a/src/browserview.h +++ b/src/browserview.h @@ -1,80 +1,80 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef NEXUSVIEW_H -#define NEXUSVIEW_H - -#include "finddialog.h" - -#include <QWebView> -#include <QWebPage> -#include <QTabWidget> - -/** - * @brief web view used to display a nexus page - **/ -class BrowserView : public QWebView -{ - Q_OBJECT - -public: - - explicit BrowserView(QWidget *parent = 0); - -signals: - - /** - * @brief emitted when the user opens a new window to be displayed in another tab - * - * @param newView the view for the newly opened window - **/ - void initTab(BrowserView *newView); - - /** - * @brief emitted when the user requests a link to be opened in a new tab by middle-clicking - * - * @param url the url to open - */ - void openUrlInNewTab(const QUrl &url); - - /** - * @brief Ctrl-f was clicked. The containing dialog should activate its find-facility - */ - void startFind(); - - /** - * @brief F3 was pressed. The containing dialog should search again - */ - void findAgain(); - -protected: - - virtual QWebView *createWindow(QWebPage::WebWindowType type); - - virtual bool eventFilter(QObject *obj, QEvent *event); - - -private: - - QString m_FindPattern; - bool m_MiddleClick; - -}; - -#endif // NEXUSVIEW_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef NEXUSVIEW_H
+#define NEXUSVIEW_H
+
+#include "finddialog.h"
+
+#include <QWebView>
+#include <QWebPage>
+#include <QTabWidget>
+
+/**
+ * @brief web view used to display a nexus page
+ **/
+class BrowserView : public QWebView
+{
+ Q_OBJECT
+
+public:
+
+ explicit BrowserView(QWidget *parent = 0);
+
+signals:
+
+ /**
+ * @brief emitted when the user opens a new window to be displayed in another tab
+ *
+ * @param newView the view for the newly opened window
+ **/
+ void initTab(BrowserView *newView);
+
+ /**
+ * @brief emitted when the user requests a link to be opened in a new tab by middle-clicking
+ *
+ * @param url the url to open
+ */
+ void openUrlInNewTab(const QUrl &url);
+
+ /**
+ * @brief Ctrl-f was clicked. The containing dialog should activate its find-facility
+ */
+ void startFind();
+
+ /**
+ * @brief F3 was pressed. The containing dialog should search again
+ */
+ void findAgain();
+
+protected:
+
+ virtual QWebView *createWindow(QWebPage::WebWindowType type);
+
+ virtual bool eventFilter(QObject *obj, QEvent *event);
+
+
+private:
+
+ QString m_FindPattern;
+ bool m_MiddleClick;
+
+};
+
+#endif // NEXUSVIEW_H
diff --git a/src/categories.cpp b/src/categories.cpp index 27720829..79c646b3 100644 --- a/src/categories.cpp +++ b/src/categories.cpp @@ -1,301 +1,301 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "categories.h" -#include <utility.h> -#include "report.h" -#include <gameinfo.h> -#include <QObject> -#include <QFile> -#include <QDir> -#include <QList> -#include <QCoreApplication> - - -using namespace MOBase; -using namespace MOShared; - - -CategoryFactory* CategoryFactory::s_Instance = NULL; - - -QString CategoryFactory::categoriesFilePath() -{ - return QCoreApplication::applicationDirPath() + "/categories.dat"; -} - - -CategoryFactory::CategoryFactory() -{ - atexit(&cleanup); - reset(); - - QFile categoryFile(categoriesFilePath()); - - if (!categoryFile.open(QIODevice::ReadOnly)) { - loadDefaultCategories(); - } else { - int lineNum = 0; - while (!categoryFile.atEnd()) { - QByteArray line = categoryFile.readLine(); - ++lineNum; - QList<QByteArray> cells = line.split('|'); - if (cells.count() != 4) { - qCritical("invalid category line %d: %s (%d cells)", - lineNum, line.constData(), cells.count()); - } else { - std::vector<int> nexusIDs; - if (cells[2].length() > 0) { - QList<QByteArray> nexusIDStrings = cells[2].split(','); - for (QList<QByteArray>::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); - } - } - categoryFile.close(); - } - std::sort(m_Categories.begin(), m_Categories.end()); - setParents(); -} - - -CategoryFactory &CategoryFactory::instance() -{ - if (s_Instance == NULL) { - s_Instance = new CategoryFactory; - } - return *s_Instance; -} - - -void CategoryFactory::reset() -{ - m_Categories.clear(); - m_IDMap.clear(); - addCategory(0, "None", MakeVector<int>(2, 28, 87), 0); -} - - -void CategoryFactory::setParents() -{ - for (std::vector<Category>::iterator iter = m_Categories.begin(); - iter != m_Categories.end(); ++iter) { - iter->m_HasChildren = false; - } - - for (std::vector<Category>::const_iterator categoryIter = m_Categories.begin(); - categoryIter != m_Categories.end(); ++categoryIter) { - if (categoryIter->m_ParentID != 0) { - std::map<int, unsigned int>::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 = NULL; -} - - -void CategoryFactory::saveCategories() -{ - QFile categoryFile(categoriesFilePath()); - - if (!categoryFile.open(QIODevice::WriteOnly)) { - reportError(QObject::tr("Failed to save custom categories")); - return; - } - - categoryFile.resize(0); - for (std::vector<Category>::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); - } - categoryFile.close(); -} - - -unsigned int CategoryFactory::countCategories(std::tr1::function<bool (const Category &category)> filter) -{ - unsigned int result = 0; - for (auto iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { - if (filter(*iter)) { - ++result; - } - } - return result; -} - - -void CategoryFactory::addCategory(int id, const QString &name, const std::vector<int> &nexusIDs, int parentID) -{ - int index = m_Categories.size(); - m_Categories.push_back(Category(index, id, name, nexusIDs, parentID)); - for (std::vector<int>::const_iterator iter = nexusIDs.begin(); - iter != nexusIDs.end(); ++iter) { - m_NexusMap[*iter] = 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<int>(1, 51), 0); - addCategory(2, "Armour", MakeVector<int>(1, 54), 0); - addCategory(3, "Audio", MakeVector<int>(1, 61), 0); - addCategory(5, "Clothing", MakeVector<int>(1, 60), 0); - addCategory(6, "Collectables", MakeVector<int>(1, 92), 0); - addCategory(7, "Creatures", MakeVector<int>(2, 83, 65), 0); - addCategory(8, "Factions", MakeVector<int>(1, 25), 0); - addCategory(9, "Gameplay", MakeVector<int>(1, 24), 0); - addCategory(10, "Hair", MakeVector<int>(1, 26), 0); - addCategory(11, "Items", MakeVector<int>(2, 27, 85), 0); - addCategory(19, "Weapons", MakeVector<int>(2, 36, 55), 11); - addCategory(12, "Locations", MakeVector<int>(7, 22, 30, 70, 88, 89, 90, 91), 0); - addCategory(4, "Cities", MakeVector<int>(1, 53), 12); - addCategory(20, "Magic", MakeVector<int>(3, 75, 93, 94), 0); - addCategory(21, "Models & Textures", MakeVector<int>(1, 29), 0); - addCategory(13, "NPCs", MakeVector<int>(1, 33), 0); - addCategory(14, "Patches", MakeVector<int>(2, 79, 84), 0); - addCategory(15, "Quests", MakeVector<int>(1, 35), 0); - addCategory(16, "Races & Classes", MakeVector<int>(1, 34), 0); - addCategory(22, "Skills", MakeVector<int>(1, 73), 0); - addCategory(17, "UI", MakeVector<int>(1, 42), 0); - addCategory(18, "Visuals", MakeVector<int>(1, 62), 0); -} - - -int CategoryFactory::getParentID(unsigned int index) const -{ - if ((index < 0) || (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<int, unsigned int>::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); - } - } else { - qWarning("%d is no valid category id", id); - return false; - } -} - - -bool CategoryFactory::hasChildren(unsigned int index) const -{ - if ((index < 0) || (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 < 0) || (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 < 0) || (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<int, unsigned int>::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; -} - - - -unsigned int CategoryFactory::resolveNexusID(int nexusID) const -{ - std::map<int, unsigned int>::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; - } -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "categories.h"
+#include <utility.h>
+#include "report.h"
+#include <gameinfo.h>
+#include <QObject>
+#include <QFile>
+#include <QDir>
+#include <QList>
+#include <QCoreApplication>
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+CategoryFactory* CategoryFactory::s_Instance = NULL;
+
+
+QString CategoryFactory::categoriesFilePath()
+{
+ return QCoreApplication::applicationDirPath() + "/categories.dat";
+}
+
+
+CategoryFactory::CategoryFactory()
+{
+ atexit(&cleanup);
+ reset();
+
+ QFile categoryFile(categoriesFilePath());
+
+ if (!categoryFile.open(QIODevice::ReadOnly)) {
+ loadDefaultCategories();
+ } else {
+ int lineNum = 0;
+ while (!categoryFile.atEnd()) {
+ QByteArray line = categoryFile.readLine();
+ ++lineNum;
+ QList<QByteArray> cells = line.split('|');
+ if (cells.count() != 4) {
+ qCritical("invalid category line %d: %s (%d cells)",
+ lineNum, line.constData(), cells.count());
+ } else {
+ std::vector<int> nexusIDs;
+ if (cells[2].length() > 0) {
+ QList<QByteArray> nexusIDStrings = cells[2].split(',');
+ for (QList<QByteArray>::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);
+ }
+ }
+ categoryFile.close();
+ }
+ std::sort(m_Categories.begin(), m_Categories.end());
+ setParents();
+}
+
+
+CategoryFactory &CategoryFactory::instance()
+{
+ if (s_Instance == NULL) {
+ s_Instance = new CategoryFactory;
+ }
+ return *s_Instance;
+}
+
+
+void CategoryFactory::reset()
+{
+ m_Categories.clear();
+ m_IDMap.clear();
+ addCategory(0, "None", MakeVector<int>(2, 28, 87), 0);
+}
+
+
+void CategoryFactory::setParents()
+{
+ for (std::vector<Category>::iterator iter = m_Categories.begin();
+ iter != m_Categories.end(); ++iter) {
+ iter->m_HasChildren = false;
+ }
+
+ for (std::vector<Category>::const_iterator categoryIter = m_Categories.begin();
+ categoryIter != m_Categories.end(); ++categoryIter) {
+ if (categoryIter->m_ParentID != 0) {
+ std::map<int, unsigned int>::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 = NULL;
+}
+
+
+void CategoryFactory::saveCategories()
+{
+ QFile categoryFile(categoriesFilePath());
+
+ if (!categoryFile.open(QIODevice::WriteOnly)) {
+ reportError(QObject::tr("Failed to save custom categories"));
+ return;
+ }
+
+ categoryFile.resize(0);
+ for (std::vector<Category>::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);
+ }
+ categoryFile.close();
+}
+
+
+unsigned int CategoryFactory::countCategories(std::tr1::function<bool (const Category &category)> filter)
+{
+ unsigned int result = 0;
+ for (auto iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) {
+ if (filter(*iter)) {
+ ++result;
+ }
+ }
+ return result;
+}
+
+
+void CategoryFactory::addCategory(int id, const QString &name, const std::vector<int> &nexusIDs, int parentID)
+{
+ int index = m_Categories.size();
+ m_Categories.push_back(Category(index, id, name, nexusIDs, parentID));
+ for (std::vector<int>::const_iterator iter = nexusIDs.begin();
+ iter != nexusIDs.end(); ++iter) {
+ m_NexusMap[*iter] = 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<int>(1, 51), 0);
+ addCategory(2, "Armour", MakeVector<int>(1, 54), 0);
+ addCategory(3, "Audio", MakeVector<int>(1, 61), 0);
+ addCategory(5, "Clothing", MakeVector<int>(1, 60), 0);
+ addCategory(6, "Collectables", MakeVector<int>(1, 92), 0);
+ addCategory(7, "Creatures", MakeVector<int>(2, 83, 65), 0);
+ addCategory(8, "Factions", MakeVector<int>(1, 25), 0);
+ addCategory(9, "Gameplay", MakeVector<int>(1, 24), 0);
+ addCategory(10, "Hair", MakeVector<int>(1, 26), 0);
+ addCategory(11, "Items", MakeVector<int>(2, 27, 85), 0);
+ addCategory(19, "Weapons", MakeVector<int>(2, 36, 55), 11);
+ addCategory(12, "Locations", MakeVector<int>(7, 22, 30, 70, 88, 89, 90, 91), 0);
+ addCategory(4, "Cities", MakeVector<int>(1, 53), 12);
+ addCategory(20, "Magic", MakeVector<int>(3, 75, 93, 94), 0);
+ addCategory(21, "Models & Textures", MakeVector<int>(1, 29), 0);
+ addCategory(13, "NPCs", MakeVector<int>(1, 33), 0);
+ addCategory(14, "Patches", MakeVector<int>(2, 79, 84), 0);
+ addCategory(15, "Quests", MakeVector<int>(1, 35), 0);
+ addCategory(16, "Races & Classes", MakeVector<int>(1, 34), 0);
+ addCategory(22, "Skills", MakeVector<int>(1, 73), 0);
+ addCategory(17, "UI", MakeVector<int>(1, 42), 0);
+ addCategory(18, "Visuals", MakeVector<int>(1, 62), 0);
+}
+
+
+int CategoryFactory::getParentID(unsigned int index) const
+{
+ if ((index < 0) || (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<int, unsigned int>::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);
+ }
+ } else {
+ qWarning("%d is no valid category id", id);
+ return false;
+ }
+}
+
+
+bool CategoryFactory::hasChildren(unsigned int index) const
+{
+ if ((index < 0) || (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 < 0) || (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 < 0) || (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<int, unsigned int>::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;
+}
+
+
+
+unsigned int CategoryFactory::resolveNexusID(int nexusID) const
+{
+ std::map<int, unsigned int>::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 bfebe1cb..96e436e5 100644 --- a/src/categories.h +++ b/src/categories.h @@ -1,200 +1,200 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef CATEGORIES_H -#define CATEGORIES_H - - -#include <QString> -#include <vector> -#include <map> -#include <functional> - - -/** - * @brief Manage the available mod categories - * @warning member functions of this class currently use a wild mix of ids and indexes to look up categories, - * optimized to where the request comes from. Therefore be very careful which of the two you have available - **/ -class CategoryFactory { - - 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; - -public: - - struct Category { - Category(int sortValue, int id, const QString &name, const std::vector<int> &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<int> 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 save the categories to the categories.dat file - **/ - void saveCategories(); - - /** - * @brief retrieve the number of available categories - * - * @return unsigned int number of categories - **/ - unsigned 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<bool (const Category &category)> 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 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(); - -private: - - CategoryFactory(); - - void loadDefaultCategories(); - - void addCategory(int id, const QString &name, const std::vector<int> &nexusID, int parentID); - - void setParents(); - - static void cleanup(); - -private: - - static CategoryFactory *s_Instance; - - std::vector<Category> m_Categories; - std::map<int, unsigned int> m_IDMap; - std::map<int, unsigned int> m_NexusMap; - -private: - -}; - - -#endif // CATEGORIES_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef CATEGORIES_H
+#define CATEGORIES_H
+
+
+#include <QString>
+#include <vector>
+#include <map>
+#include <functional>
+
+
+/**
+ * @brief Manage the available mod categories
+ * @warning member functions of this class currently use a wild mix of ids and indexes to look up categories,
+ * optimized to where the request comes from. Therefore be very careful which of the two you have available
+ **/
+class CategoryFactory {
+
+ 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;
+
+public:
+
+ struct Category {
+ Category(int sortValue, int id, const QString &name, const std::vector<int> &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<int> 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 save the categories to the categories.dat file
+ **/
+ void saveCategories();
+
+ /**
+ * @brief retrieve the number of available categories
+ *
+ * @return unsigned int number of categories
+ **/
+ unsigned 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<bool (const Category &category)> 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 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();
+
+private:
+
+ CategoryFactory();
+
+ void loadDefaultCategories();
+
+ void addCategory(int id, const QString &name, const std::vector<int> &nexusID, int parentID);
+
+ void setParents();
+
+ static void cleanup();
+
+private:
+
+ static CategoryFactory *s_Instance;
+
+ std::vector<Category> m_Categories;
+ std::map<int, unsigned int> m_IDMap;
+ std::map<int, unsigned int> m_NexusMap;
+
+private:
+
+};
+
+
+#endif // CATEGORIES_H
diff --git a/src/categoriesdialog.cpp b/src/categoriesdialog.cpp index 793ece66..881179a4 100644 --- a/src/categoriesdialog.cpp +++ b/src/categoriesdialog.cpp @@ -1,243 +1,243 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "categoriesdialog.h" -#include "ui_categoriesdialog.h" -#include "categories.h" -#include "utility.h" -#include <QItemDelegate> -#include <QRegExpValidator> -#include <QLineEdit> -#include <QMenu> - - -class NewIDValidator : public QIntValidator { -public: - NewIDValidator(const std::set<int> &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<int> &m_UsedIDs; -}; - - -class ExistingIDValidator : public QIntValidator { -public: - ExistingIDValidator(const std::set<int> &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<int> &m_UsedIDs; -}; - - -class ValidatingDelegate : public QItemDelegate { - -public: - ValidatingDelegate(QObject *parent, QValidator *validator) - : QItemDelegate(parent), m_Validator(validator) {} - - QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex&) const - { - QLineEdit *edit = new QLineEdit(parent); - edit->setValidator(m_Validator); - return edit; - } - virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const - { - QLineEdit *edit = qobject_cast<QLineEdit*>(editor); - int pos = 0; - QString editText = edit->text(); - if (m_Validator->validate(editText, pos) == QValidator::Acceptable) { - QItemDelegate::setModelData(editor, model, index); - } - } -private: - QValidator *m_Validator; -}; - - -CategoriesDialog::CategoriesDialog(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; -} - - -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<int> 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()); - } - 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::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); -#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<CategoryFactory::Category>::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<QTableWidgetItem> idItem(new QTableWidgetItem()); - idItem->setData(Qt::DisplayRole, category.m_ID); - - QScopedPointer<QTableWidgetItem> nameItem(new QTableWidgetItem(category.m_Name)); - QScopedPointer<QTableWidgetItem> nexusIDItem(new QTableWidgetItem(MOBase::VectorJoin(category.m_NexusIDs, ","))); - QScopedPointer<QTableWidgetItem> 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::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())); - - menu.exec(ui->categoriesTable->mapToGlobal(pos)); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "categoriesdialog.h"
+#include "ui_categoriesdialog.h"
+#include "categories.h"
+#include "utility.h"
+#include <QItemDelegate>
+#include <QRegExpValidator>
+#include <QLineEdit>
+#include <QMenu>
+
+
+class NewIDValidator : public QIntValidator {
+public:
+ NewIDValidator(const std::set<int> &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<int> &m_UsedIDs;
+};
+
+
+class ExistingIDValidator : public QIntValidator {
+public:
+ ExistingIDValidator(const std::set<int> &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<int> &m_UsedIDs;
+};
+
+
+class ValidatingDelegate : public QItemDelegate {
+
+public:
+ ValidatingDelegate(QObject *parent, QValidator *validator)
+ : QItemDelegate(parent), m_Validator(validator) {}
+
+ QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem&, const QModelIndex&) const
+ {
+ QLineEdit *edit = new QLineEdit(parent);
+ edit->setValidator(m_Validator);
+ return edit;
+ }
+ virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
+ {
+ QLineEdit *edit = qobject_cast<QLineEdit*>(editor);
+ int pos = 0;
+ QString editText = edit->text();
+ if (m_Validator->validate(editText, pos) == QValidator::Acceptable) {
+ QItemDelegate::setModelData(editor, model, index);
+ }
+ }
+private:
+ QValidator *m_Validator;
+};
+
+
+CategoriesDialog::CategoriesDialog(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;
+}
+
+
+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<int> 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());
+ }
+ 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::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);
+#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<CategoryFactory::Category>::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<QTableWidgetItem> idItem(new QTableWidgetItem());
+ idItem->setData(Qt::DisplayRole, category.m_ID);
+
+ QScopedPointer<QTableWidgetItem> nameItem(new QTableWidgetItem(category.m_Name));
+ QScopedPointer<QTableWidgetItem> nexusIDItem(new QTableWidgetItem(MOBase::VectorJoin(category.m_NexusIDs, ",")));
+ QScopedPointer<QTableWidgetItem> 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::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()));
+
+ menu.exec(ui->categoriesTable->mapToGlobal(pos));
+}
diff --git a/src/categoriesdialog.h b/src/categoriesdialog.h index 67851618..72d2154d 100644 --- a/src/categoriesdialog.h +++ b/src/categoriesdialog.h @@ -1,71 +1,71 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef CATEGORIESDIALOG_H -#define CATEGORIESDIALOG_H - -#include "tutorabledialog.h" -#include <set> - -namespace Ui { -class CategoriesDialog; -} - -/** - * @brief Dialog that allows users to configure mod categories - **/ -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(); - -private slots: - - void on_categoriesTable_customContextMenuRequested(const QPoint &pos); - void addCategory_clicked(); - void removeCategory_clicked(); - void cellChanged(int row, int column); - -private: - - void refreshIDs(); - void fillTable(); - -private: - - Ui::CategoriesDialog *ui; - int m_ContextRow; - - int m_HighestID; - std::set<int> m_IDs; - -}; - -#endif // CATEGORIESDIALOG_H - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef CATEGORIESDIALOG_H
+#define CATEGORIESDIALOG_H
+
+#include "tutorabledialog.h"
+#include <set>
+
+namespace Ui {
+class CategoriesDialog;
+}
+
+/**
+ * @brief Dialog that allows users to configure mod categories
+ **/
+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();
+
+private slots:
+
+ void on_categoriesTable_customContextMenuRequested(const QPoint &pos);
+ void addCategory_clicked();
+ void removeCategory_clicked();
+ void cellChanged(int row, int column);
+
+private:
+
+ void refreshIDs();
+ void fillTable();
+
+private:
+
+ Ui::CategoriesDialog *ui;
+ int m_ContextRow;
+
+ int m_HighestID;
+ std::set<int> m_IDs;
+
+};
+
+#endif // CATEGORIESDIALOG_H
+
diff --git a/src/credentialsdialog.cpp b/src/credentialsdialog.cpp index d9438c75..04774548 100644 --- a/src/credentialsdialog.cpp +++ b/src/credentialsdialog.cpp @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#include "credentialsdialog.h"
#include "ui_credentialsdialog.h"
diff --git a/src/credentialsdialog.h b/src/credentialsdialog.h index 1d65447b..8a68c7d8 100644 --- a/src/credentialsdialog.h +++ b/src/credentialsdialog.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#ifndef CREDENTIALSDIALOG_H
#define CREDENTIALSDIALOG_H
diff --git a/src/directoryrefresher.cpp b/src/directoryrefresher.cpp index a00907a1..2186f9a5 100644 --- a/src/directoryrefresher.cpp +++ b/src/directoryrefresher.cpp @@ -1,156 +1,156 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "directoryrefresher.h" -#include "utility.h" -#include "report.h" -#include "modinfo.h" -#include <gameinfo.h> -#include <QDir> -#include <QString> - - -using namespace MOBase; -using namespace MOShared; - - -DirectoryRefresher::DirectoryRefresher() - : m_DirectoryStructure(NULL) -{ -} - -DirectoryRefresher::~DirectoryRefresher() -{ - delete m_DirectoryStructure; -} - -DirectoryEntry *DirectoryRefresher::getDirectoryStructure() -{ - QMutexLocker locker(&m_RefreshLock); - DirectoryEntry *result = m_DirectoryStructure; - m_DirectoryStructure = NULL; - return result; -} - -void DirectoryRefresher::setMods(const std::vector<std::tuple<QString, QString, int> > &mods - , const std::set<QString> &managedArchives) -{ - QMutexLocker locker(&m_RefreshLock); - - m_Mods.clear(); - for (auto mod = mods.begin(); mod != mods.end(); ++mod) { - QString name = std::get<0>(*mod); - ModInfo::Ptr info = ModInfo::getByIndex(ModInfo::getIndex(name)); - m_Mods.push_back(EntryInfo(name, std::get<1>(*mod), info->stealFiles(), info->archives(), std::get<2>(*mod))); - } - - m_EnabledArchives = managedArchives; -} - -void DirectoryRefresher::cleanStructure(DirectoryEntry *structure) -{ - static 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 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)); - - foreach (const QString &archive, archives) { - QFileInfo fileInfo(archive); - if (m_EnabledArchives.find(fileInfo.fileName()) != m_EnabledArchives.end()) { - directoryStructure->addFromBSA(ToWString(modName), directoryW, - ToWString(QDir::toNativeSeparators(fileInfo.absoluteFilePath())), priority); - } - } -} - -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); - foreach (const QString &filename, stealFiles) { - QFileInfo fileInfo(filename); - FileEntry::Ptr file = directoryStructure->findFile(ToWString(fileInfo.fileName())); - if (file.get() != NULL) { - if (file->getOrigin() == 0) { - // replace data as the origin on this bsa - file->removeOrigin(0); - file->addOrigin(origin.getID(), file->getFileTime(), L""); - } - } - } - } 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::refresh() -{ - QMutexLocker locker(&m_RefreshLock); - - delete m_DirectoryStructure; - - m_DirectoryStructure = new DirectoryEntry(L"data", NULL, 0); - - std::wstring dataDirectory = GameInfo::instance().getGameDirectory() + L"\\data"; - 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 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 bsa: %1").arg(e.what())); - } - emit progress((i * 100) / m_Mods.size() + 1); - } - - emit progress(100); - - cleanStructure(m_DirectoryStructure); - - emit refreshed(); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "directoryrefresher.h"
+#include "utility.h"
+#include "report.h"
+#include "modinfo.h"
+#include <gameinfo.h>
+#include <QDir>
+#include <QString>
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+DirectoryRefresher::DirectoryRefresher()
+ : m_DirectoryStructure(NULL)
+{
+}
+
+DirectoryRefresher::~DirectoryRefresher()
+{
+ delete m_DirectoryStructure;
+}
+
+DirectoryEntry *DirectoryRefresher::getDirectoryStructure()
+{
+ QMutexLocker locker(&m_RefreshLock);
+ DirectoryEntry *result = m_DirectoryStructure;
+ m_DirectoryStructure = NULL;
+ return result;
+}
+
+void DirectoryRefresher::setMods(const std::vector<std::tuple<QString, QString, int> > &mods
+ , const std::set<QString> &managedArchives)
+{
+ QMutexLocker locker(&m_RefreshLock);
+
+ m_Mods.clear();
+ for (auto mod = mods.begin(); mod != mods.end(); ++mod) {
+ QString name = std::get<0>(*mod);
+ ModInfo::Ptr info = ModInfo::getByIndex(ModInfo::getIndex(name));
+ m_Mods.push_back(EntryInfo(name, std::get<1>(*mod), info->stealFiles(), info->archives(), std::get<2>(*mod)));
+ }
+
+ m_EnabledArchives = managedArchives;
+}
+
+void DirectoryRefresher::cleanStructure(DirectoryEntry *structure)
+{
+ static 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 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));
+
+ foreach (const QString &archive, archives) {
+ QFileInfo fileInfo(archive);
+ if (m_EnabledArchives.find(fileInfo.fileName()) != m_EnabledArchives.end()) {
+ directoryStructure->addFromBSA(ToWString(modName), directoryW,
+ ToWString(QDir::toNativeSeparators(fileInfo.absoluteFilePath())), priority);
+ }
+ }
+}
+
+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);
+ foreach (const QString &filename, stealFiles) {
+ QFileInfo fileInfo(filename);
+ FileEntry::Ptr file = directoryStructure->findFile(ToWString(fileInfo.fileName()));
+ if (file.get() != NULL) {
+ if (file->getOrigin() == 0) {
+ // replace data as the origin on this bsa
+ file->removeOrigin(0);
+ file->addOrigin(origin.getID(), file->getFileTime(), L"");
+ }
+ }
+ }
+ } 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::refresh()
+{
+ QMutexLocker locker(&m_RefreshLock);
+
+ delete m_DirectoryStructure;
+
+ m_DirectoryStructure = new DirectoryEntry(L"data", NULL, 0);
+
+ std::wstring dataDirectory = GameInfo::instance().getGameDirectory() + L"\\data";
+ 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 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 bsa: %1").arg(e.what()));
+ }
+ emit progress((i * 100) / m_Mods.size() + 1);
+ }
+
+ emit progress(100);
+
+ cleanStructure(m_DirectoryStructure);
+
+ emit refreshed();
+}
diff --git a/src/directoryrefresher.h b/src/directoryrefresher.h index 66e85465..53709e1e 100644 --- a/src/directoryrefresher.h +++ b/src/directoryrefresher.h @@ -1,147 +1,147 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef DIRECTORYREFRESHER_H -#define DIRECTORYREFRESHER_H - -#include <directoryentry.h> -#include <QObject> -#include <QMutex> -#include <QStringList> -#include <vector> -#include <set> -#include <tuple> - - -/** - * @brief used to asynchronously generate the virtual view of the combined data directory - **/ -class DirectoryRefresher : public QObject -{ - - 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<std::tuple<QString, QString, int> > &mods, const std::set<QString> &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(); - -signals: - - 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; - }; - -private: - - std::vector<EntryInfo> m_Mods; - std::set<QString> m_EnabledArchives; - MOShared::DirectoryEntry *m_DirectoryStructure; - QMutex m_RefreshLock; - -}; - -#endif // DIRECTORYREFRESHER_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef DIRECTORYREFRESHER_H
+#define DIRECTORYREFRESHER_H
+
+#include <directoryentry.h>
+#include <QObject>
+#include <QMutex>
+#include <QStringList>
+#include <vector>
+#include <set>
+#include <tuple>
+
+
+/**
+ * @brief used to asynchronously generate the virtual view of the combined data directory
+ **/
+class DirectoryRefresher : public QObject
+{
+
+ 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<std::tuple<QString, QString, int> > &mods, const std::set<QString> &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();
+
+signals:
+
+ 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;
+ };
+
+private:
+
+ std::vector<EntryInfo> m_Mods;
+ std::set<QString> m_EnabledArchives;
+ MOShared::DirectoryEntry *m_DirectoryStructure;
+ QMutex m_RefreshLock;
+
+};
+
+#endif // DIRECTORYREFRESHER_H
diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp index e19e2e7b..ef2db91b 100644 --- a/src/downloadlist.cpp +++ b/src/downloadlist.cpp @@ -1,114 +1,114 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "downloadlist.h" -#include "downloadmanager.h" -#include <QEvent> - -#include <QSortFilterProxyModel> - - -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::columnCount(const QModelIndex&) const -{ - return 3; -} - - -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<br><span>%4</span>").arg(info->modName).arg(m_Manager->getModID(index.row())).arg(info->version.canonicalString()).arg(info->description); - } - return text; - } else { - return tr("pending download"); - } - } else { - return QVariant(); - } -} - - -void DownloadList::aboutToUpdate() -{ - emit beginResetModel(); -} - - -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); - } -} - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "downloadlist.h"
+#include "downloadmanager.h"
+#include <QEvent>
+
+#include <QSortFilterProxyModel>
+
+
+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::columnCount(const QModelIndex&) const
+{
+ return 3;
+}
+
+
+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<br><span>%4</span>").arg(info->modName).arg(m_Manager->getModID(index.row())).arg(info->version.canonicalString()).arg(info->description);
+ }
+ return text;
+ } else {
+ return tr("pending download");
+ }
+ } else {
+ return QVariant();
+ }
+}
+
+
+void DownloadList::aboutToUpdate()
+{
+ emit beginResetModel();
+}
+
+
+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);
+ }
+}
+
diff --git a/src/downloadlist.h b/src/downloadlist.h index ba0c287d..2316dddc 100644 --- a/src/downloadlist.h +++ b/src/downloadlist.h @@ -1,96 +1,96 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef DOWNLOADLIST_H -#define DOWNLOADLIST_H - -#include <QAbstractTableModel> - - -class DownloadManager; - - -/** - * @brief model of the list of active and completed downloads - **/ -class DownloadList : public QAbstractTableModel -{ - - Q_OBJECT - -public: - - 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; - -public slots: - - /** - * @brief used to inform the model that data has changed - * - * @param row the row that changed. This can be negative to update the whole view - **/ - void update(int row); - - void aboutToUpdate(); - -private: - - DownloadManager *m_Manager; - -}; - -#endif // DOWNLOADLIST_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef DOWNLOADLIST_H
+#define DOWNLOADLIST_H
+
+#include <QAbstractTableModel>
+
+
+class DownloadManager;
+
+
+/**
+ * @brief model of the list of active and completed downloads
+ **/
+class DownloadList : public QAbstractTableModel
+{
+
+ Q_OBJECT
+
+public:
+
+ 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;
+
+public slots:
+
+ /**
+ * @brief used to inform the model that data has changed
+ *
+ * @param row the row that changed. This can be negative to update the whole view
+ **/
+ void update(int row);
+
+ void aboutToUpdate();
+
+private:
+
+ DownloadManager *m_Manager;
+
+};
+
+#endif // DOWNLOADLIST_H
diff --git a/src/downloadlistsortproxy.cpp b/src/downloadlistsortproxy.cpp index 7abe8579..9eb22655 100644 --- a/src/downloadlistsortproxy.cpp +++ b/src/downloadlistsortproxy.cpp @@ -1,65 +1,65 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "downloadlistsortproxy.h" -#include "downloadlist.h" - -DownloadListSortProxy::DownloadListSortProxy(const DownloadManager *manager, QObject *parent) - : QSortFilterProxyModel(parent), m_Manager(manager), m_CurrentFilter() -{ -} - -void DownloadListSortProxy::updateFilter(const QString &filter) -{ - m_CurrentFilter = filter; - invalidateFilter(); -} - - -bool DownloadListSortProxy::lessThan(const QModelIndex &left, - const QModelIndex &right) const -{ - int leftIndex = sourceModel()->data(left).toInt(); - int rightIndex = sourceModel()->data(right).toInt(); - if (leftIndex < 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; - } - } else { - return leftIndex < rightIndex; - } -} - - -bool DownloadListSortProxy::filterAcceptsRow(int source_row, const QModelIndex&) const -{ - if (m_CurrentFilter.length() == 0) { - return true; - } else if (source_row < m_Manager->numTotalDownloads()) { - return m_Manager->getFileName(source_row).contains(m_CurrentFilter, Qt::CaseInsensitive); - } else { - return false; - } -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "downloadlistsortproxy.h"
+#include "downloadlist.h"
+
+DownloadListSortProxy::DownloadListSortProxy(const DownloadManager *manager, QObject *parent)
+ : QSortFilterProxyModel(parent), m_Manager(manager), m_CurrentFilter()
+{
+}
+
+void DownloadListSortProxy::updateFilter(const QString &filter)
+{
+ m_CurrentFilter = filter;
+ invalidateFilter();
+}
+
+
+bool DownloadListSortProxy::lessThan(const QModelIndex &left,
+ const QModelIndex &right) const
+{
+ int leftIndex = sourceModel()->data(left).toInt();
+ int rightIndex = sourceModel()->data(right).toInt();
+ if (leftIndex < 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;
+ }
+ } else {
+ return leftIndex < rightIndex;
+ }
+}
+
+
+bool DownloadListSortProxy::filterAcceptsRow(int source_row, const QModelIndex&) const
+{
+ if (m_CurrentFilter.length() == 0) {
+ return true;
+ } else if (source_row < m_Manager->numTotalDownloads()) {
+ return m_Manager->getFileName(source_row).contains(m_CurrentFilter, Qt::CaseInsensitive);
+ } else {
+ return false;
+ }
+}
diff --git a/src/downloadlistsortproxy.h b/src/downloadlistsortproxy.h index 36c72609..e2c1bfb6 100644 --- a/src/downloadlistsortproxy.h +++ b/src/downloadlistsortproxy.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#ifndef DOWNLOADLISTSORTPROXY_H
#define DOWNLOADLISTSORTPROXY_H
@@ -29,28 +29,28 @@ class DownloadListSortProxy : public QSortFilterProxyModel {
Q_OBJECT
public:
- - explicit DownloadListSortProxy(const DownloadManager *manager, QObject *parent = 0); - -public slots: - - void updateFilter(const QString &filter); - -protected: - - bool lessThan(const QModelIndex &left, const QModelIndex &right) const; - bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; - -signals: - +
+ explicit DownloadListSortProxy(const DownloadManager *manager, QObject *parent = 0);
+
+public slots:
+
+ void updateFilter(const QString &filter);
+
+protected:
+
+ bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
+ bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const;
+
+signals:
+
public slots:
-private: - - const DownloadManager *m_Manager; - QString m_CurrentFilter; - - -}; - -#endif // DOWNLOADLISTSORTPROXY_H +private:
+
+ const DownloadManager *m_Manager;
+ QString m_CurrentFilter;
+
+
+};
+
+#endif // DOWNLOADLISTSORTPROXY_H
diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index 953cdacd..9d276de9 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -1,351 +1,351 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "downloadlistwidget.h" -#include "ui_downloadlistwidget.h" -#include "downloadmanager.h" -#include <QPainter> -#include <QMouseEvent> -#include <QMenu> -#include <QMessageBox> -#include <QSortFilterProxyModel> - - -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<QLabel*>("nameLabel"); - m_SizeLabel = m_ItemWidget->findChild<QLabel*>("sizeLabel"); - m_Progress = m_ItemWidget->findChild<QProgressBar*>("downloadProgress"); - m_InstallLabel = m_ItemWidget->findChild<QLabel*>("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); -} - - -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::paintPendingDownload(int downloadIndex) const -{ - std::pair<int, int> 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)); -#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)); -#else - 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)); -#else - 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)); -#else - 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("<img src=\":/MO/gui/warning_16\" /> " + 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; - } - - m_ItemWidget->resize(QSize(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2), option.rect.height())); - - 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 (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); -#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()); - } -} - -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::issueDelete() -{ - emit removeDownload(m_ContextRow, true); -} - -void DownloadListWidgetDelegate::issueRemoveFromView() -{ - emit removeDownload(m_ContextRow, false); -} - -void DownloadListWidgetDelegate::issueRestoreToView() -{ - emit restoreDownload(m_ContextRow); -} - -void DownloadListWidgetDelegate::issueCancel() -{ - emit cancelDownload(m_ContextRow); -} - -void DownloadListWidgetDelegate::issuePause() -{ - emit pauseDownload(m_ContextRow); -} - -void DownloadListWidgetDelegate::issueResume() -{ - emit resumeDownload(m_ContextRow); -} - -void DownloadListWidgetDelegate::issueDeleteAll() -{ - if (QMessageBox::question(NULL, 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(NULL, 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(NULL, 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(NULL, 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<QSortFilterProxyModel*>(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<QMouseEvent*>(event); - if (mouseEvent->button() == Qt::RightButton) { - QMenu menu(m_View); - bool hidden = false; - m_ContextRow = qobject_cast<QSortFilterProxyModel*>(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("Remove from View"), 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("Remove Installed..."), this, SLOT(issueRemoveFromViewCompleted())); - menu.addAction(tr("Remove All..."), this, SLOT(issueRemoveFromViewAll())); - } - menu.exec(mouseEvent->globalPos()); - - event->accept(); - return true; - } - } - } catch (const std::exception &e) { - qCritical("failed to handle editor event: %s", e.what()); - } - return QItemDelegate::editorEvent(event, model, option, index); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "downloadlistwidget.h"
+#include "ui_downloadlistwidget.h"
+#include "downloadmanager.h"
+#include <QPainter>
+#include <QMouseEvent>
+#include <QMenu>
+#include <QMessageBox>
+#include <QSortFilterProxyModel>
+
+
+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<QLabel*>("nameLabel");
+ m_SizeLabel = m_ItemWidget->findChild<QLabel*>("sizeLabel");
+ m_Progress = m_ItemWidget->findChild<QProgressBar*>("downloadProgress");
+ m_InstallLabel = m_ItemWidget->findChild<QLabel*>("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);
+}
+
+
+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::paintPendingDownload(int downloadIndex) const
+{
+ std::pair<int, int> 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));
+#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));
+#else
+ 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));
+#else
+ 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));
+#else
+ 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("<img src=\":/MO/gui/warning_16\" /> " + 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;
+ }
+
+ m_ItemWidget->resize(QSize(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2), option.rect.height()));
+
+ 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 (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);
+#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());
+ }
+}
+
+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::issueDelete()
+{
+ emit removeDownload(m_ContextRow, true);
+}
+
+void DownloadListWidgetDelegate::issueRemoveFromView()
+{
+ emit removeDownload(m_ContextRow, false);
+}
+
+void DownloadListWidgetDelegate::issueRestoreToView()
+{
+ emit restoreDownload(m_ContextRow);
+}
+
+void DownloadListWidgetDelegate::issueCancel()
+{
+ emit cancelDownload(m_ContextRow);
+}
+
+void DownloadListWidgetDelegate::issuePause()
+{
+ emit pauseDownload(m_ContextRow);
+}
+
+void DownloadListWidgetDelegate::issueResume()
+{
+ emit resumeDownload(m_ContextRow);
+}
+
+void DownloadListWidgetDelegate::issueDeleteAll()
+{
+ if (QMessageBox::question(NULL, 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(NULL, 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(NULL, 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(NULL, 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<QSortFilterProxyModel*>(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<QMouseEvent*>(event);
+ if (mouseEvent->button() == Qt::RightButton) {
+ QMenu menu(m_View);
+ bool hidden = false;
+ m_ContextRow = qobject_cast<QSortFilterProxyModel*>(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("Remove from View"), 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("Remove Installed..."), this, SLOT(issueRemoveFromViewCompleted()));
+ menu.addAction(tr("Remove All..."), this, SLOT(issueRemoveFromViewAll()));
+ }
+ menu.exec(mouseEvent->globalPos());
+
+ event->accept();
+ return true;
+ }
+ }
+ } 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 fa7cc845..a8e702e3 100644 --- a/src/downloadlistwidget.h +++ b/src/downloadlistwidget.h @@ -1,121 +1,121 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef DOWNLOADLISTWIDGET_H -#define DOWNLOADLISTWIDGET_H - -#include <QWidget> -#include <QItemDelegate> -#include <QLabel> -#include <QProgressBar> -#include <QTreeView> - -#include "downloadmanager.h" - -namespace Ui { - class DownloadListWidget; -} - -class DownloadListWidget : public QWidget -{ - Q_OBJECT - -public: - explicit DownloadListWidget(QWidget *parent = 0); - ~DownloadListWidget(); - - -private: - Ui::DownloadListWidget *ui; -}; - -class DownloadManager; - -class DownloadListWidgetDelegate : public QItemDelegate -{ - - 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; - - 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); - -protected: - - bool editorEvent(QEvent *event, QAbstractItemModel *model, - const QStyleOptionViewItem &option, const QModelIndex &index); - -private: - - 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); - -private: - - DownloadListWidget *m_ItemWidget; - DownloadManager *m_Manager; - - bool m_MetaDisplay; - - QLabel *m_NameLabel; - QLabel *m_SizeLabel; - QProgressBar *m_Progress; - QLabel *m_InstallLabel; - int m_ContextRow; - - QTreeView *m_View; - - mutable QMap<int, QPixmap> m_Cache; -}; - -#endif // DOWNLOADLISTWIDGET_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef DOWNLOADLISTWIDGET_H
+#define DOWNLOADLISTWIDGET_H
+
+#include <QWidget>
+#include <QItemDelegate>
+#include <QLabel>
+#include <QProgressBar>
+#include <QTreeView>
+
+#include "downloadmanager.h"
+
+namespace Ui {
+ class DownloadListWidget;
+}
+
+class DownloadListWidget : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit DownloadListWidget(QWidget *parent = 0);
+ ~DownloadListWidget();
+
+
+private:
+ Ui::DownloadListWidget *ui;
+};
+
+class DownloadManager;
+
+class DownloadListWidgetDelegate : public QItemDelegate
+{
+
+ 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;
+
+ 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);
+
+protected:
+
+ bool editorEvent(QEvent *event, QAbstractItemModel *model,
+ const QStyleOptionViewItem &option, const QModelIndex &index);
+
+private:
+
+ 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);
+
+private:
+
+ DownloadListWidget *m_ItemWidget;
+ DownloadManager *m_Manager;
+
+ bool m_MetaDisplay;
+
+ QLabel *m_NameLabel;
+ QLabel *m_SizeLabel;
+ QProgressBar *m_Progress;
+ QLabel *m_InstallLabel;
+ int m_ContextRow;
+
+ QTreeView *m_View;
+
+ mutable QMap<int, QPixmap> m_Cache;
+};
+
+#endif // DOWNLOADLISTWIDGET_H
diff --git a/src/downloadlistwidgetcompact.cpp b/src/downloadlistwidgetcompact.cpp index 818d339b..21a690ab 100644 --- a/src/downloadlistwidgetcompact.cpp +++ b/src/downloadlistwidgetcompact.cpp @@ -1,341 +1,341 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "downloadlistwidgetcompact.h" -#include "ui_downloadlistwidgetcompact.h" -#include <QPainter> -#include <QMouseEvent> -#include <QMenu> -#include <QMessageBox> -#include <QSortFilterProxyModel> -#include "downloadmanager.h" - - -DownloadListWidgetCompact::DownloadListWidgetCompact(QWidget *parent) : - QWidget(parent), - ui(new Ui::DownloadListWidgetCompact) -{ - ui->setupUi(this); -} - -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<QLabel*>("nameLabel"); - m_SizeLabel = m_ItemWidget->findChild<QLabel*>("sizeLabel"); - m_Progress = m_ItemWidget->findChild<QProgressBar*>("downloadProgress"); - m_DoneLabel = m_ItemWidget->findChild<QLabel*>("doneLabel"); - - 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))); -} - - -DownloadListWidgetCompactDelegate::~DownloadListWidgetCompactDelegate() -{ - delete m_ItemWidget; -} - - -void DownloadListWidgetCompactDelegate::stateChanged(int row,DownloadManager::DownloadState) -{ - m_Cache.remove(row); -} - - -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::paintPendingDownload(int downloadIndex) const -{ - std::pair<int, int> nexusids = m_Manager->getPendingDownload(downloadIndex); - m_NameLabel->setText(tr("< mod %1 file %2 >").arg(nexusids.first).arg(nexusids.second)); - if (m_SizeLabel != NULL) { - 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 != NULL) && (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(tr("Paused")); - m_DoneLabel->setForegroundRole(QPalette::Link); - } else if (state == DownloadManager::STATE_FETCHINGMODINFO) { - m_DoneLabel->setText(tr("Fetching Info 1")); - } else if (state == DownloadManager::STATE_FETCHINGFILEINFO) { - m_DoneLabel->setText(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(tr("Installed")); - m_DoneLabel->setForegroundRole(QPalette::Mid); - } else if (state == DownloadManager::STATE_UNINSTALLED) { - m_DoneLabel->setText(tr("Uninstalled")); - m_DoneLabel->setForegroundRole(QPalette::Dark); - } else { - m_DoneLabel->setText(tr("Done")); - m_DoneLabel->setForegroundRole(QPalette::WindowText); - } - if (m_Manager->isInfoIncomplete(downloadIndex)) { - m_NameLabel->setText("<img src=\":/MO/gui/warning_16\"/> " + 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 -{ -#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; - } - - 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); - } - -#pragma message("caching disabled because changes in the list (including resorting) doesn't work correctly") - if (false) { -// if (state >= DownloadManager::STATE_READY) { -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - QPixmap cache = m_ItemWidget->grab(); -#else - 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()); - } -} - -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::issueDelete() -{ - emit removeDownload(m_ContextIndex.row(), true); -} - -void DownloadListWidgetCompactDelegate::issueRemoveFromView() -{ - emit removeDownload(m_ContextIndex.row(), false); -} - -void DownloadListWidgetCompactDelegate::issueRestoreToView() -{ - emit restoreDownload(m_ContextIndex.row()); -} - -void DownloadListWidgetCompactDelegate::issueCancel() -{ - emit cancelDownload(m_ContextIndex.row()); -} - -void DownloadListWidgetCompactDelegate::issuePause() -{ - emit pauseDownload(m_ContextIndex.row()); -} - -void DownloadListWidgetCompactDelegate::issueResume() -{ - emit resumeDownload(m_ContextIndex.row()); -} - -void DownloadListWidgetCompactDelegate::issueDeleteAll() -{ - if (QMessageBox::question(NULL, 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::issueDeleteCompleted() -{ - if (QMessageBox::question(NULL, 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::issueRemoveFromViewAll() -{ - if (QMessageBox::question(NULL, 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(NULL, 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<QSortFilterProxyModel*>(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<QMouseEvent*>(event); - if (mouseEvent->button() == Qt::RightButton) { - QMenu menu; - bool hidden = false; - m_ContextIndex = qobject_cast<QSortFilterProxyModel*>(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("Remove from View"), 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("Remove Installed..."), this, SLOT(issueRemoveFromViewCompleted())); - menu.addAction(tr("Remove 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); -} - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "downloadlistwidgetcompact.h"
+#include "ui_downloadlistwidgetcompact.h"
+#include <QPainter>
+#include <QMouseEvent>
+#include <QMenu>
+#include <QMessageBox>
+#include <QSortFilterProxyModel>
+#include "downloadmanager.h"
+
+
+DownloadListWidgetCompact::DownloadListWidgetCompact(QWidget *parent) :
+ QWidget(parent),
+ ui(new Ui::DownloadListWidgetCompact)
+{
+ ui->setupUi(this);
+}
+
+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<QLabel*>("nameLabel");
+ m_SizeLabel = m_ItemWidget->findChild<QLabel*>("sizeLabel");
+ m_Progress = m_ItemWidget->findChild<QProgressBar*>("downloadProgress");
+ m_DoneLabel = m_ItemWidget->findChild<QLabel*>("doneLabel");
+
+ 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)));
+}
+
+
+DownloadListWidgetCompactDelegate::~DownloadListWidgetCompactDelegate()
+{
+ delete m_ItemWidget;
+}
+
+
+void DownloadListWidgetCompactDelegate::stateChanged(int row,DownloadManager::DownloadState)
+{
+ m_Cache.remove(row);
+}
+
+
+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::paintPendingDownload(int downloadIndex) const
+{
+ std::pair<int, int> nexusids = m_Manager->getPendingDownload(downloadIndex);
+ m_NameLabel->setText(tr("< mod %1 file %2 >").arg(nexusids.first).arg(nexusids.second));
+ if (m_SizeLabel != NULL) {
+ 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 != NULL) && (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(tr("Paused"));
+ m_DoneLabel->setForegroundRole(QPalette::Link);
+ } else if (state == DownloadManager::STATE_FETCHINGMODINFO) {
+ m_DoneLabel->setText(tr("Fetching Info 1"));
+ } else if (state == DownloadManager::STATE_FETCHINGFILEINFO) {
+ m_DoneLabel->setText(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(tr("Installed"));
+ m_DoneLabel->setForegroundRole(QPalette::Mid);
+ } else if (state == DownloadManager::STATE_UNINSTALLED) {
+ m_DoneLabel->setText(tr("Uninstalled"));
+ m_DoneLabel->setForegroundRole(QPalette::Dark);
+ } else {
+ m_DoneLabel->setText(tr("Done"));
+ m_DoneLabel->setForegroundRole(QPalette::WindowText);
+ }
+ if (m_Manager->isInfoIncomplete(downloadIndex)) {
+ m_NameLabel->setText("<img src=\":/MO/gui/warning_16\"/> " + 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
+{
+#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;
+ }
+
+ 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);
+ }
+
+#pragma message("caching disabled because changes in the list (including resorting) doesn't work correctly")
+ if (false) {
+// if (state >= DownloadManager::STATE_READY) {
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+ QPixmap cache = m_ItemWidget->grab();
+#else
+ 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());
+ }
+}
+
+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::issueDelete()
+{
+ emit removeDownload(m_ContextIndex.row(), true);
+}
+
+void DownloadListWidgetCompactDelegate::issueRemoveFromView()
+{
+ emit removeDownload(m_ContextIndex.row(), false);
+}
+
+void DownloadListWidgetCompactDelegate::issueRestoreToView()
+{
+ emit restoreDownload(m_ContextIndex.row());
+}
+
+void DownloadListWidgetCompactDelegate::issueCancel()
+{
+ emit cancelDownload(m_ContextIndex.row());
+}
+
+void DownloadListWidgetCompactDelegate::issuePause()
+{
+ emit pauseDownload(m_ContextIndex.row());
+}
+
+void DownloadListWidgetCompactDelegate::issueResume()
+{
+ emit resumeDownload(m_ContextIndex.row());
+}
+
+void DownloadListWidgetCompactDelegate::issueDeleteAll()
+{
+ if (QMessageBox::question(NULL, 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::issueDeleteCompleted()
+{
+ if (QMessageBox::question(NULL, 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::issueRemoveFromViewAll()
+{
+ if (QMessageBox::question(NULL, 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(NULL, 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<QSortFilterProxyModel*>(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<QMouseEvent*>(event);
+ if (mouseEvent->button() == Qt::RightButton) {
+ QMenu menu;
+ bool hidden = false;
+ m_ContextIndex = qobject_cast<QSortFilterProxyModel*>(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("Remove from View"), 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("Remove Installed..."), this, SLOT(issueRemoveFromViewCompleted()));
+ menu.addAction(tr("Remove 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);
+}
+
diff --git a/src/downloadlistwidgetcompact.h b/src/downloadlistwidgetcompact.h index c3cd5c11..bf855d5f 100644 --- a/src/downloadlistwidgetcompact.h +++ b/src/downloadlistwidgetcompact.h @@ -1,122 +1,122 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef DOWNLOADLISTWIDGETCOMPACT_H -#define DOWNLOADLISTWIDGETCOMPACT_H - -#include <QWidget> -#include <QItemDelegate> -#include <QLabel> -#include <QProgressBar> -#include <QTreeView> -#include "downloadmanager.h" - - -namespace Ui { -class DownloadListWidgetCompact; -} - -class DownloadListWidgetCompact : public QWidget -{ - Q_OBJECT - -public: - explicit DownloadListWidgetCompact(QWidget *parent = 0); - ~DownloadListWidgetCompact(); - -private: - Ui::DownloadListWidgetCompact *ui; - int m_ContextRow; -}; - -class DownloadManager; - -class DownloadListWidgetCompactDelegate : public QItemDelegate -{ - - 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; - -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); - -protected: - - 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 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; - - QLabel *m_NameLabel; - QLabel *m_SizeLabel; - QProgressBar *m_Progress; - QLabel *m_DoneLabel; - - QModelIndex m_ContextIndex; - - QTreeView *m_View; - - mutable QMap<int, QPixmap> m_Cache; - -}; - -#endif // DOWNLOADLISTWIDGETCOMPACT_H - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef DOWNLOADLISTWIDGETCOMPACT_H
+#define DOWNLOADLISTWIDGETCOMPACT_H
+
+#include <QWidget>
+#include <QItemDelegate>
+#include <QLabel>
+#include <QProgressBar>
+#include <QTreeView>
+#include "downloadmanager.h"
+
+
+namespace Ui {
+class DownloadListWidgetCompact;
+}
+
+class DownloadListWidgetCompact : public QWidget
+{
+ Q_OBJECT
+
+public:
+ explicit DownloadListWidgetCompact(QWidget *parent = 0);
+ ~DownloadListWidgetCompact();
+
+private:
+ Ui::DownloadListWidgetCompact *ui;
+ int m_ContextRow;
+};
+
+class DownloadManager;
+
+class DownloadListWidgetCompactDelegate : public QItemDelegate
+{
+
+ 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;
+
+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);
+
+protected:
+
+ 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 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;
+
+ QLabel *m_NameLabel;
+ QLabel *m_SizeLabel;
+ QProgressBar *m_Progress;
+ QLabel *m_DoneLabel;
+
+ QModelIndex m_ContextIndex;
+
+ QTreeView *m_View;
+
+ mutable QMap<int, QPixmap> m_Cache;
+
+};
+
+#endif // DOWNLOADLISTWIDGETCOMPACT_H
+
diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 2d2b85c8..391ac7b9 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -1,1471 +1,1471 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "downloadmanager.h" -#include "report.h" -#include "nxmurl.h" -#include <gameinfo.h> -#include <nxmurl.h> -#include <taskprogressmanager.h> -#include "utility.h" -#include "json.h" -#include "selectiondialog.h" -#include "bbcode.h" -#include <utility.h> -#include <QTimer> -#include <QFileInfo> -#include <QRegExp> -#include <QDirIterator> -#include <QInputDialog> -#include <QMessageBox> -#include <QCoreApplication> -#include <boost/bind.hpp> -#include <regex> - - -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 NULL; - } 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() - 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<DownloadInfo*>::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter) { - delete *iter; - } - m_ActiveDownloads.clear(); -} - - -bool DownloadManager::downloadsInProgress() -{ - for (QVector<DownloadInfo*>::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 (!done) { - ::Sleep(100); - } - } -} - - -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); -} - - -void DownloadManager::setPreferredServers(const std::map<QString, int> &preferredServers) -{ - m_PreferredServers = preferredServers; -} - - -void DownloadManager::setSupportedExtensions(const QStringList &extensions) -{ - m_SupportedExtensions = extensions; - refreshList(); -} - -void DownloadManager::setShowHidden(bool showHidden) -{ - m_ShowHidden = showHidden; - refreshList(); -} - -void DownloadManager::refreshList() -{ - try { - int downloadsBefore = m_ActiveDownloads.size(); - - // remove finished downloads - for (QVector<DownloadInfo*>::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<DownloadInfo*>::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 != NULL) { - m_ActiveDownloads.push_front(info); - } - } - - 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"; - } - - QNetworkRequest request(URLs.first()); - return addDownload(m_NexusInterface->getAccessManager()->get(request), URLs, fileName, modID, fileID, fileInfo); -} - - -bool DownloadManager::addDownload(QNetworkReply *reply, const ModRepositoryFileInfo *fileInfo) -{ - QString fileName = getFileNameFromNetworkReply(reply); - if (fileName.isEmpty()) { - fileName = "unknown"; - } - - return addDownload(reply, QStringList(reply->url().toString()), fileName, fileInfo->modID, fileInfo->fileID, fileInfo); -} - - -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); - - if (!dispoName.isEmpty()) { - baseName = dispoName; - } - } - - if (QFile::exists(m_OutputDirectory + "/" + baseName) && - (QMessageBox::question(NULL, 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); - - startDownload(reply, newDownload, false); - -// emit update(-1); - return true; -} - - -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); -} - - -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()); - } - - QIODevice::OpenMode mode = QIODevice::WriteOnly; - if (resume) { - mode |= QIODevice::Append; - } - - newDownload->m_StartTime.start(); - createMetaFile(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; - } - - 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); - - emit aboutToUpdate(); - m_ActiveDownloads.append(newDownload); - - emit update(-1); - emit downloadAdded(); - - if (reply->isFinished()) { - // it's possible the download has already finished before this function ran - downloadFinished(); - } - } -} - - -void DownloadManager::addNXMDownload(const QString &url) -{ - NXMUrl nxmInfo(url); - - QString managedGame = ToQString(MOShared::GameInfo::instance().getGameShortName()); - 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(NULL, 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())); - - emit update(-1); - emit downloadAdded(); - m_RequestIDs.insert(m_NexusInterface->requestFileInfo(nxmInfo.modId(), nxmInfo.fileId(), this, nxmInfo.fileId(), "")); -} - - -void DownloadManager::removeFile(int index, bool deleteFile) -{ - if (index >= m_ActiveDownloads.size()) { - throw MyException(tr("invalid index")); - } - - 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 ((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); - } -} - -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; - - } - -private: - DownloadManager *m_Manager; -}; - - -bool DownloadManager::ByName(int LHS, int RHS) -{ - return m_ActiveDownloads[LHS]->m_FileName < m_ActiveDownloads[RHS]->m_FileName; -} - - -void DownloadManager::refreshAlphabeticalTranslation() -{ - m_AlphabeticalTranslation.clear(); - int pos = 0; - for (QVector<DownloadInfo*>::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("invalid index")); - } - - DownloadInfo *download = m_ActiveDownloads.at(index); - download->m_Hidden = false; - - QString filePath = m_OutputDirectory + "/" + download->m_FileName; - - QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat); - metaSettings.setValue("removed", false); -} - - -void DownloadManager::removeDownload(int index, bool deleteFile) -{ - try { - emit aboutToUpdate(); - - if (index < 0) { - DownloadState minState = index < -1 ? STATE_INSTALLED : STATE_READY; - index = 0; - for (QVector<DownloadInfo*>::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("invalid 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()); - } -} - - -void DownloadManager::cancelDownload(int index) -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - reportError(tr("invalid index %1").arg(index)); - return; - } - - if (m_ActiveDownloads.at(index)->m_State == STATE_DOWNLOADING) { - setState(m_ActiveDownloads.at(index), STATE_CANCELING); - } -} - - -void DownloadManager::pauseDownload(int index) -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - reportError(tr("invalid index %1").arg(index)); - return; - } - - DownloadInfo *info = m_ActiveDownloads.at(index); - - if (info->m_State == STATE_DOWNLOADING) { - if (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); - } -} - -void DownloadManager::resumeDownload(int index) -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - reportError(tr("invalid 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("invalid 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(info->currentURL()); - 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 NULL; - } -} - - -void DownloadManager::queryInfo(int index) -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - reportError(tr("invalid 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; - } - - if (info->m_State < DownloadManager::STATE_READY) { - // UI shouldn't allow this - return; - } - - if (info->m_FileInfo->modID == 0UL) { - QString fileName = getFileName(index); - QString ignore; - NexusInterface::interpretNexusFileName(fileName, ignore, info->m_FileInfo->modID, true); - if (info->m_FileInfo->modID < 0) { - QString modIDString; - while (modIDString.isEmpty()) { - modIDString = QInputDialog::getText(NULL, tr("Please enter the nexus mod id"), tr("Mod ID:"), QLineEdit::Normal, - QString(), NULL, 0, Qt::ImhFormattedNumbersOnly); - if (modIDString.isNull()) { - // canceled - return; - } else if (modIDString.contains(QRegExp("[^0-9]"))) { - qDebug("illegal character in mod-id"); - modIDString.clear(); - } - } - info->m_FileInfo->modID = modIDString.toInt(NULL, 10); - } - } - info->m_ReQueried = true; - setState(info, STATE_FETCHINGMODINFO); -} - - -int DownloadManager::numTotalDownloads() const -{ - return m_ActiveDownloads.size(); -} - -int DownloadManager::numPendingDownloads() const -{ - return m_PendingDownloads.size(); -} - -std::pair<int, int> DownloadManager::getPendingDownload(int index) -{ - if ((index < 0) || (index >= m_PendingDownloads.size())) { - throw MyException(tr("invalid index")); - } - - return m_PendingDownloads.at(index); -} - -QString DownloadManager::getFilePath(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid 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"); - } -} - -QString DownloadManager::getDisplayName(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - - DownloadInfo *info = m_ActiveDownloads.at(index); - - if (!info->m_FileInfo->name.isEmpty()) { - return QString("%1 (%2, v%3)").arg(info->m_FileInfo->name) - .arg(getFileTypeString(info->m_FileInfo->fileCategory)) - .arg(info->m_FileInfo->version.displayString()); - } else { - return info->m_FileName; - } -} - -QString DownloadManager::getFileName(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - - return m_ActiveDownloads.at(index)->m_FileName; -} - -QDateTime DownloadManager::getFileTime(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - - DownloadInfo *info = m_ActiveDownloads.at(index); - if (!info->m_Created.isValid()) { - info->m_Created = QFileInfo(info->m_Output).created(); - } - - return info->m_Created; -} - -qint64 DownloadManager::getFileSize(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid index")); - } - - return m_ActiveDownloads.at(index)->m_TotalSize; -} - - -int DownloadManager::getProgress(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid 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("invalid index")); - } - - return m_ActiveDownloads.at(index)->m_State; -} - - -bool DownloadManager::isInfoIncomplete(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid 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) || !info->m_FileInfo->version.isValid(); -} - - -int DownloadManager::getModID(int index) const -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid 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("invalid 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("invalid index")); - } - - return m_ActiveDownloads.at(index)->m_FileInfo; -} - - -void DownloadManager::markInstalled(int index) -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid 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); -} - - -void DownloadManager::markUninstalled(int index) -{ - if ((index < 0) || (index >= m_ActiveDownloads.size())) { - throw MyException(tr("invalid 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); -} - - -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); - } - return fullPath; -} - - -QString DownloadManager::getFileNameFromNetworkReply(QNetworkReply *reply) -{ - if (reply->hasRawHeader("Content-Disposition")) { - std::tr1::regex exp("filename=\"(.*)\""); - - std::tr1::cmatch result; - if (std::tr1::regex_search(reply->rawHeader("Content-Disposition").constData(), result, exp)) { - return QString::fromUtf8(result.str(1).c_str()); - } - } - - 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; - } - } - info->m_State = state; - switch (state) { - case STATE_PAUSED: - case STATE_ERROR: { - info->m_Reply->abort(); - } break; - case STATE_CANCELED: { - info->m_Reply->abort(); - } break; - case STATE_FETCHINGMODINFO: { - 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())); - } break; - case STATE_READY: { - createMetaFile(info); - emit downloadComplete(row); - } break; - 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 != NULL) { - *index = i; - } - return m_ActiveDownloads[i]; - } - } - return NULL; -} - - -void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) -{ - if (bytesTotal == 0) { - return; - } - int index = 0; - try { - DownloadInfo *info = findDownload(this->sender(), &index); - if (info != NULL) { - 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::downloadReadyRead() -{ - try { - DownloadInfo *info = findDownload(this->sender()); - if (info != NULL) { - 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<int>::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 == NULL) return; - info->m_FileInfo->categoryID = result["category_id"].toInt(); - info->m_FileInfo->modName = result["name"].toString().trimmed(); - 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(); - } -} - - -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<int>::iterator idIter = m_RequestIDs.find(requestID); - if (idIter == m_RequestIDs.end()) { - return; - } else { - m_RequestIDs.erase(idIter); - } - - DownloadInfo *info = downloadInfoByID(userData.toInt()); - if (info == NULL) return; - - QVariantList result = resultData.toList(); - - // MO sometimes prepends <digit>_ 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; - - foreach(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; - } - 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 { - SelectionDialog selection(tr("No file on Nexus matches the selected file by name. Please manually choose the correct one.")); - foreach(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); - } - } - - setState(info, STATE_READY); -} - - -void DownloadManager::nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID) -{ - std::set<int>::iterator idIter = m_RequestIDs.find(requestID); - if (idIter == m_RequestIDs.end()) { - return; - } else { - m_RequestIDs.erase(idIter); - } - - ModRepositoryFileInfo *info = new ModRepositoryFileInfo(); - - QVariantMap result = resultData.toMap(); - info->name = result["name"].toString(); - qDebug("file info received for %s", qPrintable(info->name)); - 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()); - - info->repository = "Nexus"; - info->modID = modID; - info->fileID = fileID; - - QObject *test = info; - m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(modID, fileID, this, qVariantFromValue(test), QString())); -} - - -// sort function to sort by best download server -bool DownloadManager::ServerByPreference(const std::map<QString, int> &preferredServers, const QVariant &LHS, const QVariant &RHS) -{ - int LHSVal = 0; - int RHSVal = 0; - - QVariantMap LHSMap = LHS.toMap(); - QVariantMap RHSMap = RHS.toMap(); - - int LHSUsers = LHSMap["ConnectedUsers"].toInt(); - int RHSUsers = RHSMap["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 (LHSUsers == 0) { - LHSVal -= 500; - } else { - LHSVal -= LHSUsers; - } - if (RHSUsers == 0) { - RHSVal -= 500; - } else { - RHSVal -= RHSUsers; - } - - // user preference. This is a bit silly because the more servers on the preferred list the higher the boost - auto LHSPreference = preferredServers.find(LHSMap["Name"].toString()); - auto RHSPreference = preferredServers.find(RHSMap["Name"].toString()); - - if (LHSPreference != preferredServers.end()) { - LHSVal += 100 + LHSPreference->second * 20; - } - if (RHSPreference != preferredServers.end()) { - RHSVal += 100 + RHSPreference->second * 20; - } - - // premium isn't valued high because premium servers already get a massive boost for having few users online - if (LHSMap["IsPremium"].toBool()) LHSVal += 5; - if (RHSMap["IsPremium"].toBool()) RHSVal += 5; - - return RHSVal < LHSVal; -} - -int DownloadManager::startDownloadURLs(const QStringList &urls) -{ - addDownload(urls, -1, -1, nullptr); - 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(ToQString(MOShared::GameInfo::instance().getGameName())).arg(modID).arg(fileID)); - return newID; -} - -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; - } - } - return -1; -} - -void DownloadManager::nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID) -{ - std::set<int>::iterator idIter = m_RequestIDs.find(requestID); - if (idIter == m_RequestIDs.end()) { - return; - } else { - m_RequestIDs.erase(idIter); - } - - ModRepositoryFileInfo *info = qobject_cast<ModRepositoryFileInfo*>(qvariant_cast<QObject*>(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)); - - info->userData["downloadMap"] = resultList; - - QStringList URLs; - - 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<int>::iterator idIter = m_RequestIDs.find(requestID); - if (idIter == m_RequestIDs.end()) { - return; - } else { - m_RequestIDs.erase(idIter); - } - - int index = 0; - - for (QVector<DownloadInfo*>::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 != NULL) { - 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); - } - } - - if (info->m_State == STATE_CANCELING) { - setState(info, STATE_CANCELED); - } else if (info->m_State == STATE_PAUSING) { - 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); - } - - 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); - } -} - - -void DownloadManager::downloadError(QNetworkReply::NetworkError error) -{ - if (error != QNetworkReply::OperationCanceledError) { - qWarning("Download error occured: %d", error); - } -} - - -void DownloadManager::metaDataChanged() -{ - int index = 0; - - DownloadInfo *info = findDownload(this->sender(), &index); - if (info != NULL) { - 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(); -} - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "downloadmanager.h"
+#include "report.h"
+#include "nxmurl.h"
+#include <gameinfo.h>
+#include <nxmurl.h>
+#include <taskprogressmanager.h>
+#include "utility.h"
+#include "json.h"
+#include "selectiondialog.h"
+#include "bbcode.h"
+#include <utility.h>
+#include <QTimer>
+#include <QFileInfo>
+#include <QRegExp>
+#include <QDirIterator>
+#include <QInputDialog>
+#include <QMessageBox>
+#include <QCoreApplication>
+#include <boost/bind.hpp>
+#include <regex>
+
+
+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 NULL;
+ } 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() - 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<DownloadInfo*>::iterator iter = m_ActiveDownloads.begin(); iter != m_ActiveDownloads.end(); ++iter) {
+ delete *iter;
+ }
+ m_ActiveDownloads.clear();
+}
+
+
+bool DownloadManager::downloadsInProgress()
+{
+ for (QVector<DownloadInfo*>::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 (!done) {
+ ::Sleep(100);
+ }
+ }
+}
+
+
+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);
+}
+
+
+void DownloadManager::setPreferredServers(const std::map<QString, int> &preferredServers)
+{
+ m_PreferredServers = preferredServers;
+}
+
+
+void DownloadManager::setSupportedExtensions(const QStringList &extensions)
+{
+ m_SupportedExtensions = extensions;
+ refreshList();
+}
+
+void DownloadManager::setShowHidden(bool showHidden)
+{
+ m_ShowHidden = showHidden;
+ refreshList();
+}
+
+void DownloadManager::refreshList()
+{
+ try {
+ int downloadsBefore = m_ActiveDownloads.size();
+
+ // remove finished downloads
+ for (QVector<DownloadInfo*>::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<DownloadInfo*>::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 != NULL) {
+ m_ActiveDownloads.push_front(info);
+ }
+ }
+
+ 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";
+ }
+
+ QNetworkRequest request(URLs.first());
+ return addDownload(m_NexusInterface->getAccessManager()->get(request), URLs, fileName, modID, fileID, fileInfo);
+}
+
+
+bool DownloadManager::addDownload(QNetworkReply *reply, const ModRepositoryFileInfo *fileInfo)
+{
+ QString fileName = getFileNameFromNetworkReply(reply);
+ if (fileName.isEmpty()) {
+ fileName = "unknown";
+ }
+
+ return addDownload(reply, QStringList(reply->url().toString()), fileName, fileInfo->modID, fileInfo->fileID, fileInfo);
+}
+
+
+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);
+
+ if (!dispoName.isEmpty()) {
+ baseName = dispoName;
+ }
+ }
+
+ if (QFile::exists(m_OutputDirectory + "/" + baseName) &&
+ (QMessageBox::question(NULL, 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);
+
+ startDownload(reply, newDownload, false);
+
+// emit update(-1);
+ return true;
+}
+
+
+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);
+}
+
+
+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());
+ }
+
+ QIODevice::OpenMode mode = QIODevice::WriteOnly;
+ if (resume) {
+ mode |= QIODevice::Append;
+ }
+
+ newDownload->m_StartTime.start();
+ createMetaFile(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;
+ }
+
+ 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);
+
+ emit aboutToUpdate();
+ m_ActiveDownloads.append(newDownload);
+
+ emit update(-1);
+ emit downloadAdded();
+
+ if (reply->isFinished()) {
+ // it's possible the download has already finished before this function ran
+ downloadFinished();
+ }
+ }
+}
+
+
+void DownloadManager::addNXMDownload(const QString &url)
+{
+ NXMUrl nxmInfo(url);
+
+ QString managedGame = ToQString(MOShared::GameInfo::instance().getGameShortName());
+ 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(NULL, 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()));
+
+ emit update(-1);
+ emit downloadAdded();
+ m_RequestIDs.insert(m_NexusInterface->requestFileInfo(nxmInfo.modId(), nxmInfo.fileId(), this, nxmInfo.fileId(), ""));
+}
+
+
+void DownloadManager::removeFile(int index, bool deleteFile)
+{
+ if (index >= m_ActiveDownloads.size()) {
+ throw MyException(tr("invalid index"));
+ }
+
+ 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 ((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);
+ }
+}
+
+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;
+
+ }
+
+private:
+ DownloadManager *m_Manager;
+};
+
+
+bool DownloadManager::ByName(int LHS, int RHS)
+{
+ return m_ActiveDownloads[LHS]->m_FileName < m_ActiveDownloads[RHS]->m_FileName;
+}
+
+
+void DownloadManager::refreshAlphabeticalTranslation()
+{
+ m_AlphabeticalTranslation.clear();
+ int pos = 0;
+ for (QVector<DownloadInfo*>::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("invalid index"));
+ }
+
+ DownloadInfo *download = m_ActiveDownloads.at(index);
+ download->m_Hidden = false;
+
+ QString filePath = m_OutputDirectory + "/" + download->m_FileName;
+
+ QSettings metaSettings(filePath.append(".meta"), QSettings::IniFormat);
+ metaSettings.setValue("removed", false);
+}
+
+
+void DownloadManager::removeDownload(int index, bool deleteFile)
+{
+ try {
+ emit aboutToUpdate();
+
+ if (index < 0) {
+ DownloadState minState = index < -1 ? STATE_INSTALLED : STATE_READY;
+ index = 0;
+ for (QVector<DownloadInfo*>::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("invalid 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());
+ }
+}
+
+
+void DownloadManager::cancelDownload(int index)
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ reportError(tr("invalid index %1").arg(index));
+ return;
+ }
+
+ if (m_ActiveDownloads.at(index)->m_State == STATE_DOWNLOADING) {
+ setState(m_ActiveDownloads.at(index), STATE_CANCELING);
+ }
+}
+
+
+void DownloadManager::pauseDownload(int index)
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ reportError(tr("invalid index %1").arg(index));
+ return;
+ }
+
+ DownloadInfo *info = m_ActiveDownloads.at(index);
+
+ if (info->m_State == STATE_DOWNLOADING) {
+ if (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);
+ }
+}
+
+void DownloadManager::resumeDownload(int index)
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ reportError(tr("invalid 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("invalid 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(info->currentURL());
+ 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 NULL;
+ }
+}
+
+
+void DownloadManager::queryInfo(int index)
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ reportError(tr("invalid 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;
+ }
+
+ if (info->m_State < DownloadManager::STATE_READY) {
+ // UI shouldn't allow this
+ return;
+ }
+
+ if (info->m_FileInfo->modID == 0UL) {
+ QString fileName = getFileName(index);
+ QString ignore;
+ NexusInterface::interpretNexusFileName(fileName, ignore, info->m_FileInfo->modID, true);
+ if (info->m_FileInfo->modID < 0) {
+ QString modIDString;
+ while (modIDString.isEmpty()) {
+ modIDString = QInputDialog::getText(NULL, tr("Please enter the nexus mod id"), tr("Mod ID:"), QLineEdit::Normal,
+ QString(), NULL, 0, Qt::ImhFormattedNumbersOnly);
+ if (modIDString.isNull()) {
+ // canceled
+ return;
+ } else if (modIDString.contains(QRegExp("[^0-9]"))) {
+ qDebug("illegal character in mod-id");
+ modIDString.clear();
+ }
+ }
+ info->m_FileInfo->modID = modIDString.toInt(NULL, 10);
+ }
+ }
+ info->m_ReQueried = true;
+ setState(info, STATE_FETCHINGMODINFO);
+}
+
+
+int DownloadManager::numTotalDownloads() const
+{
+ return m_ActiveDownloads.size();
+}
+
+int DownloadManager::numPendingDownloads() const
+{
+ return m_PendingDownloads.size();
+}
+
+std::pair<int, int> DownloadManager::getPendingDownload(int index)
+{
+ if ((index < 0) || (index >= m_PendingDownloads.size())) {
+ throw MyException(tr("invalid index"));
+ }
+
+ return m_PendingDownloads.at(index);
+}
+
+QString DownloadManager::getFilePath(int index) const
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ throw MyException(tr("invalid 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");
+ }
+}
+
+QString DownloadManager::getDisplayName(int index) const
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ throw MyException(tr("invalid index"));
+ }
+
+ DownloadInfo *info = m_ActiveDownloads.at(index);
+
+ if (!info->m_FileInfo->name.isEmpty()) {
+ return QString("%1 (%2, v%3)").arg(info->m_FileInfo->name)
+ .arg(getFileTypeString(info->m_FileInfo->fileCategory))
+ .arg(info->m_FileInfo->version.displayString());
+ } else {
+ return info->m_FileName;
+ }
+}
+
+QString DownloadManager::getFileName(int index) const
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ throw MyException(tr("invalid index"));
+ }
+
+ return m_ActiveDownloads.at(index)->m_FileName;
+}
+
+QDateTime DownloadManager::getFileTime(int index) const
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ throw MyException(tr("invalid index"));
+ }
+
+ DownloadInfo *info = m_ActiveDownloads.at(index);
+ if (!info->m_Created.isValid()) {
+ info->m_Created = QFileInfo(info->m_Output).created();
+ }
+
+ return info->m_Created;
+}
+
+qint64 DownloadManager::getFileSize(int index) const
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ throw MyException(tr("invalid index"));
+ }
+
+ return m_ActiveDownloads.at(index)->m_TotalSize;
+}
+
+
+int DownloadManager::getProgress(int index) const
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ throw MyException(tr("invalid 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("invalid index"));
+ }
+
+ return m_ActiveDownloads.at(index)->m_State;
+}
+
+
+bool DownloadManager::isInfoIncomplete(int index) const
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ throw MyException(tr("invalid 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) || !info->m_FileInfo->version.isValid();
+}
+
+
+int DownloadManager::getModID(int index) const
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ throw MyException(tr("invalid 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("invalid 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("invalid index"));
+ }
+
+ return m_ActiveDownloads.at(index)->m_FileInfo;
+}
+
+
+void DownloadManager::markInstalled(int index)
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ throw MyException(tr("invalid 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);
+}
+
+
+void DownloadManager::markUninstalled(int index)
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ throw MyException(tr("invalid 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);
+}
+
+
+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);
+ }
+ return fullPath;
+}
+
+
+QString DownloadManager::getFileNameFromNetworkReply(QNetworkReply *reply)
+{
+ if (reply->hasRawHeader("Content-Disposition")) {
+ std::tr1::regex exp("filename=\"(.*)\"");
+
+ std::tr1::cmatch result;
+ if (std::tr1::regex_search(reply->rawHeader("Content-Disposition").constData(), result, exp)) {
+ return QString::fromUtf8(result.str(1).c_str());
+ }
+ }
+
+ 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;
+ }
+ }
+ info->m_State = state;
+ switch (state) {
+ case STATE_PAUSED:
+ case STATE_ERROR: {
+ info->m_Reply->abort();
+ } break;
+ case STATE_CANCELED: {
+ info->m_Reply->abort();
+ } break;
+ case STATE_FETCHINGMODINFO: {
+ 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()));
+ } break;
+ case STATE_READY: {
+ createMetaFile(info);
+ emit downloadComplete(row);
+ } break;
+ 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 != NULL) {
+ *index = i;
+ }
+ return m_ActiveDownloads[i];
+ }
+ }
+ return NULL;
+}
+
+
+void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
+{
+ if (bytesTotal == 0) {
+ return;
+ }
+ int index = 0;
+ try {
+ DownloadInfo *info = findDownload(this->sender(), &index);
+ if (info != NULL) {
+ 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::downloadReadyRead()
+{
+ try {
+ DownloadInfo *info = findDownload(this->sender());
+ if (info != NULL) {
+ 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<int>::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 == NULL) return;
+ info->m_FileInfo->categoryID = result["category_id"].toInt();
+ info->m_FileInfo->modName = result["name"].toString().trimmed();
+ 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();
+ }
+}
+
+
+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<int>::iterator idIter = m_RequestIDs.find(requestID);
+ if (idIter == m_RequestIDs.end()) {
+ return;
+ } else {
+ m_RequestIDs.erase(idIter);
+ }
+
+ DownloadInfo *info = downloadInfoByID(userData.toInt());
+ if (info == NULL) return;
+
+ QVariantList result = resultData.toList();
+
+ // MO sometimes prepends <digit>_ 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;
+
+ foreach(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;
+ }
+ 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 {
+ SelectionDialog selection(tr("No file on Nexus matches the selected file by name. Please manually choose the correct one."));
+ foreach(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);
+ }
+ }
+
+ setState(info, STATE_READY);
+}
+
+
+void DownloadManager::nxmFileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID)
+{
+ std::set<int>::iterator idIter = m_RequestIDs.find(requestID);
+ if (idIter == m_RequestIDs.end()) {
+ return;
+ } else {
+ m_RequestIDs.erase(idIter);
+ }
+
+ ModRepositoryFileInfo *info = new ModRepositoryFileInfo();
+
+ QVariantMap result = resultData.toMap();
+ info->name = result["name"].toString();
+ qDebug("file info received for %s", qPrintable(info->name));
+ 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());
+
+ info->repository = "Nexus";
+ info->modID = modID;
+ info->fileID = fileID;
+
+ QObject *test = info;
+ m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(modID, fileID, this, qVariantFromValue(test), QString()));
+}
+
+
+// sort function to sort by best download server
+bool DownloadManager::ServerByPreference(const std::map<QString, int> &preferredServers, const QVariant &LHS, const QVariant &RHS)
+{
+ int LHSVal = 0;
+ int RHSVal = 0;
+
+ QVariantMap LHSMap = LHS.toMap();
+ QVariantMap RHSMap = RHS.toMap();
+
+ int LHSUsers = LHSMap["ConnectedUsers"].toInt();
+ int RHSUsers = RHSMap["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 (LHSUsers == 0) {
+ LHSVal -= 500;
+ } else {
+ LHSVal -= LHSUsers;
+ }
+ if (RHSUsers == 0) {
+ RHSVal -= 500;
+ } else {
+ RHSVal -= RHSUsers;
+ }
+
+ // user preference. This is a bit silly because the more servers on the preferred list the higher the boost
+ auto LHSPreference = preferredServers.find(LHSMap["Name"].toString());
+ auto RHSPreference = preferredServers.find(RHSMap["Name"].toString());
+
+ if (LHSPreference != preferredServers.end()) {
+ LHSVal += 100 + LHSPreference->second * 20;
+ }
+ if (RHSPreference != preferredServers.end()) {
+ RHSVal += 100 + RHSPreference->second * 20;
+ }
+
+ // premium isn't valued high because premium servers already get a massive boost for having few users online
+ if (LHSMap["IsPremium"].toBool()) LHSVal += 5;
+ if (RHSMap["IsPremium"].toBool()) RHSVal += 5;
+
+ return RHSVal < LHSVal;
+}
+
+int DownloadManager::startDownloadURLs(const QStringList &urls)
+{
+ addDownload(urls, -1, -1, nullptr);
+ 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(ToQString(MOShared::GameInfo::instance().getGameName())).arg(modID).arg(fileID));
+ return newID;
+}
+
+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;
+ }
+ }
+ return -1;
+}
+
+void DownloadManager::nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID)
+{
+ std::set<int>::iterator idIter = m_RequestIDs.find(requestID);
+ if (idIter == m_RequestIDs.end()) {
+ return;
+ } else {
+ m_RequestIDs.erase(idIter);
+ }
+
+ ModRepositoryFileInfo *info = qobject_cast<ModRepositoryFileInfo*>(qvariant_cast<QObject*>(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));
+
+ info->userData["downloadMap"] = resultList;
+
+ QStringList URLs;
+
+ 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<int>::iterator idIter = m_RequestIDs.find(requestID);
+ if (idIter == m_RequestIDs.end()) {
+ return;
+ } else {
+ m_RequestIDs.erase(idIter);
+ }
+
+ int index = 0;
+
+ for (QVector<DownloadInfo*>::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 != NULL) {
+ 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);
+ }
+ }
+
+ if (info->m_State == STATE_CANCELING) {
+ setState(info, STATE_CANCELED);
+ } else if (info->m_State == STATE_PAUSING) {
+ 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);
+ }
+
+ 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);
+ }
+}
+
+
+void DownloadManager::downloadError(QNetworkReply::NetworkError error)
+{
+ if (error != QNetworkReply::OperationCanceledError) {
+ qWarning("Download error occured: %d", error);
+ }
+}
+
+
+void DownloadManager::metaDataChanged()
+{
+ int index = 0;
+
+ DownloadInfo *info = findDownload(this->sender(), &index);
+ if (info != NULL) {
+ 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();
+}
+
diff --git a/src/downloadmanager.h b/src/downloadmanager.h index 0d8d314a..95288884 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -1,492 +1,492 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef DOWNLOADMANAGER_H -#define DOWNLOADMANAGER_H - -#include "nexusinterface.h" -#include <idownloadmanager.h> -#include <set> -#include <QObject> -#include <QUrl> -#include <QQueue> -#include <QFile> -#include <QNetworkReply> -#include <QTime> -#include <QVector> -#include <QMap> -#include <QStringList> -#include <QFileSystemWatcher> -#include <QSettings> - - -/*! - * \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; - - 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) {} - }; - -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<QString, int> &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<int, int> 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<QString, int> &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(); - -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 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); - - void pauseDownload(int index); - - void resumeDownload(int index); - - void queryInfo(int index); - - 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 nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorString); - -private slots: - - 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); - QString getDownloadFileName(const QString &baseName) const; - - 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 = NULL) const; - - void removeFile(int index, bool deleteFile); - - void refreshAlphabeticalTranslation(); - - bool ByName(int LHS, int RHS); - - QString getFileNameFromNetworkReply(QNetworkReply *reply); - - void setState(DownloadInfo *info, DownloadManager::DownloadState state); - - DownloadInfo *downloadInfoByID(unsigned int id); - - QDateTime matchDate(const QString &timeString); - - void removePending(int modID, int fileID); - - static QString getFileTypeString(int fileType); - -private: - - static const int AUTOMATIC_RETRIES = 3; - -private: - - NexusInterface *m_NexusInterface; - - QVector<std::pair<int, int> > m_PendingDownloads; - - QVector<DownloadInfo*> m_ActiveDownloads; - - QString m_OutputDirectory; - std::map<QString, int> m_PreferredServers; - QStringList m_SupportedExtensions; - std::set<int> m_RequestIDs; - QVector<int> m_AlphabeticalTranslation; - - QFileSystemWatcher m_DirWatcher; - - std::map<QString, int> m_DownloadFails; - - bool m_ShowHidden; - - QRegExp m_DateExpression; - -}; - - - -#endif // DOWNLOADMANAGER_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef DOWNLOADMANAGER_H
+#define DOWNLOADMANAGER_H
+
+#include "nexusinterface.h"
+#include <idownloadmanager.h>
+#include <set>
+#include <QObject>
+#include <QUrl>
+#include <QQueue>
+#include <QFile>
+#include <QNetworkReply>
+#include <QTime>
+#include <QVector>
+#include <QMap>
+#include <QStringList>
+#include <QFileSystemWatcher>
+#include <QSettings>
+
+
+/*!
+ * \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;
+
+ 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) {}
+ };
+
+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<QString, int> &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<int, int> 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<QString, int> &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();
+
+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 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);
+
+ void pauseDownload(int index);
+
+ void resumeDownload(int index);
+
+ void queryInfo(int index);
+
+ 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 nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorString);
+
+private slots:
+
+ 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);
+ QString getDownloadFileName(const QString &baseName) const;
+
+ 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 = NULL) const;
+
+ void removeFile(int index, bool deleteFile);
+
+ void refreshAlphabeticalTranslation();
+
+ bool ByName(int LHS, int RHS);
+
+ QString getFileNameFromNetworkReply(QNetworkReply *reply);
+
+ void setState(DownloadInfo *info, DownloadManager::DownloadState state);
+
+ DownloadInfo *downloadInfoByID(unsigned int id);
+
+ QDateTime matchDate(const QString &timeString);
+
+ void removePending(int modID, int fileID);
+
+ static QString getFileTypeString(int fileType);
+
+private:
+
+ static const int AUTOMATIC_RETRIES = 3;
+
+private:
+
+ NexusInterface *m_NexusInterface;
+
+ QVector<std::pair<int, int> > m_PendingDownloads;
+
+ QVector<DownloadInfo*> m_ActiveDownloads;
+
+ QString m_OutputDirectory;
+ std::map<QString, int> m_PreferredServers;
+ QStringList m_SupportedExtensions;
+ std::set<int> m_RequestIDs;
+ QVector<int> m_AlphabeticalTranslation;
+
+ QFileSystemWatcher m_DirWatcher;
+
+ std::map<QString, int> m_DownloadFails;
+
+ bool m_ShowHidden;
+
+ QRegExp m_DateExpression;
+
+};
+
+
+
+#endif // DOWNLOADMANAGER_H
diff --git a/src/dummybsa.cpp b/src/dummybsa.cpp index 3357dea6..df757139 100644 --- a/src/dummybsa.cpp +++ b/src/dummybsa.cpp @@ -1,244 +1,244 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "dummybsa.h" -#include <QFile> -#include <gameinfo.h> -#define WIN32_LEAN_AND_MEAN -#include <Windows.h> - - -static void writeUlong(unsigned char* buffer, int offset, unsigned long value) -{ - union { - unsigned long ulValue; - unsigned char cValue[4]; - }; - ulValue = value; - memcpy(buffer + offset, cValue, 4); -} - -static void writeUlonglong(unsigned char* buffer, int offset, unsigned long long value) -{ - union { - unsigned long long ullValue; - unsigned char cValue[8]; - }; - ullValue = value; - memcpy(buffer + offset, cValue, 8); -} -/* -static unsigned long long genHashInt(const char* pos, const char* end) -{ - unsigned long long hash2 = 0; - for (; pos < end; ++pos) { - hash2 *= 0x1003f; - hash2 += *pos; - } - return hash2; -} - - -static unsigned long long genHash(const char* fileName) -{ - char fileNameLower[MAX_PATH + 1]; - int i = 0; - for (; i < MAX_PATH && fileName[i] != '\0'; ++i) { - fileNameLower[i] = tolower(fileName[i]); - } - fileNameLower[i] = '\0'; - - char* ext = strrchr(fileNameLower, '.'); - if (ext == NULL) { - ext = fileNameLower + strlen(fileNameLower); - } - - int length = ext - fileNameLower; - - unsigned long long hash = 0ULL; - - if (length > 0) { - hash = *(ext - 1) | - ((length > 2 ? *(ext - 2) : 0) << 8) | - (length << 16) | - (fileNameLower[0] << 24); - } - - if (strcmp(ext + 1, "dds") == 0) { - hash |= 0x8080; - } // the rest is not supported currently - - hash |= genHashInt(fileNameLower + 1, ext - 2) << 0x32; - hash |= genHashInt(ext, ext + strlen(ext)) << 0x32; - - return hash; -}*/ - - -static unsigned long genHashInt(const unsigned char *pos, const unsigned char *end) -{ - unsigned long hash = 0; - for (; pos < end; ++pos) { - hash *= 0x1003f; - hash += *pos; - } - return hash; -} - - -static unsigned long long genHash(const char* fileName) -{ - char fileNameLower[MAX_PATH + 1]; - int i = 0; - for (; i < MAX_PATH && fileName[i] != '\0'; ++i) { - fileNameLower[i] = static_cast<char>(tolower(fileName[i])); - if (fileNameLower[i] == '\\') { - fileNameLower[i] = '/'; - } - } - fileNameLower[i] = '\0'; - - unsigned char *fileNameLowerU = reinterpret_cast<unsigned char*>(fileNameLower); - - char* ext = strrchr(fileNameLower, '.'); - if (ext == NULL) { - ext = fileNameLower + strlen(fileNameLower); - } - unsigned char *extU = reinterpret_cast<unsigned char*>(ext); - - int length = ext - fileNameLower; - - unsigned long long hash = 0ULL; - - if (length > 0) { - hash = *(extU - 1) | - ((length > 2 ? *(ext - 2) : 0) << 8) | - (length << 16) | - (fileNameLowerU[0] << 24); - } - - if (strlen(ext) > 0) { - if (strcmp(ext + 1, "kf") == 0) { - hash |= 0x80; - } else if (strcmp(ext + 1, "nif") == 0) { - hash |= 0x8000; - } else if (strcmp(ext + 1, "dds") == 0) { - hash |= 0x8080; - } else if (strcmp(ext + 1, "wav") == 0) { - hash |= 0x80000000; - } - - unsigned long long temp = static_cast<unsigned long long>(genHashInt( - fileNameLowerU + 1, extU - 2)); - temp += static_cast<unsigned long long>(genHashInt( - extU, extU + strlen(ext))); - - hash |= (temp & 0xFFFFFFFF) << 32; - } - return hash; -} - - -DummyBSA::DummyBSA() - : m_Version(MOShared::GameInfo::instance().getBSAVersion()), m_FolderName(""), m_FileName("dummy.dds"), - m_TotalFileNameLength(0) -{ -} - - -void DummyBSA::writeHeader(QFile& file) -{ - unsigned char header[] = { - 'B', 'S', 'A', '\0', // magic string - 0xDE, 0xAD, 0xBE, 0xEF, // version - insert later - 0x24, 0x00, 0x00, 0x00, // offset to folder recors. header size is static - 0xDE, 0xAD, 0xBE, 0xEF, // archive flags - insert later - 0x01, 0x00, 0x00, 0x00, // folder count - 0x01, 0x00, 0x00, 0x00, // file count - 0xDE, 0xAD, 0xBE, 0xEF, // total folder names length - insert later - 0xDE, 0xAD, 0xBE, 0xEF, // total file names length - insert later - 0xDE, 0xAD, 0xBE, 0xEF // file flags - insert later - }; - - writeUlong(header, 4, MOShared::GameInfo::instance().getBSAVersion()); - writeUlong(header, 12, 0x01 | 0x02); // has directories and has files. - writeUlong(header, 24, m_FolderName.length() + 1); // empty folder name - writeUlong(header, 28, m_TotalFileNameLength); // single character file name - - writeUlong(header, 32, 2); // has dds - - file.write(reinterpret_cast<char*>(header), sizeof(header)); -} - - -void DummyBSA::writeFolderRecord(QFile& file, const std::string& folderName) -{ - unsigned char folderRecord[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, // folder hash - 0x01, 0x00, 0x00, 0x00, // file count - 0xDE, 0xAD, 0xBE, 0xEF, // offset to folder name - }; - // we'd usually have to sort folders be the hash value generated here - writeUlonglong(folderRecord, 0, genHash(folderName.c_str())); - writeUlong( folderRecord, 12, 0x34 + m_TotalFileNameLength); // TODO: this should be calculated properly - - file.write(reinterpret_cast<char*>(folderRecord), sizeof(folderRecord)); -} - - -void DummyBSA::writeFileRecord(QFile& file, const std::string& fileName) -{ - unsigned char fileRecord[] = { - 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, // file name hash - 0xDE, 0xAD, 0xBE, 0xEF, // size - 0xDE, 0xAD, 0xBE, 0xEF, // offset to file data - }; - - // we'd usually have to sort files by the value generated here - writeUlonglong(fileRecord, 0, genHash(fileName.c_str())); - writeUlong( fileRecord, 8, 0); - writeUlong( fileRecord, 12, 0x44 + (fileName.length() + 1) + 4); // after this record we expect the filename and 4 bytes of file size - - file.write(reinterpret_cast<char*>(fileRecord), sizeof(fileRecord)); -} - - -void DummyBSA::writeFileRecordBlocks(QFile& file, const std::string& folderName) -{ - file.write(folderName.c_str(), folderName.length() + 1); - - writeFileRecord(file, m_FileName); -} - - -void DummyBSA::write(const QString& fileName) -{ - QFile file(fileName); - file.open(QIODevice::WriteOnly); - - m_TotalFileNameLength = m_FileName.length() + 1; - - writeHeader(file); - writeFolderRecord(file, m_FolderName); - writeFileRecordBlocks(file, m_FolderName); - file.write(m_FileName.c_str() , m_FileName.length() + 1); - char fileSize[] = { 0x00, 0x00, 0x00, 0x00 }; - file.write(fileSize, sizeof(fileSize)); - file.close(); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "dummybsa.h"
+#include <QFile>
+#include <gameinfo.h>
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+
+static void writeUlong(unsigned char* buffer, int offset, unsigned long value)
+{
+ union {
+ unsigned long ulValue;
+ unsigned char cValue[4];
+ };
+ ulValue = value;
+ memcpy(buffer + offset, cValue, 4);
+}
+
+static void writeUlonglong(unsigned char* buffer, int offset, unsigned long long value)
+{
+ union {
+ unsigned long long ullValue;
+ unsigned char cValue[8];
+ };
+ ullValue = value;
+ memcpy(buffer + offset, cValue, 8);
+}
+/*
+static unsigned long long genHashInt(const char* pos, const char* end)
+{
+ unsigned long long hash2 = 0;
+ for (; pos < end; ++pos) {
+ hash2 *= 0x1003f;
+ hash2 += *pos;
+ }
+ return hash2;
+}
+
+
+static unsigned long long genHash(const char* fileName)
+{
+ char fileNameLower[MAX_PATH + 1];
+ int i = 0;
+ for (; i < MAX_PATH && fileName[i] != '\0'; ++i) {
+ fileNameLower[i] = tolower(fileName[i]);
+ }
+ fileNameLower[i] = '\0';
+
+ char* ext = strrchr(fileNameLower, '.');
+ if (ext == NULL) {
+ ext = fileNameLower + strlen(fileNameLower);
+ }
+
+ int length = ext - fileNameLower;
+
+ unsigned long long hash = 0ULL;
+
+ if (length > 0) {
+ hash = *(ext - 1) |
+ ((length > 2 ? *(ext - 2) : 0) << 8) |
+ (length << 16) |
+ (fileNameLower[0] << 24);
+ }
+
+ if (strcmp(ext + 1, "dds") == 0) {
+ hash |= 0x8080;
+ } // the rest is not supported currently
+
+ hash |= genHashInt(fileNameLower + 1, ext - 2) << 0x32;
+ hash |= genHashInt(ext, ext + strlen(ext)) << 0x32;
+
+ return hash;
+}*/
+
+
+static unsigned long genHashInt(const unsigned char *pos, const unsigned char *end)
+{
+ unsigned long hash = 0;
+ for (; pos < end; ++pos) {
+ hash *= 0x1003f;
+ hash += *pos;
+ }
+ return hash;
+}
+
+
+static unsigned long long genHash(const char* fileName)
+{
+ char fileNameLower[MAX_PATH + 1];
+ int i = 0;
+ for (; i < MAX_PATH && fileName[i] != '\0'; ++i) {
+ fileNameLower[i] = static_cast<char>(tolower(fileName[i]));
+ if (fileNameLower[i] == '\\') {
+ fileNameLower[i] = '/';
+ }
+ }
+ fileNameLower[i] = '\0';
+
+ unsigned char *fileNameLowerU = reinterpret_cast<unsigned char*>(fileNameLower);
+
+ char* ext = strrchr(fileNameLower, '.');
+ if (ext == NULL) {
+ ext = fileNameLower + strlen(fileNameLower);
+ }
+ unsigned char *extU = reinterpret_cast<unsigned char*>(ext);
+
+ int length = ext - fileNameLower;
+
+ unsigned long long hash = 0ULL;
+
+ if (length > 0) {
+ hash = *(extU - 1) |
+ ((length > 2 ? *(ext - 2) : 0) << 8) |
+ (length << 16) |
+ (fileNameLowerU[0] << 24);
+ }
+
+ if (strlen(ext) > 0) {
+ if (strcmp(ext + 1, "kf") == 0) {
+ hash |= 0x80;
+ } else if (strcmp(ext + 1, "nif") == 0) {
+ hash |= 0x8000;
+ } else if (strcmp(ext + 1, "dds") == 0) {
+ hash |= 0x8080;
+ } else if (strcmp(ext + 1, "wav") == 0) {
+ hash |= 0x80000000;
+ }
+
+ unsigned long long temp = static_cast<unsigned long long>(genHashInt(
+ fileNameLowerU + 1, extU - 2));
+ temp += static_cast<unsigned long long>(genHashInt(
+ extU, extU + strlen(ext)));
+
+ hash |= (temp & 0xFFFFFFFF) << 32;
+ }
+ return hash;
+}
+
+
+DummyBSA::DummyBSA()
+ : m_Version(MOShared::GameInfo::instance().getBSAVersion()), m_FolderName(""), m_FileName("dummy.dds"),
+ m_TotalFileNameLength(0)
+{
+}
+
+
+void DummyBSA::writeHeader(QFile& file)
+{
+ unsigned char header[] = {
+ 'B', 'S', 'A', '\0', // magic string
+ 0xDE, 0xAD, 0xBE, 0xEF, // version - insert later
+ 0x24, 0x00, 0x00, 0x00, // offset to folder recors. header size is static
+ 0xDE, 0xAD, 0xBE, 0xEF, // archive flags - insert later
+ 0x01, 0x00, 0x00, 0x00, // folder count
+ 0x01, 0x00, 0x00, 0x00, // file count
+ 0xDE, 0xAD, 0xBE, 0xEF, // total folder names length - insert later
+ 0xDE, 0xAD, 0xBE, 0xEF, // total file names length - insert later
+ 0xDE, 0xAD, 0xBE, 0xEF // file flags - insert later
+ };
+
+ writeUlong(header, 4, MOShared::GameInfo::instance().getBSAVersion());
+ writeUlong(header, 12, 0x01 | 0x02); // has directories and has files.
+ writeUlong(header, 24, m_FolderName.length() + 1); // empty folder name
+ writeUlong(header, 28, m_TotalFileNameLength); // single character file name
+
+ writeUlong(header, 32, 2); // has dds
+
+ file.write(reinterpret_cast<char*>(header), sizeof(header));
+}
+
+
+void DummyBSA::writeFolderRecord(QFile& file, const std::string& folderName)
+{
+ unsigned char folderRecord[] = {
+ 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, // folder hash
+ 0x01, 0x00, 0x00, 0x00, // file count
+ 0xDE, 0xAD, 0xBE, 0xEF, // offset to folder name
+ };
+ // we'd usually have to sort folders be the hash value generated here
+ writeUlonglong(folderRecord, 0, genHash(folderName.c_str()));
+ writeUlong( folderRecord, 12, 0x34 + m_TotalFileNameLength); // TODO: this should be calculated properly
+
+ file.write(reinterpret_cast<char*>(folderRecord), sizeof(folderRecord));
+}
+
+
+void DummyBSA::writeFileRecord(QFile& file, const std::string& fileName)
+{
+ unsigned char fileRecord[] = {
+ 0xDE, 0xAD, 0xBE, 0xEF, 0xDE, 0xAD, 0xBE, 0xEF, // file name hash
+ 0xDE, 0xAD, 0xBE, 0xEF, // size
+ 0xDE, 0xAD, 0xBE, 0xEF, // offset to file data
+ };
+
+ // we'd usually have to sort files by the value generated here
+ writeUlonglong(fileRecord, 0, genHash(fileName.c_str()));
+ writeUlong( fileRecord, 8, 0);
+ writeUlong( fileRecord, 12, 0x44 + (fileName.length() + 1) + 4); // after this record we expect the filename and 4 bytes of file size
+
+ file.write(reinterpret_cast<char*>(fileRecord), sizeof(fileRecord));
+}
+
+
+void DummyBSA::writeFileRecordBlocks(QFile& file, const std::string& folderName)
+{
+ file.write(folderName.c_str(), folderName.length() + 1);
+
+ writeFileRecord(file, m_FileName);
+}
+
+
+void DummyBSA::write(const QString& fileName)
+{
+ QFile file(fileName);
+ file.open(QIODevice::WriteOnly);
+
+ m_TotalFileNameLength = m_FileName.length() + 1;
+
+ writeHeader(file);
+ writeFolderRecord(file, m_FolderName);
+ writeFileRecordBlocks(file, m_FolderName);
+ file.write(m_FileName.c_str() , m_FileName.length() + 1);
+ char fileSize[] = { 0x00, 0x00, 0x00, 0x00 };
+ file.write(fileSize, sizeof(fileSize));
+ file.close();
+}
diff --git a/src/dummybsa.h b/src/dummybsa.h index 6fcea82b..0d231393 100644 --- a/src/dummybsa.h +++ b/src/dummybsa.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#ifndef DUMMYBSA_H
#define DUMMYBSA_H
diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp index 13223b68..02abf30e 100644 --- a/src/editexecutablesdialog.cpp +++ b/src/editexecutablesdialog.cpp @@ -1,289 +1,289 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "editexecutablesdialog.h" -#include "ui_editexecutablesdialog.h" -#include "filedialogmemory.h" -#include <QMessageBox> -#include <Shellapi.h> -#include <utility.h> - - -using namespace MOBase; -using namespace MOShared; - -EditExecutablesDialog::EditExecutablesDialog(const ExecutablesList &executablesList, QWidget *parent) - : TutorableDialog("EditExecutables", parent), - ui(new Ui::EditExecutablesDialog), m_CurrentItem(NULL), m_ExecutablesList(executablesList) -{ - ui->setupUi(this); - - refreshExecutablesWidget(); -} - -EditExecutablesDialog::~EditExecutablesDialog() -{ - delete ui; -} - -ExecutablesList EditExecutablesDialog::getExecutablesList() const -{ - ExecutablesList newList; - for (int i = 0; i < ui->executablesListBox->count(); ++i) { - newList.addExecutable(ui->executablesListBox->item(i)->data(Qt::UserRole).value<Executable>()); - } - return newList; -} - -void EditExecutablesDialog::refreshExecutablesWidget() -{ - QListWidget *executablesWidget = findChild<QListWidget*>("executablesListBox"); - - executablesWidget->clear(); - std::vector<Executable>::const_iterator current, end; - m_ExecutablesList.getExecutables(current, end); - - for(; current != end; ++current) { - QListWidgetItem *newItem = new QListWidgetItem(current->m_Title); - QVariant temp; - temp.setValue(*current); - newItem->setData(Qt::UserRole, temp); - newItem->setTextColor(current->m_Custom ? QColor(Qt::black) : QColor(Qt::darkGray)); - executablesWidget->addItem(newItem); - } - - ui->addButton->setEnabled(false); - ui->removeButton->setEnabled(false); -} - - -void EditExecutablesDialog::on_binaryEdit_textChanged(const QString &arg1) -{ - QFileInfo fileInfo(arg1); - 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->closeCheckBox->setChecked(false); - m_CurrentItem = NULL; -} - - -void EditExecutablesDialog::saveExecutable() -{ - m_ExecutablesList.addExecutable(ui->titleEdit->text(), QDir::fromNativeSeparators(ui->binaryEdit->text()), - ui->argumentsEdit->text(), QDir::fromNativeSeparators(ui->workingDirEdit->text()), - (ui->closeCheckBox->checkState() == Qt::Checked) ? DEFAULT_CLOSE : DEFAULT_STAY, - ui->overwriteAppIDBox->isChecked() ? ui->appIDOverwriteEdit->text() : "", - true, false); -} - - -void EditExecutablesDialog::delayedRefresh() -{ - int index = ui->executablesListBox->currentIndex().row(); - resetInput(); - refreshExecutablesWidget(); - ui->executablesListBox->setCurrentRow(index); -} - - -void EditExecutablesDialog::on_addButton_clicked() -{ - 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(), NULL, buffer) > (HINSTANCE)32) { - DWORD binaryType = 0UL; - if (!::GetBinaryTypeW(binaryNameW.c_str(), &binaryType)) { - qDebug("failed to determine binary type: %lu", ::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) + "\""); - } else { - ui->binaryEdit->setText(QDir::toNativeSeparators(binaryName)); - } -} - -void EditExecutablesDialog::on_browseDirButton_clicked() -{ - QString dirName = FileDialogMemory::getExistingDirectory("editExecutableDirectory", this, - tr("Select a directory")); - - ui->workingDirEdit->setText(dirName); -} - -void EditExecutablesDialog::on_removeButton_clicked() -{ -// QLineEdit *binaryEdit = findChild<QLineEdit*>("binaryEdit"); - - 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(); -} - -void EditExecutablesDialog::on_titleEdit_textChanged(const QString &arg1) -{ - QPushButton *addButton = findChild<QPushButton*>("addButton"); - QPushButton *removeButton = findChild<QPushButton*>("removeButton"); - - QListWidget *executablesWidget = findChild<QListWidget*>("executablesListBox"); - - QList<QListWidgetItem*> existingItems = executablesWidget->findItems(arg1, Qt::MatchFixedString); - - 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); - } -} - - -bool EditExecutablesDialog::executableChanged() -{ - if (m_CurrentItem != NULL) { - const Executable &selectedExecutable = m_CurrentItem->data(Qt::UserRole).value<Executable>(); - - return selectedExecutable.m_Arguments != ui->argumentsEdit->text() - || selectedExecutable.m_SteamAppID != ui->appIDOverwriteEdit->text() - || selectedExecutable.m_WorkingDirectory != QDir::fromNativeSeparators(ui->workingDirEdit->text()) - || selectedExecutable.m_BinaryInfo.absoluteFilePath() != QDir::fromNativeSeparators(ui->binaryEdit->text()) - || (selectedExecutable.m_CloseMO == DEFAULT_CLOSE) != ui->closeCheckBox->isChecked(); - } else { - return false; - } -} - - -void EditExecutablesDialog::on_executablesListBox_currentItemChanged(QListWidgetItem *current, QListWidgetItem*) -{ - if (current == NULL) { - resetInput(); - return; - } - - 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) { - // this invalidates the item passed as a a parameter - saveExecutable(); - - QTimer::singleShot(50, this, SLOT(delayedRefresh())); - return; - } - } - - m_CurrentItem = current; - - const Executable &selectedExecutable = current->data(Qt::UserRole).value<Executable>(); - - 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->closeCheckBox->setChecked(selectedExecutable.m_CloseMO == DEFAULT_CLOSE); - if (selectedExecutable.m_CloseMO == NEVER_CLOSE) { - ui->closeCheckBox->setEnabled(false); - ui->closeCheckBox->setToolTip(tr("MO must be kept running or this application will not work correctly.")); - } else { - ui->closeCheckBox->setEnabled(true); - ui->closeCheckBox->setToolTip(tr("If checked, MO will be closed once the specified executable is run.")); - } - ui->removeButton->setEnabled(selectedExecutable.m_Custom); - ui->overwriteAppIDBox->setChecked(selectedExecutable.m_SteamAppID != 0); - if (selectedExecutable.m_SteamAppID != 0) { - ui->appIDOverwriteEdit->setText(selectedExecutable.m_SteamAppID); - } else { - ui->appIDOverwriteEdit->clear(); - } -} - - -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(); - } - } - this->accept(); -} - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "editexecutablesdialog.h"
+#include "ui_editexecutablesdialog.h"
+#include "filedialogmemory.h"
+#include <QMessageBox>
+#include <Shellapi.h>
+#include <utility.h>
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+EditExecutablesDialog::EditExecutablesDialog(const ExecutablesList &executablesList, QWidget *parent)
+ : TutorableDialog("EditExecutables", parent),
+ ui(new Ui::EditExecutablesDialog), m_CurrentItem(NULL), m_ExecutablesList(executablesList)
+{
+ ui->setupUi(this);
+
+ refreshExecutablesWidget();
+}
+
+EditExecutablesDialog::~EditExecutablesDialog()
+{
+ delete ui;
+}
+
+ExecutablesList EditExecutablesDialog::getExecutablesList() const
+{
+ ExecutablesList newList;
+ for (int i = 0; i < ui->executablesListBox->count(); ++i) {
+ newList.addExecutable(ui->executablesListBox->item(i)->data(Qt::UserRole).value<Executable>());
+ }
+ return newList;
+}
+
+void EditExecutablesDialog::refreshExecutablesWidget()
+{
+ QListWidget *executablesWidget = findChild<QListWidget*>("executablesListBox");
+
+ executablesWidget->clear();
+ std::vector<Executable>::const_iterator current, end;
+ m_ExecutablesList.getExecutables(current, end);
+
+ for(; current != end; ++current) {
+ QListWidgetItem *newItem = new QListWidgetItem(current->m_Title);
+ QVariant temp;
+ temp.setValue(*current);
+ newItem->setData(Qt::UserRole, temp);
+ newItem->setTextColor(current->m_Custom ? QColor(Qt::black) : QColor(Qt::darkGray));
+ executablesWidget->addItem(newItem);
+ }
+
+ ui->addButton->setEnabled(false);
+ ui->removeButton->setEnabled(false);
+}
+
+
+void EditExecutablesDialog::on_binaryEdit_textChanged(const QString &arg1)
+{
+ QFileInfo fileInfo(arg1);
+ 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->closeCheckBox->setChecked(false);
+ m_CurrentItem = NULL;
+}
+
+
+void EditExecutablesDialog::saveExecutable()
+{
+ m_ExecutablesList.addExecutable(ui->titleEdit->text(), QDir::fromNativeSeparators(ui->binaryEdit->text()),
+ ui->argumentsEdit->text(), QDir::fromNativeSeparators(ui->workingDirEdit->text()),
+ (ui->closeCheckBox->checkState() == Qt::Checked) ? DEFAULT_CLOSE : DEFAULT_STAY,
+ ui->overwriteAppIDBox->isChecked() ? ui->appIDOverwriteEdit->text() : "",
+ true, false);
+}
+
+
+void EditExecutablesDialog::delayedRefresh()
+{
+ int index = ui->executablesListBox->currentIndex().row();
+ resetInput();
+ refreshExecutablesWidget();
+ ui->executablesListBox->setCurrentRow(index);
+}
+
+
+void EditExecutablesDialog::on_addButton_clicked()
+{
+ 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(), NULL, buffer) > (HINSTANCE)32) {
+ DWORD binaryType = 0UL;
+ if (!::GetBinaryTypeW(binaryNameW.c_str(), &binaryType)) {
+ qDebug("failed to determine binary type: %lu", ::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) + "\"");
+ } else {
+ ui->binaryEdit->setText(QDir::toNativeSeparators(binaryName));
+ }
+}
+
+void EditExecutablesDialog::on_browseDirButton_clicked()
+{
+ QString dirName = FileDialogMemory::getExistingDirectory("editExecutableDirectory", this,
+ tr("Select a directory"));
+
+ ui->workingDirEdit->setText(dirName);
+}
+
+void EditExecutablesDialog::on_removeButton_clicked()
+{
+// QLineEdit *binaryEdit = findChild<QLineEdit*>("binaryEdit");
+
+ 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();
+}
+
+void EditExecutablesDialog::on_titleEdit_textChanged(const QString &arg1)
+{
+ QPushButton *addButton = findChild<QPushButton*>("addButton");
+ QPushButton *removeButton = findChild<QPushButton*>("removeButton");
+
+ QListWidget *executablesWidget = findChild<QListWidget*>("executablesListBox");
+
+ QList<QListWidgetItem*> existingItems = executablesWidget->findItems(arg1, Qt::MatchFixedString);
+
+ 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);
+ }
+}
+
+
+bool EditExecutablesDialog::executableChanged()
+{
+ if (m_CurrentItem != NULL) {
+ const Executable &selectedExecutable = m_CurrentItem->data(Qt::UserRole).value<Executable>();
+
+ return selectedExecutable.m_Arguments != ui->argumentsEdit->text()
+ || selectedExecutable.m_SteamAppID != ui->appIDOverwriteEdit->text()
+ || selectedExecutable.m_WorkingDirectory != QDir::fromNativeSeparators(ui->workingDirEdit->text())
+ || selectedExecutable.m_BinaryInfo.absoluteFilePath() != QDir::fromNativeSeparators(ui->binaryEdit->text())
+ || (selectedExecutable.m_CloseMO == DEFAULT_CLOSE) != ui->closeCheckBox->isChecked();
+ } else {
+ return false;
+ }
+}
+
+
+void EditExecutablesDialog::on_executablesListBox_currentItemChanged(QListWidgetItem *current, QListWidgetItem*)
+{
+ if (current == NULL) {
+ resetInput();
+ return;
+ }
+
+ 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) {
+ // this invalidates the item passed as a a parameter
+ saveExecutable();
+
+ QTimer::singleShot(50, this, SLOT(delayedRefresh()));
+ return;
+ }
+ }
+
+ m_CurrentItem = current;
+
+ const Executable &selectedExecutable = current->data(Qt::UserRole).value<Executable>();
+
+ 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->closeCheckBox->setChecked(selectedExecutable.m_CloseMO == DEFAULT_CLOSE);
+ if (selectedExecutable.m_CloseMO == NEVER_CLOSE) {
+ ui->closeCheckBox->setEnabled(false);
+ ui->closeCheckBox->setToolTip(tr("MO must be kept running or this application will not work correctly."));
+ } else {
+ ui->closeCheckBox->setEnabled(true);
+ ui->closeCheckBox->setToolTip(tr("If checked, MO will be closed once the specified executable is run."));
+ }
+ ui->removeButton->setEnabled(selectedExecutable.m_Custom);
+ ui->overwriteAppIDBox->setChecked(selectedExecutable.m_SteamAppID != 0);
+ if (selectedExecutable.m_SteamAppID != 0) {
+ ui->appIDOverwriteEdit->setText(selectedExecutable.m_SteamAppID);
+ } else {
+ ui->appIDOverwriteEdit->clear();
+ }
+}
+
+
+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();
+ }
+ }
+ this->accept();
+}
+
diff --git a/src/editexecutablesdialog.h b/src/editexecutablesdialog.h index a90cbc59..d27ccb0a 100644 --- a/src/editexecutablesdialog.h +++ b/src/editexecutablesdialog.h @@ -1,98 +1,98 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef EDITEXECUTABLESDIALOG_H -#define EDITEXECUTABLESDIALOG_H - -#include "tutorabledialog.h" -#include <QListWidgetItem> -#include <QTimer> -#include "executableslist.h" - -namespace Ui { - class EditExecutablesDialog; -} - -/** - * @brief Dialog to manage the list of executables - **/ -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, 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_binaryEdit_textChanged(const QString &arg1); - - void on_addButton_clicked(); - - void on_browseButton_clicked(); - - void on_removeButton_clicked(); - - void on_titleEdit_textChanged(const QString &arg1); - - void on_overwriteAppIDBox_toggled(bool checked); - - void on_browseDirButton_clicked(); - - void on_closeButton_clicked(); - - void on_executablesListBox_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - - void delayedRefresh(); - -private: - - void resetInput(); - - void refreshExecutablesWidget(); - - bool executableChanged(); - -private: - Ui::EditExecutablesDialog *ui; - - QListWidgetItem *m_CurrentItem; - - ExecutablesList m_ExecutablesList; - -}; - -#endif // EDITEXECUTABLESDIALOG_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef EDITEXECUTABLESDIALOG_H
+#define EDITEXECUTABLESDIALOG_H
+
+#include "tutorabledialog.h"
+#include <QListWidgetItem>
+#include <QTimer>
+#include "executableslist.h"
+
+namespace Ui {
+ class EditExecutablesDialog;
+}
+
+/**
+ * @brief Dialog to manage the list of executables
+ **/
+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, 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_binaryEdit_textChanged(const QString &arg1);
+
+ void on_addButton_clicked();
+
+ void on_browseButton_clicked();
+
+ void on_removeButton_clicked();
+
+ void on_titleEdit_textChanged(const QString &arg1);
+
+ void on_overwriteAppIDBox_toggled(bool checked);
+
+ void on_browseDirButton_clicked();
+
+ void on_closeButton_clicked();
+
+ void on_executablesListBox_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
+
+ void delayedRefresh();
+
+private:
+
+ void resetInput();
+
+ void refreshExecutablesWidget();
+
+ bool executableChanged();
+
+private:
+ Ui::EditExecutablesDialog *ui;
+
+ QListWidgetItem *m_CurrentItem;
+
+ ExecutablesList m_ExecutablesList;
+
+};
+
+#endif // EDITEXECUTABLESDIALOG_H
diff --git a/src/executableslist.cpp b/src/executableslist.cpp index 57341323..2c6754d7 100644 --- a/src/executableslist.cpp +++ b/src/executableslist.cpp @@ -1,203 +1,203 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "executableslist.h" -#include <gameinfo.h> -#include <QFileInfo> -#include <QDir> -#include "utility.h" -#include <algorithm> - - -using namespace MOBase; -using namespace MOShared; - - -QDataStream &operator<<(QDataStream &out, const Executable &obj) -{ - out << obj.m_Title << obj.m_BinaryInfo.absoluteFilePath() << obj.m_Arguments << obj.m_CloseMO - << obj.m_SteamAppID << obj.m_WorkingDirectory << obj.m_Custom << obj.m_Toolbar; - return out; -} - -QDataStream &operator>>(QDataStream &in, Executable &obj) -{ - QString binaryTemp; - int closeStyleTemp; - in >> obj.m_Title >> binaryTemp >> obj.m_Arguments >> closeStyleTemp - >> obj.m_SteamAppID >> obj.m_WorkingDirectory >> obj.m_Custom >> obj.m_Toolbar; - - obj.m_CloseMO = (CloseMOStyle)closeStyleTemp; - obj.m_BinaryInfo.setFile(binaryTemp); - return in; -} - - -void registerExecutable() -{ - qRegisterMetaType<Executable>("Executable"); - qRegisterMetaTypeStreamOperators<Executable>("Executable"); -} - - -ExecutablesList::ExecutablesList() -{ -} - -ExecutablesList::~ExecutablesList() -{ -} - -void ExecutablesList::init() -{ - std::vector<ExecutableInfo> executables = GameInfo::instance().getExecutables(); - for (std::vector<ExecutableInfo>::const_iterator iter = executables.begin(); iter != executables.end(); ++iter) { - addExecutableInternal(ToQString(iter->title), - QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())).append("/").append(ToQString(iter->binary)), - ToQString(iter->arguments), ToQString(iter->workingDirectory), - iter->closeMO, ToQString(iter->steamAppID)); - } -} - - -void ExecutablesList::getExecutables(std::vector<Executable>::iterator &begin, std::vector<Executable>::iterator &end) -{ - begin = m_Executables.begin(); - end = m_Executables.end(); -} - - -void ExecutablesList::getExecutables(std::vector<Executable>::const_iterator &begin, - std::vector<Executable>::const_iterator &end) const -{ - begin = m_Executables.begin(); - end = m_Executables.end(); -} - - -const Executable &ExecutablesList::find(const QString &title) const -{ - for (std::vector<Executable>::const_iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { - if (iter->m_Title == title) { - return *iter; - } - } - throw std::runtime_error("invalid name"); -} - - -Executable &ExecutablesList::find(const QString &title) -{ - for (std::vector<Executable>::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { - if (QString::compare(iter->m_Title, title, Qt::CaseInsensitive) == 0) { - return *iter; - } - } - throw std::runtime_error("invalid name"); -} - - -Executable *ExecutablesList::findExe(const QString &title) -{ - for (std::vector<Executable>::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { - if (iter->m_Title == title) { - return &*iter; - } - } - return NULL; -} - - -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) -{ - Executable *existingExe = findExe(executable.m_Title); - if (existingExe != NULL) { - *existingExe = executable; - } else { - m_Executables.push_back(executable); - } -} - - -void ExecutablesList::addExecutable(const QString &title, const QString &executableName, const QString &arguments, - const QString &workingDirectory, CloseMOStyle closeMO, const QString &steamAppID, - bool custom, bool toolbar) -{ - QFileInfo file(executableName); - Executable *existingExe = findExe(title); - if (existingExe != NULL) { - existingExe->m_Title = title; - existingExe->m_CloseMO = closeMO; - existingExe->m_BinaryInfo = file; - existingExe->m_Arguments = arguments; - existingExe->m_WorkingDirectory = workingDirectory; - existingExe->m_SteamAppID = steamAppID; - existingExe->m_Custom = custom; - existingExe->m_Toolbar = toolbar; - } else { - Executable newExe; - newExe.m_Title = title; - newExe.m_CloseMO = closeMO; - newExe.m_BinaryInfo = file; - newExe.m_Arguments = arguments; - newExe.m_WorkingDirectory = workingDirectory; - newExe.m_SteamAppID = steamAppID; - newExe.m_Custom = true; - newExe.m_Toolbar = toolbar; - m_Executables.push_back(newExe); - } -} - - -void ExecutablesList::remove(const QString &title) -{ - for (std::vector<Executable>::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) { - if (iter->m_Custom && (iter->m_Title == title)) { - m_Executables.erase(iter); - break; - } - } -} - - -void ExecutablesList::addExecutableInternal(const QString &title, const QString &executableName, - const QString &arguments, const QString &workingDirectory, - CloseMOStyle closeMO, const QString &steamAppID) -{ - QFileInfo file(executableName); - if (file.exists()) { - Executable newExe; - newExe.m_CloseMO = closeMO; - newExe.m_BinaryInfo = file; - newExe.m_Title = title; - newExe.m_Arguments = arguments; - newExe.m_WorkingDirectory = workingDirectory; - newExe.m_SteamAppID = steamAppID; - newExe.m_Custom = false; - newExe.m_Toolbar = false; - m_Executables.push_back(newExe); - } -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "executableslist.h"
+#include <gameinfo.h>
+#include <QFileInfo>
+#include <QDir>
+#include "utility.h"
+#include <algorithm>
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+QDataStream &operator<<(QDataStream &out, const Executable &obj)
+{
+ out << obj.m_Title << obj.m_BinaryInfo.absoluteFilePath() << obj.m_Arguments << obj.m_CloseMO
+ << obj.m_SteamAppID << obj.m_WorkingDirectory << obj.m_Custom << obj.m_Toolbar;
+ return out;
+}
+
+QDataStream &operator>>(QDataStream &in, Executable &obj)
+{
+ QString binaryTemp;
+ int closeStyleTemp;
+ in >> obj.m_Title >> binaryTemp >> obj.m_Arguments >> closeStyleTemp
+ >> obj.m_SteamAppID >> obj.m_WorkingDirectory >> obj.m_Custom >> obj.m_Toolbar;
+
+ obj.m_CloseMO = (CloseMOStyle)closeStyleTemp;
+ obj.m_BinaryInfo.setFile(binaryTemp);
+ return in;
+}
+
+
+void registerExecutable()
+{
+ qRegisterMetaType<Executable>("Executable");
+ qRegisterMetaTypeStreamOperators<Executable>("Executable");
+}
+
+
+ExecutablesList::ExecutablesList()
+{
+}
+
+ExecutablesList::~ExecutablesList()
+{
+}
+
+void ExecutablesList::init()
+{
+ std::vector<ExecutableInfo> executables = GameInfo::instance().getExecutables();
+ for (std::vector<ExecutableInfo>::const_iterator iter = executables.begin(); iter != executables.end(); ++iter) {
+ addExecutableInternal(ToQString(iter->title),
+ QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())).append("/").append(ToQString(iter->binary)),
+ ToQString(iter->arguments), ToQString(iter->workingDirectory),
+ iter->closeMO, ToQString(iter->steamAppID));
+ }
+}
+
+
+void ExecutablesList::getExecutables(std::vector<Executable>::iterator &begin, std::vector<Executable>::iterator &end)
+{
+ begin = m_Executables.begin();
+ end = m_Executables.end();
+}
+
+
+void ExecutablesList::getExecutables(std::vector<Executable>::const_iterator &begin,
+ std::vector<Executable>::const_iterator &end) const
+{
+ begin = m_Executables.begin();
+ end = m_Executables.end();
+}
+
+
+const Executable &ExecutablesList::find(const QString &title) const
+{
+ for (std::vector<Executable>::const_iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) {
+ if (iter->m_Title == title) {
+ return *iter;
+ }
+ }
+ throw std::runtime_error("invalid name");
+}
+
+
+Executable &ExecutablesList::find(const QString &title)
+{
+ for (std::vector<Executable>::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) {
+ if (QString::compare(iter->m_Title, title, Qt::CaseInsensitive) == 0) {
+ return *iter;
+ }
+ }
+ throw std::runtime_error("invalid name");
+}
+
+
+Executable *ExecutablesList::findExe(const QString &title)
+{
+ for (std::vector<Executable>::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) {
+ if (iter->m_Title == title) {
+ return &*iter;
+ }
+ }
+ return NULL;
+}
+
+
+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)
+{
+ Executable *existingExe = findExe(executable.m_Title);
+ if (existingExe != NULL) {
+ *existingExe = executable;
+ } else {
+ m_Executables.push_back(executable);
+ }
+}
+
+
+void ExecutablesList::addExecutable(const QString &title, const QString &executableName, const QString &arguments,
+ const QString &workingDirectory, CloseMOStyle closeMO, const QString &steamAppID,
+ bool custom, bool toolbar)
+{
+ QFileInfo file(executableName);
+ Executable *existingExe = findExe(title);
+ if (existingExe != NULL) {
+ existingExe->m_Title = title;
+ existingExe->m_CloseMO = closeMO;
+ existingExe->m_BinaryInfo = file;
+ existingExe->m_Arguments = arguments;
+ existingExe->m_WorkingDirectory = workingDirectory;
+ existingExe->m_SteamAppID = steamAppID;
+ existingExe->m_Custom = custom;
+ existingExe->m_Toolbar = toolbar;
+ } else {
+ Executable newExe;
+ newExe.m_Title = title;
+ newExe.m_CloseMO = closeMO;
+ newExe.m_BinaryInfo = file;
+ newExe.m_Arguments = arguments;
+ newExe.m_WorkingDirectory = workingDirectory;
+ newExe.m_SteamAppID = steamAppID;
+ newExe.m_Custom = true;
+ newExe.m_Toolbar = toolbar;
+ m_Executables.push_back(newExe);
+ }
+}
+
+
+void ExecutablesList::remove(const QString &title)
+{
+ for (std::vector<Executable>::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) {
+ if (iter->m_Custom && (iter->m_Title == title)) {
+ m_Executables.erase(iter);
+ break;
+ }
+ }
+}
+
+
+void ExecutablesList::addExecutableInternal(const QString &title, const QString &executableName,
+ const QString &arguments, const QString &workingDirectory,
+ CloseMOStyle closeMO, const QString &steamAppID)
+{
+ QFileInfo file(executableName);
+ if (file.exists()) {
+ Executable newExe;
+ newExe.m_CloseMO = closeMO;
+ newExe.m_BinaryInfo = file;
+ newExe.m_Title = title;
+ newExe.m_Arguments = arguments;
+ newExe.m_WorkingDirectory = workingDirectory;
+ newExe.m_SteamAppID = steamAppID;
+ newExe.m_Custom = false;
+ newExe.m_Toolbar = false;
+ m_Executables.push_back(newExe);
+ }
+}
diff --git a/src/executableslist.h b/src/executableslist.h index 4c9e662a..6f7771e5 100644 --- a/src/executableslist.h +++ b/src/executableslist.h @@ -1,155 +1,155 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef EXECUTABLESLIST_H -#define EXECUTABLESLIST_H - - -#include <vector> -#include <QFileInfo> -#include <QMetaType> -#include <gameinfo.h> - - -/*! - * @brief Information about an executable - **/ -struct Executable { - QString m_Title; - QFileInfo m_BinaryInfo; - QString m_Arguments; - MOShared::CloseMOStyle m_CloseMO; - QString m_SteamAppID; - QString m_WorkingDirectory; - - bool m_Custom; - bool m_Toolbar; -}; -Q_DECLARE_METATYPE(Executable) - - -void registerExecutable(); - - -/*! - * @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(); - - /** - * @brief retrieve an executable by index - * - * @param index index of the executable to look up - * @return reference to the executable - * @exception out_of_range will throw an exception if the index is invalid - **/ - const Executable &get(int index) const { return m_Executables.at(index); } - - /** - * @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 &tilte) 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 &tilte); - - /** - * @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 - * @param closeMO if true, MO will be closed when the binary is started - **/ - void addExecutable(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, MOShared::CloseMOStyle closeMO, const QString &steamAppID, bool custom, bool toolbar); - - /** - * @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<Executable>::const_iterator &begin, std::vector<Executable>::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<Executable>::iterator &begin, std::vector<Executable>::iterator &end); - -private: - - Executable *findExe(const QString &title); - - void addExecutableInternal(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, MOShared::CloseMOStyle closeMO, const QString &steamAppID); - -private: - - std::vector<Executable> m_Executables; - -}; - -#endif // EXECUTABLESLIST_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef EXECUTABLESLIST_H
+#define EXECUTABLESLIST_H
+
+
+#include <vector>
+#include <QFileInfo>
+#include <QMetaType>
+#include <gameinfo.h>
+
+
+/*!
+ * @brief Information about an executable
+ **/
+struct Executable {
+ QString m_Title;
+ QFileInfo m_BinaryInfo;
+ QString m_Arguments;
+ MOShared::CloseMOStyle m_CloseMO;
+ QString m_SteamAppID;
+ QString m_WorkingDirectory;
+
+ bool m_Custom;
+ bool m_Toolbar;
+};
+Q_DECLARE_METATYPE(Executable)
+
+
+void registerExecutable();
+
+
+/*!
+ * @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();
+
+ /**
+ * @brief retrieve an executable by index
+ *
+ * @param index index of the executable to look up
+ * @return reference to the executable
+ * @exception out_of_range will throw an exception if the index is invalid
+ **/
+ const Executable &get(int index) const { return m_Executables.at(index); }
+
+ /**
+ * @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 &tilte) 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 &tilte);
+
+ /**
+ * @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
+ * @param closeMO if true, MO will be closed when the binary is started
+ **/
+ void addExecutable(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, MOShared::CloseMOStyle closeMO, const QString &steamAppID, bool custom, bool toolbar);
+
+ /**
+ * @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<Executable>::const_iterator &begin, std::vector<Executable>::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<Executable>::iterator &begin, std::vector<Executable>::iterator &end);
+
+private:
+
+ Executable *findExe(const QString &title);
+
+ void addExecutableInternal(const QString &title, const QString &executableName, const QString &arguments, const QString &workingDirectory, MOShared::CloseMOStyle closeMO, const QString &steamAppID);
+
+private:
+
+ std::vector<Executable> m_Executables;
+
+};
+
+#endif // EXECUTABLESLIST_H
diff --git a/src/filedialogmemory.cpp b/src/filedialogmemory.cpp index f614e76d..6b440d0f 100644 --- a/src/filedialogmemory.cpp +++ b/src/filedialogmemory.cpp @@ -1,94 +1,94 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "filedialogmemory.h" -#include <QFileDialog> - - -FileDialogMemory::FileDialogMemory() -{ -} - - -void FileDialogMemory::save(QSettings &settings) -{ - settings.remove("recentDirectories"); - settings.beginWriteArray("recentDirectories"); - int index = 0; - for (std::map<QString, QString>::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(); -} - - -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(); -} - - -QString FileDialogMemory::getOpenFileName(const QString &dirID, QWidget *parent, - const QString &caption, const QString &dir, - const QString &filter, QString *selectedFilter, - QFileDialog::Options options) -{ - std::pair<std::map<QString, QString>::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; -} - - -QString FileDialogMemory::getExistingDirectory(const QString &dirID, QWidget *parent, - const QString &caption, const QString &dir, QFileDialog::Options options) -{ - std::pair<std::map<QString, QString>::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; -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "filedialogmemory.h"
+#include <QFileDialog>
+
+
+FileDialogMemory::FileDialogMemory()
+{
+}
+
+
+void FileDialogMemory::save(QSettings &settings)
+{
+ settings.remove("recentDirectories");
+ settings.beginWriteArray("recentDirectories");
+ int index = 0;
+ for (std::map<QString, QString>::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();
+}
+
+
+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();
+}
+
+
+QString FileDialogMemory::getOpenFileName(const QString &dirID, QWidget *parent,
+ const QString &caption, const QString &dir,
+ const QString &filter, QString *selectedFilter,
+ QFileDialog::Options options)
+{
+ std::pair<std::map<QString, QString>::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;
+}
+
+
+QString FileDialogMemory::getExistingDirectory(const QString &dirID, QWidget *parent,
+ const QString &caption, const QString &dir, QFileDialog::Options options)
+{
+ std::pair<std::map<QString, QString>::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 e17d83d6..b2bfdb53 100644 --- a/src/filedialogmemory.h +++ b/src/filedialogmemory.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#ifndef FILEDIALOGMEMORY_H
#define FILEDIALOGMEMORY_H
diff --git a/src/gameinfoimpl.cpp b/src/gameinfoimpl.cpp index 80c46bc8..025ce4e6 100644 --- a/src/gameinfoimpl.cpp +++ b/src/gameinfoimpl.cpp @@ -1,54 +1,54 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "gameinfoimpl.h" -#include <gameinfo.h> -#include <utility.h> -#include <QDir> - - -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_FALLOUTNV: return IGameInfo::TYPE_FALLOUTNV; - case GameInfo::TYPE_SKYRIM: return IGameInfo::TYPE_SKYRIM; - default: throw MyException(QObject::tr("invalid game type %1").arg(GameInfo::instance().getType())); - } -} - - -QString GameInfoImpl::path() const -{ - return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); -} - -QString GameInfoImpl::binaryName() const -{ - return ToQString(GameInfo::instance().getBinaryName()); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "gameinfoimpl.h"
+#include <gameinfo.h>
+#include <utility.h>
+#include <QDir>
+
+
+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_FALLOUTNV: return IGameInfo::TYPE_FALLOUTNV;
+ case GameInfo::TYPE_SKYRIM: return IGameInfo::TYPE_SKYRIM;
+ default: throw MyException(QObject::tr("invalid game type %1").arg(GameInfo::instance().getType()));
+ }
+}
+
+
+QString GameInfoImpl::path() const
+{
+ return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
+}
+
+QString GameInfoImpl::binaryName() const
+{
+ return ToQString(GameInfo::instance().getBinaryName());
+}
diff --git a/src/gameinfoimpl.h b/src/gameinfoimpl.h index 372c4c20..3ed7be6b 100644 --- a/src/gameinfoimpl.h +++ b/src/gameinfoimpl.h @@ -1,39 +1,39 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef GAMEINFOIMPL_H -#define GAMEINFOIMPL_H - - -#include <igameinfo.h> -#include <QString> - - -class GameInfoImpl : public MOBase::IGameInfo -{ -public: - GameInfoImpl(); - - virtual Type type() const; - virtual QString path() const; - virtual QString binaryName() const; - -}; - -#endif // GAMEINFOIMPL_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef GAMEINFOIMPL_H
+#define GAMEINFOIMPL_H
+
+
+#include <igameinfo.h>
+#include <QString>
+
+
+class GameInfoImpl : public MOBase::IGameInfo
+{
+public:
+ GameInfoImpl();
+
+ virtual Type type() const;
+ virtual QString path() const;
+ virtual QString binaryName() const;
+
+};
+
+#endif // GAMEINFOIMPL_H
diff --git a/src/helper.cpp b/src/helper.cpp index 55b21a4b..d0825d10 100644 --- a/src/helper.cpp +++ b/src/helper.cpp @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#include "helper.h"
#include "report.h"
#include "utility.h"
diff --git a/src/helper.h b/src/helper.h index ee49a0cd..0144c39f 100644 --- a/src/helper.h +++ b/src/helper.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#ifndef HELPER_H
#define HELPER_H
diff --git a/src/icondelegate.cpp b/src/icondelegate.cpp index a0993add..f7cf6977 100644 --- a/src/icondelegate.cpp +++ b/src/icondelegate.cpp @@ -1,70 +1,70 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "icondelegate.h" -#include <QHBoxLayout> -#include <QLabel> -#include <QPainter> -#include <QDebug> - - -IconDelegate::IconDelegate(QObject *parent) - : QStyledItemDelegate(parent) -{ -} - - -void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const -{ - QStyledItemDelegate::paint(painter, option, index); - - QList<QIcon> 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 (auto iter = icons.begin(); iter != icons.end(); ++iter) { - painter->drawPixmap(x, 2, iconWidth, iconWidth, iter->pixmap(QSize(iconWidth, iconWidth))); - x += iconWidth + 4; - } - - painter->restore(); -} - - -QSize IconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const -{ - int count = getNumIcons(modelIndex); - unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt(); - QSize result; - if (index < ModInfo::getNumMods()) { - result = QSize(count * 40, 20); - } else { - result = QSize(1, 20); - } - if (option.rect.width() > 0) { - result.setWidth(std::min(option.rect.width(), result.width())); - } - return result; -} - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "icondelegate.h"
+#include <QHBoxLayout>
+#include <QLabel>
+#include <QPainter>
+#include <QDebug>
+
+
+IconDelegate::IconDelegate(QObject *parent)
+ : QStyledItemDelegate(parent)
+{
+}
+
+
+void IconDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
+{
+ QStyledItemDelegate::paint(painter, option, index);
+
+ QList<QIcon> 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 (auto iter = icons.begin(); iter != icons.end(); ++iter) {
+ painter->drawPixmap(x, 2, iconWidth, iconWidth, iter->pixmap(QSize(iconWidth, iconWidth)));
+ x += iconWidth + 4;
+ }
+
+ painter->restore();
+}
+
+
+QSize IconDelegate::sizeHint(const QStyleOptionViewItem &option, const QModelIndex &modelIndex) const
+{
+ int count = getNumIcons(modelIndex);
+ unsigned int index = modelIndex.data(Qt::UserRole + 1).toInt();
+ QSize result;
+ if (index < ModInfo::getNumMods()) {
+ result = QSize(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/icondelegate.h b/src/icondelegate.h index cf292443..072343bb 100644 --- a/src/icondelegate.h +++ b/src/icondelegate.h @@ -1,52 +1,52 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef ICONDELEGATE_H -#define ICONDELEGATE_H - -#include "modinfo.h" -#include <QStyledItemDelegate> -#include <QAbstractProxyModel> - - -class IconDelegate : public QStyledItemDelegate -{ - Q_OBJECT -public: - - explicit IconDelegate(QObject *parent = 0); - - virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const; - virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const; - -signals: - -public slots: - -private: - - virtual QList<QIcon> getIcons(const QModelIndex &index) const = 0; - virtual size_t getNumIcons(const QModelIndex &index) const = 0; - - -private: - -}; - -#endif // ICONDELEGATE_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef ICONDELEGATE_H
+#define ICONDELEGATE_H
+
+#include "modinfo.h"
+#include <QStyledItemDelegate>
+#include <QAbstractProxyModel>
+
+
+class IconDelegate : public QStyledItemDelegate
+{
+ Q_OBJECT
+public:
+
+ explicit IconDelegate(QObject *parent = 0);
+
+ virtual void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
+ virtual QSize sizeHint(const QStyleOptionViewItem &option, const QModelIndex &index) const;
+
+signals:
+
+public slots:
+
+private:
+
+ virtual QList<QIcon> getIcons(const QModelIndex &index) const = 0;
+ virtual size_t getNumIcons(const QModelIndex &index) const = 0;
+
+
+private:
+
+};
+
+#endif // ICONDELEGATE_H
diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index c6ddabce..c865d897 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -1,802 +1,802 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "installationmanager.h" -#include "utility.h" -#include "report.h" -#include "categories.h" -#include "questionboxmemory.h" -#include "settings.h" -#include "queryoverwritedialog.h" -#include "messagedialog.h" -#include "iplugininstallersimple.h" -#include "iplugininstallercustom.h" -#include "nexusinterface.h" -#include "selectiondialog.h" -#include <scopeguard.h> -#include <installationtester.h> -#include <gameinfo.h> -#include <utility.h> -#include <QFileInfo> -#include <QLibrary> -#include <QInputDialog> -#include <QRegExp> -#include <QDir> -#include <QMessageBox> -#include <QSettings> -#include <Shellapi.h> -#include <QPushButton> -#include <QApplication> -#include <QDateTime> -#include <QDirIterator> -#include <boost/assign.hpp> -#include <boost/scoped_ptr.hpp> - - -using namespace MOBase; -using namespace MOShared; - - -typedef Archive* (*CreateArchiveType)(); - - - -template <typename T> T resolveFunction(QLibrary &lib, const char *name) -{ - T temp = reinterpret_cast<T>(lib.resolve(name)); - if (temp == NULL) { - throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData()); - } - return temp; -} - - -InstallationManager::InstallationManager(QWidget *parent) - : QObject(parent), m_ParentWidget(parent), - m_InstallationProgress(parent), m_SupportedExtensions(boost::assign::list_of("zip")("rar")("7z")("fomod")("001")) -{ - QLibrary archiveLib("dlls\\archive.dll"); - if (!archiveLib.load()) { - throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString())); - } - - CreateArchiveType CreateArchiveFunc = resolveFunction<CreateArchiveType>(archiveLib, "CreateArchive"); - - m_CurrentArchive = CreateArchiveFunc(); - if (!m_CurrentArchive->isValid()) { - throw MyException(getErrorString(m_CurrentArchive->getLastError())); - } - - m_InstallationProgress.setWindowFlags(m_InstallationProgress.windowFlags() & (~Qt::WindowContextHelpButtonHint)); -} - - -InstallationManager::~InstallationManager() -{ - delete m_CurrentArchive; -} - - -void InstallationManager::queryPassword(LPSTR password) -{ - QString result = QInputDialog::getText(NULL, tr("Password required"), tr("Password"), QLineEdit::Password); - strncpy(password, result.toLocal8Bit().constData(), MAX_PASSWORD_LENGTH); -} - - -void InstallationManager::mapToArchive(const DirectoryTree::Node *node, std::wstring path, FileData * const *data) -{ - if (path.length() > 0) { - path.append(L"\\"); - } - - for (DirectoryTree::const_leaf_iterator iter = node->leafsBegin(); iter != node->leafsEnd(); ++iter) { - data[iter->getIndex()]->setSkip(false); - std::wstring temp = path.substr().append(ToWString(iter->getName())); - data[iter->getIndex()]->setOutputFileName(temp.c_str()); - } - - for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) { - if ((*iter)->getData().index != -1) { - data[(*iter)->getData().index]->setSkip(false); - std::wstring temp = path.substr().append(ToWString((*iter)->getData().name)); - data[(*iter)->getData().index]->setOutputFileName(temp.c_str()); - } - mapToArchive(*iter, path.substr().append(ToWString((*iter)->getData().name)), data); - } -} - - -void InstallationManager::mapToArchive(const DirectoryTree::Node *baseNode) -{ - FileData* const *data; - size_t size; - m_CurrentArchive->getFileList(data, size); - - // first disable all files + folders, we will re-enable those present in baseNode - for (size_t i = 0; i < size; ++i) { - data[i]->setSkip(true); - } - - std::wstring currentPath; - - mapToArchive(baseNode, currentPath, data); -} - - -bool InstallationManager::unpackSingleFile(const QString &fileName) -{ - FileData* const *data; - size_t size; - m_CurrentArchive->getFileList(data, size); - - QString baseName = QFileInfo(fileName).fileName(); - - bool available = false; - for (size_t i = 0; i < size; ++i) { - if (_wcsicmp(data[i]->getFileName(), ToWString(fileName).c_str()) == 0) { - available = true; - data[i]->setSkip(false); - data[i]->setOutputFileName(ToWString(baseName).c_str()); - m_TempFilesToDelete.insert(baseName); - } else { - data[i]->setSkip(true); - } - } - - if (available) { - m_InstallationProgress.setWindowTitle(tr("Extracting files")); - m_InstallationProgress.setLabelText(QString()); - m_InstallationProgress.setValue(0); - m_InstallationProgress.setWindowModality(Qt::WindowModal); - m_InstallationProgress.show(); - - bool res = m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(QDir::tempPath())).c_str(), - new MethodCallback<InstallationManager, void, float>(this, &InstallationManager::updateProgress), - new MethodCallback<InstallationManager, void, LPCWSTR>(this, &InstallationManager::dummyProgressFile), - new MethodCallback<InstallationManager, void, LPCWSTR>(this, &InstallationManager::report7ZipError)); - - m_InstallationProgress.hide(); - - return res; - } else { - return false; - } -} - - -QString InstallationManager::extractFile(const QString &fileName) -{ - if (unpackSingleFile(fileName)) { - return QDir::tempPath().append("/").append(QFileInfo(fileName).fileName()); - } else { - return QString(); - } -} - - -QString canonicalize(const QString &name) -{ - QString result(name); - if ((result.startsWith('/')) || - (result.startsWith('\\'))) { - result.remove(0, 1); - } - result.replace('/', '\\'); - - return result; -} - - -QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool flatten) -{ - QStringList files; - - foreach (const QString &file, filesOrig) { - files.append(canonicalize(file)); - } - - QStringList result; - - FileData* const *data; - size_t size; - m_CurrentArchive->getFileList(data, size); - - for (size_t i = 0; i < size; ++i) { - if (files.contains(ToQString(data[i]->getFileName()), Qt::CaseInsensitive)) { - const wchar_t *targetFile = data[i]->getFileName(); - if (flatten) { - targetFile = wcsrchr(data[i]->getFileName(), '\\'); - if (targetFile == NULL) { - targetFile = wcsrchr(data[i]->getFileName(), '/'); - } - if (targetFile == NULL) { - qCritical("failed to find backslash in %ls", data[i]->getFileName()); - continue; - } else { - // skip the slash - ++targetFile; - } - } - data[i]->setOutputFileName(targetFile); - - result.append(QDir::tempPath().append("/").append(ToQString(targetFile))); - - data[i]->setSkip(false); - m_TempFilesToDelete.insert(ToQString(targetFile)); - } else { - data[i]->setSkip(true); - } - } - - m_InstallationProgress.setWindowTitle(tr("Extracting files")); - m_InstallationProgress.setLabelText(QString()); - m_InstallationProgress.setValue(0); - m_InstallationProgress.setWindowModality(Qt::WindowModal); - m_InstallationProgress.show(); - - // unpack only the files we need for the installer - if (!m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(QDir::tempPath())).c_str(), - new MethodCallback<InstallationManager, void, float>(this, &InstallationManager::updateProgress), - new MethodCallback<InstallationManager, void, LPCWSTR>(this, &InstallationManager::dummyProgressFile), - new MethodCallback<InstallationManager, void, LPCWSTR>(this, &InstallationManager::report7ZipError))) { - m_InstallationProgress.hide(); - throw std::runtime_error("extracting failed"); - } - - m_InstallationProgress.hide(); - return result; -} - -IPluginInstaller::EInstallResult InstallationManager::installArchive(GuessedValue<QString> &modName, const QString &archiveName) -{ - GuessedValue<QString> temp(modName); - bool iniTweaks; - if (install(archiveName, temp, iniTweaks)) { - return IPluginInstaller::RESULT_SUCCESS; - } else { - return IPluginInstaller::RESULT_FAILED; - } -} - - -DirectoryTree *InstallationManager::createFilesTree() -{ - FileData* const *data; - size_t size; - m_CurrentArchive->getFileList(data, size); - - QScopedPointer<DirectoryTree> 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 = ToQString(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, 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(); -} - - -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; - } - } - 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 NULL; - } - } -} - - -void InstallationManager::updateProgress(float percentage) -{ - m_InstallationProgress.setValue(static_cast<int>(percentage * 100.0)); - if (m_InstallationProgress.wasCanceled()) { - m_CurrentArchive->cancel(); - m_InstallationProgress.reset(); - } -} - - -void InstallationManager::updateProgressFile(LPCWSTR fileName) -{ -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - m_InstallationProgress.setLabelText(QString::fromWCharArray(fileName)); -#else - m_InstallationProgress.setLabelText(QString::fromUtf16(fileName)); -#endif -} - - -void InstallationManager::report7ZipError(LPCWSTR errorMessage) -{ -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - m_InstallationProgress.setLabelText(QString::fromWCharArray(errorMessage)); -#else - reportError(QString::fromUtf16(errorMessage)); -#endif - m_CurrentArchive->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; -} - - -bool InstallationManager::testOverwrite(GuessedValue<QString> &modName, bool *merge) const -{ - QString targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory + "\\" + modName); - - while (QDir(targetDirectory).exists()) { - QueryOverwriteDialog overwriteDialog(m_ParentWidget); - if (overwriteDialog.exec()) { - 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.mid(0).append("\\").append(modName)); - } - } else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_REPLACE) { - // save original settings like categories. Because it makes sense - QString metaFilename = targetDirectory.mid(0).append("/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; -} - - -bool InstallationManager::ensureValidModName(GuessedValue<QString> &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<QString> &modName, int modID, - const QString &version, const QString &newestVersion, int categoryID) -{ - if (!ensureValidModName(modName)) { - return false; - } - - bool merge = false; - // determine target directory - if (!testOverwrite(modName, &merge)) { - return false; - } - - QString targetDirectoryNative = m_ModsDirectory.mid(0).append("\\").append(modName); - QString targetDirectory = QDir::fromNativeSeparators(targetDirectoryNative); - - qDebug("installing to \"%s\"", targetDirectoryNative.toUtf8().constData()); - - m_InstallationProgress.setWindowTitle(tr("Extracting files")); - m_InstallationProgress.setLabelText(QString()); - m_InstallationProgress.setValue(0); - m_InstallationProgress.setWindowModality(Qt::WindowModal); - m_InstallationProgress.show(); - if (!m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(targetDirectory)).c_str(), - new MethodCallback<InstallationManager, void, float>(this, &InstallationManager::updateProgress), - new MethodCallback<InstallationManager, void, LPCWSTR>(this, &InstallationManager::updateProgressFile), - new MethodCallback<InstallationManager, void, LPCWSTR>(this, &InstallationManager::report7ZipError))) { - m_InstallationProgress.hide(); - if (m_CurrentArchive->getLastError() == Archive::ERROR_EXTRACT_CANCELLED) { - return false; - } else { - throw std::runtime_error("extracting failed"); - } - } - - m_InstallationProgress.hide(); - - QSettings settingsFile(targetDirectory.mid(0).append("/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); - - return true; -} - - -bool InstallationManager::wasCancelled() -{ - return m_CurrentArchive->getLastError() == Archive::ERROR_EXTRACT_CANCELLED; -} - - -void InstallationManager::postInstallCleanup() const -{ - m_CurrentArchive->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<QString, std::function<bool(const QString&, const QString&)>> 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 - foreach (const QString &tempFile, m_TempFilesToDelete) { - QFileInfo fileInfo(QDir::tempPath() + "/" + tempFile); - QFile::remove(fileInfo.absoluteFilePath()); - directoriesToRemove.insert(fileInfo.absolutePath()); - } - - // try to delete each directory we had temporary files in. the call fails for non-empty directories which is ok - foreach (const QString &dir, directoriesToRemove) { - QDir().rmdir(dir); - } -} - - -bool InstallationManager::install(const QString &fileName, GuessedValue<QString> &modName, bool &hasIniTweaks) -{ - QFileInfo fileInfo(fileName); - if (m_SupportedExtensions.find(fileInfo.suffix()) == m_SupportedExtensions.end()) { - reportError(tr("File format \"%1\" not supported").arg(fileInfo.completeSuffix())); - 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 metaName = fileName.mid(0).append(".meta"); - if (QFile(metaName).exists()) { - QSettings metaFile(metaName, QSettings::IniFormat); - modID = metaFile.value("modID", 0).toInt(); - modName.update(metaFile.value("name", "").toString(), 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); - } - - 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).baseName(), 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)); - - // open the archive and construct the directory tree the installers work on - bool archiveOpen = m_CurrentArchive->open(ToWString(QDir::toNativeSeparators(fileName)).c_str(), - new MethodCallback<InstallationManager, void, LPSTR>(this, &InstallationManager::queryPassword)); - if (!archiveOpen) { - qDebug("integrated archiver can't open %s. errorcode %d", qPrintable(fileName), m_CurrentArchive->getLastError()); - } - ON_BLOCK_EXIT(std::bind(&InstallationManager::postInstallCleanup, this)); - - QScopedPointer<DirectoryTree> filesTree(archiveOpen ? createFilesTree() : NULL); - IPluginInstaller::EInstallResult installResult = IPluginInstaller::RESULT_NOTATTEMPTED; - - std::sort(m_Installers.begin(), m_Installers.end(), [] (IPluginInstaller *LHS, IPluginInstaller *RHS) { - return LHS->priority() > RHS->priority(); - }); - - foreach (IPluginInstaller *installer, m_Installers) { - // don't use inactive installers - if (!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<IPluginInstallerSimple*>(installer); - if ((installerSimple != NULL) && - (filesTree != NULL) && (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)) { - installResult = IPluginInstaller::RESULT_FAILED; - } - } - } - } - - { // custom case - IPluginInstallerCustom *installerCustom = dynamic_cast<IPluginInstallerCustom*>(installer); - if ((installerCustom != NULL) && - (((filesTree != NULL) && installer->isArchiveSupported(*filesTree)) || - ((filesTree == NULL) && installerCustom->isArchiveSupported(fileName)))) { - std::set<QString> installerExtensions = installerCustom->supportedExtensions(); - if (installerExtensions.find(fileInfo.suffix()) != installerExtensions.end()) { - installResult = installerCustom->install(modName, fileName, version, modID); - } - } - } - } 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 != NULL) { - 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; -} - - - -QString InstallationManager::getErrorString(Archive::Error errorCode) -{ - switch (errorCode) { - case Archive::ERROR_NONE: { - return tr("no error"); - } break; - case Archive::ERROR_LIBRARY_NOT_FOUND: { - return tr("7z.dll not found"); - } break; - case Archive::ERROR_LIBRARY_INVALID: { - return tr("7z.dll isn't valid"); - } break; - case Archive::ERROR_ARCHIVE_NOT_FOUND: { - return tr("archive not found"); - } break; - case Archive::ERROR_FAILED_TO_OPEN_ARCHIVE: { - return tr("failed to open archive"); - } break; - case Archive::ERROR_INVALID_ARCHIVE_FORMAT: { - return tr("unsupported archive type"); - } break; - case Archive::ERROR_LIBRARY_ERROR: { - return tr("internal library error"); - } break; - case Archive::ERROR_ARCHIVE_INVALID: { - return tr("archive invalid"); - } break; - default: { - // this probably means the archiver.dll is newer than this - return tr("unknown archive error"); - } break; - } -} - - -void InstallationManager::registerInstaller(IPluginInstaller *installer) -{ - m_Installers.push_back(installer); - installer->setInstallationManager(this); - IPluginInstallerCustom *installerCustom = dynamic_cast<IPluginInstallerCustom*>(installer); - if (installerCustom != NULL) { - std::set<QString> 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; -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "installationmanager.h"
+#include "utility.h"
+#include "report.h"
+#include "categories.h"
+#include "questionboxmemory.h"
+#include "settings.h"
+#include "queryoverwritedialog.h"
+#include "messagedialog.h"
+#include "iplugininstallersimple.h"
+#include "iplugininstallercustom.h"
+#include "nexusinterface.h"
+#include "selectiondialog.h"
+#include <scopeguard.h>
+#include <installationtester.h>
+#include <gameinfo.h>
+#include <utility.h>
+#include <QFileInfo>
+#include <QLibrary>
+#include <QInputDialog>
+#include <QRegExp>
+#include <QDir>
+#include <QMessageBox>
+#include <QSettings>
+#include <Shellapi.h>
+#include <QPushButton>
+#include <QApplication>
+#include <QDateTime>
+#include <QDirIterator>
+#include <boost/assign.hpp>
+#include <boost/scoped_ptr.hpp>
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+typedef Archive* (*CreateArchiveType)();
+
+
+
+template <typename T> T resolveFunction(QLibrary &lib, const char *name)
+{
+ T temp = reinterpret_cast<T>(lib.resolve(name));
+ if (temp == NULL) {
+ throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData());
+ }
+ return temp;
+}
+
+
+InstallationManager::InstallationManager(QWidget *parent)
+ : QObject(parent), m_ParentWidget(parent),
+ m_InstallationProgress(parent), m_SupportedExtensions(boost::assign::list_of("zip")("rar")("7z")("fomod")("001"))
+{
+ QLibrary archiveLib("dlls\\archive.dll");
+ if (!archiveLib.load()) {
+ throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString()));
+ }
+
+ CreateArchiveType CreateArchiveFunc = resolveFunction<CreateArchiveType>(archiveLib, "CreateArchive");
+
+ m_CurrentArchive = CreateArchiveFunc();
+ if (!m_CurrentArchive->isValid()) {
+ throw MyException(getErrorString(m_CurrentArchive->getLastError()));
+ }
+
+ m_InstallationProgress.setWindowFlags(m_InstallationProgress.windowFlags() & (~Qt::WindowContextHelpButtonHint));
+}
+
+
+InstallationManager::~InstallationManager()
+{
+ delete m_CurrentArchive;
+}
+
+
+void InstallationManager::queryPassword(LPSTR password)
+{
+ QString result = QInputDialog::getText(NULL, tr("Password required"), tr("Password"), QLineEdit::Password);
+ strncpy(password, result.toLocal8Bit().constData(), MAX_PASSWORD_LENGTH);
+}
+
+
+void InstallationManager::mapToArchive(const DirectoryTree::Node *node, std::wstring path, FileData * const *data)
+{
+ if (path.length() > 0) {
+ path.append(L"\\");
+ }
+
+ for (DirectoryTree::const_leaf_iterator iter = node->leafsBegin(); iter != node->leafsEnd(); ++iter) {
+ data[iter->getIndex()]->setSkip(false);
+ std::wstring temp = path.substr().append(ToWString(iter->getName()));
+ data[iter->getIndex()]->setOutputFileName(temp.c_str());
+ }
+
+ for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) {
+ if ((*iter)->getData().index != -1) {
+ data[(*iter)->getData().index]->setSkip(false);
+ std::wstring temp = path.substr().append(ToWString((*iter)->getData().name));
+ data[(*iter)->getData().index]->setOutputFileName(temp.c_str());
+ }
+ mapToArchive(*iter, path.substr().append(ToWString((*iter)->getData().name)), data);
+ }
+}
+
+
+void InstallationManager::mapToArchive(const DirectoryTree::Node *baseNode)
+{
+ FileData* const *data;
+ size_t size;
+ m_CurrentArchive->getFileList(data, size);
+
+ // first disable all files + folders, we will re-enable those present in baseNode
+ for (size_t i = 0; i < size; ++i) {
+ data[i]->setSkip(true);
+ }
+
+ std::wstring currentPath;
+
+ mapToArchive(baseNode, currentPath, data);
+}
+
+
+bool InstallationManager::unpackSingleFile(const QString &fileName)
+{
+ FileData* const *data;
+ size_t size;
+ m_CurrentArchive->getFileList(data, size);
+
+ QString baseName = QFileInfo(fileName).fileName();
+
+ bool available = false;
+ for (size_t i = 0; i < size; ++i) {
+ if (_wcsicmp(data[i]->getFileName(), ToWString(fileName).c_str()) == 0) {
+ available = true;
+ data[i]->setSkip(false);
+ data[i]->setOutputFileName(ToWString(baseName).c_str());
+ m_TempFilesToDelete.insert(baseName);
+ } else {
+ data[i]->setSkip(true);
+ }
+ }
+
+ if (available) {
+ m_InstallationProgress.setWindowTitle(tr("Extracting files"));
+ m_InstallationProgress.setLabelText(QString());
+ m_InstallationProgress.setValue(0);
+ m_InstallationProgress.setWindowModality(Qt::WindowModal);
+ m_InstallationProgress.show();
+
+ bool res = m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(QDir::tempPath())).c_str(),
+ new MethodCallback<InstallationManager, void, float>(this, &InstallationManager::updateProgress),
+ new MethodCallback<InstallationManager, void, LPCWSTR>(this, &InstallationManager::dummyProgressFile),
+ new MethodCallback<InstallationManager, void, LPCWSTR>(this, &InstallationManager::report7ZipError));
+
+ m_InstallationProgress.hide();
+
+ return res;
+ } else {
+ return false;
+ }
+}
+
+
+QString InstallationManager::extractFile(const QString &fileName)
+{
+ if (unpackSingleFile(fileName)) {
+ return QDir::tempPath().append("/").append(QFileInfo(fileName).fileName());
+ } else {
+ return QString();
+ }
+}
+
+
+QString canonicalize(const QString &name)
+{
+ QString result(name);
+ if ((result.startsWith('/')) ||
+ (result.startsWith('\\'))) {
+ result.remove(0, 1);
+ }
+ result.replace('/', '\\');
+
+ return result;
+}
+
+
+QStringList InstallationManager::extractFiles(const QStringList &filesOrig, bool flatten)
+{
+ QStringList files;
+
+ foreach (const QString &file, filesOrig) {
+ files.append(canonicalize(file));
+ }
+
+ QStringList result;
+
+ FileData* const *data;
+ size_t size;
+ m_CurrentArchive->getFileList(data, size);
+
+ for (size_t i = 0; i < size; ++i) {
+ if (files.contains(ToQString(data[i]->getFileName()), Qt::CaseInsensitive)) {
+ const wchar_t *targetFile = data[i]->getFileName();
+ if (flatten) {
+ targetFile = wcsrchr(data[i]->getFileName(), '\\');
+ if (targetFile == NULL) {
+ targetFile = wcsrchr(data[i]->getFileName(), '/');
+ }
+ if (targetFile == NULL) {
+ qCritical("failed to find backslash in %ls", data[i]->getFileName());
+ continue;
+ } else {
+ // skip the slash
+ ++targetFile;
+ }
+ }
+ data[i]->setOutputFileName(targetFile);
+
+ result.append(QDir::tempPath().append("/").append(ToQString(targetFile)));
+
+ data[i]->setSkip(false);
+ m_TempFilesToDelete.insert(ToQString(targetFile));
+ } else {
+ data[i]->setSkip(true);
+ }
+ }
+
+ m_InstallationProgress.setWindowTitle(tr("Extracting files"));
+ m_InstallationProgress.setLabelText(QString());
+ m_InstallationProgress.setValue(0);
+ m_InstallationProgress.setWindowModality(Qt::WindowModal);
+ m_InstallationProgress.show();
+
+ // unpack only the files we need for the installer
+ if (!m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(QDir::tempPath())).c_str(),
+ new MethodCallback<InstallationManager, void, float>(this, &InstallationManager::updateProgress),
+ new MethodCallback<InstallationManager, void, LPCWSTR>(this, &InstallationManager::dummyProgressFile),
+ new MethodCallback<InstallationManager, void, LPCWSTR>(this, &InstallationManager::report7ZipError))) {
+ m_InstallationProgress.hide();
+ throw std::runtime_error("extracting failed");
+ }
+
+ m_InstallationProgress.hide();
+ return result;
+}
+
+IPluginInstaller::EInstallResult InstallationManager::installArchive(GuessedValue<QString> &modName, const QString &archiveName)
+{
+ GuessedValue<QString> temp(modName);
+ bool iniTweaks;
+ if (install(archiveName, temp, iniTweaks)) {
+ return IPluginInstaller::RESULT_SUCCESS;
+ } else {
+ return IPluginInstaller::RESULT_FAILED;
+ }
+}
+
+
+DirectoryTree *InstallationManager::createFilesTree()
+{
+ FileData* const *data;
+ size_t size;
+ m_CurrentArchive->getFileList(data, size);
+
+ QScopedPointer<DirectoryTree> 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 = ToQString(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, 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();
+}
+
+
+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;
+ }
+ }
+ 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 NULL;
+ }
+ }
+}
+
+
+void InstallationManager::updateProgress(float percentage)
+{
+ m_InstallationProgress.setValue(static_cast<int>(percentage * 100.0));
+ if (m_InstallationProgress.wasCanceled()) {
+ m_CurrentArchive->cancel();
+ m_InstallationProgress.reset();
+ }
+}
+
+
+void InstallationManager::updateProgressFile(LPCWSTR fileName)
+{
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+ m_InstallationProgress.setLabelText(QString::fromWCharArray(fileName));
+#else
+ m_InstallationProgress.setLabelText(QString::fromUtf16(fileName));
+#endif
+}
+
+
+void InstallationManager::report7ZipError(LPCWSTR errorMessage)
+{
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+ m_InstallationProgress.setLabelText(QString::fromWCharArray(errorMessage));
+#else
+ reportError(QString::fromUtf16(errorMessage));
+#endif
+ m_CurrentArchive->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;
+}
+
+
+bool InstallationManager::testOverwrite(GuessedValue<QString> &modName, bool *merge) const
+{
+ QString targetDirectory = QDir::fromNativeSeparators(m_ModsDirectory + "\\" + modName);
+
+ while (QDir(targetDirectory).exists()) {
+ QueryOverwriteDialog overwriteDialog(m_ParentWidget);
+ if (overwriteDialog.exec()) {
+ 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.mid(0).append("\\").append(modName));
+ }
+ } else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_REPLACE) {
+ // save original settings like categories. Because it makes sense
+ QString metaFilename = targetDirectory.mid(0).append("/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;
+}
+
+
+bool InstallationManager::ensureValidModName(GuessedValue<QString> &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<QString> &modName, int modID,
+ const QString &version, const QString &newestVersion, int categoryID)
+{
+ if (!ensureValidModName(modName)) {
+ return false;
+ }
+
+ bool merge = false;
+ // determine target directory
+ if (!testOverwrite(modName, &merge)) {
+ return false;
+ }
+
+ QString targetDirectoryNative = m_ModsDirectory.mid(0).append("\\").append(modName);
+ QString targetDirectory = QDir::fromNativeSeparators(targetDirectoryNative);
+
+ qDebug("installing to \"%s\"", targetDirectoryNative.toUtf8().constData());
+
+ m_InstallationProgress.setWindowTitle(tr("Extracting files"));
+ m_InstallationProgress.setLabelText(QString());
+ m_InstallationProgress.setValue(0);
+ m_InstallationProgress.setWindowModality(Qt::WindowModal);
+ m_InstallationProgress.show();
+ if (!m_CurrentArchive->extract(ToWString(QDir::toNativeSeparators(targetDirectory)).c_str(),
+ new MethodCallback<InstallationManager, void, float>(this, &InstallationManager::updateProgress),
+ new MethodCallback<InstallationManager, void, LPCWSTR>(this, &InstallationManager::updateProgressFile),
+ new MethodCallback<InstallationManager, void, LPCWSTR>(this, &InstallationManager::report7ZipError))) {
+ m_InstallationProgress.hide();
+ if (m_CurrentArchive->getLastError() == Archive::ERROR_EXTRACT_CANCELLED) {
+ return false;
+ } else {
+ throw std::runtime_error("extracting failed");
+ }
+ }
+
+ m_InstallationProgress.hide();
+
+ QSettings settingsFile(targetDirectory.mid(0).append("/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);
+
+ return true;
+}
+
+
+bool InstallationManager::wasCancelled()
+{
+ return m_CurrentArchive->getLastError() == Archive::ERROR_EXTRACT_CANCELLED;
+}
+
+
+void InstallationManager::postInstallCleanup() const
+{
+ m_CurrentArchive->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<QString, std::function<bool(const QString&, const QString&)>> 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
+ foreach (const QString &tempFile, m_TempFilesToDelete) {
+ QFileInfo fileInfo(QDir::tempPath() + "/" + tempFile);
+ QFile::remove(fileInfo.absoluteFilePath());
+ directoriesToRemove.insert(fileInfo.absolutePath());
+ }
+
+ // try to delete each directory we had temporary files in. the call fails for non-empty directories which is ok
+ foreach (const QString &dir, directoriesToRemove) {
+ QDir().rmdir(dir);
+ }
+}
+
+
+bool InstallationManager::install(const QString &fileName, GuessedValue<QString> &modName, bool &hasIniTweaks)
+{
+ QFileInfo fileInfo(fileName);
+ if (m_SupportedExtensions.find(fileInfo.suffix()) == m_SupportedExtensions.end()) {
+ reportError(tr("File format \"%1\" not supported").arg(fileInfo.completeSuffix()));
+ 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 metaName = fileName.mid(0).append(".meta");
+ if (QFile(metaName).exists()) {
+ QSettings metaFile(metaName, QSettings::IniFormat);
+ modID = metaFile.value("modID", 0).toInt();
+ modName.update(metaFile.value("name", "").toString(), 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);
+ }
+
+ 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).baseName(), 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));
+
+ // open the archive and construct the directory tree the installers work on
+ bool archiveOpen = m_CurrentArchive->open(ToWString(QDir::toNativeSeparators(fileName)).c_str(),
+ new MethodCallback<InstallationManager, void, LPSTR>(this, &InstallationManager::queryPassword));
+ if (!archiveOpen) {
+ qDebug("integrated archiver can't open %s. errorcode %d", qPrintable(fileName), m_CurrentArchive->getLastError());
+ }
+ ON_BLOCK_EXIT(std::bind(&InstallationManager::postInstallCleanup, this));
+
+ QScopedPointer<DirectoryTree> filesTree(archiveOpen ? createFilesTree() : NULL);
+ IPluginInstaller::EInstallResult installResult = IPluginInstaller::RESULT_NOTATTEMPTED;
+
+ std::sort(m_Installers.begin(), m_Installers.end(), [] (IPluginInstaller *LHS, IPluginInstaller *RHS) {
+ return LHS->priority() > RHS->priority();
+ });
+
+ foreach (IPluginInstaller *installer, m_Installers) {
+ // don't use inactive installers
+ if (!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<IPluginInstallerSimple*>(installer);
+ if ((installerSimple != NULL) &&
+ (filesTree != NULL) && (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)) {
+ installResult = IPluginInstaller::RESULT_FAILED;
+ }
+ }
+ }
+ }
+
+ { // custom case
+ IPluginInstallerCustom *installerCustom = dynamic_cast<IPluginInstallerCustom*>(installer);
+ if ((installerCustom != NULL) &&
+ (((filesTree != NULL) && installer->isArchiveSupported(*filesTree)) ||
+ ((filesTree == NULL) && installerCustom->isArchiveSupported(fileName)))) {
+ std::set<QString> installerExtensions = installerCustom->supportedExtensions();
+ if (installerExtensions.find(fileInfo.suffix()) != installerExtensions.end()) {
+ installResult = installerCustom->install(modName, fileName, version, modID);
+ }
+ }
+ }
+ } 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 != NULL) {
+ 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;
+}
+
+
+
+QString InstallationManager::getErrorString(Archive::Error errorCode)
+{
+ switch (errorCode) {
+ case Archive::ERROR_NONE: {
+ return tr("no error");
+ } break;
+ case Archive::ERROR_LIBRARY_NOT_FOUND: {
+ return tr("7z.dll not found");
+ } break;
+ case Archive::ERROR_LIBRARY_INVALID: {
+ return tr("7z.dll isn't valid");
+ } break;
+ case Archive::ERROR_ARCHIVE_NOT_FOUND: {
+ return tr("archive not found");
+ } break;
+ case Archive::ERROR_FAILED_TO_OPEN_ARCHIVE: {
+ return tr("failed to open archive");
+ } break;
+ case Archive::ERROR_INVALID_ARCHIVE_FORMAT: {
+ return tr("unsupported archive type");
+ } break;
+ case Archive::ERROR_LIBRARY_ERROR: {
+ return tr("internal library error");
+ } break;
+ case Archive::ERROR_ARCHIVE_INVALID: {
+ return tr("archive invalid");
+ } break;
+ default: {
+ // this probably means the archiver.dll is newer than this
+ return tr("unknown archive error");
+ } break;
+ }
+}
+
+
+void InstallationManager::registerInstaller(IPluginInstaller *installer)
+{
+ m_Installers.push_back(installer);
+ installer->setInstallationManager(this);
+ IPluginInstallerCustom *installerCustom = dynamic_cast<IPluginInstallerCustom*>(installer);
+ if (installerCustom != NULL) {
+ std::set<QString> 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;
+}
diff --git a/src/installationmanager.h b/src/installationmanager.h index 3478fecf..d430c065 100644 --- a/src/installationmanager.h +++ b/src/installationmanager.h @@ -1,216 +1,216 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef INSTALLATIONMANAGER_H -#define INSTALLATIONMANAGER_H - - -#include <iinstallationmanager.h> -#include <iplugininstaller.h> -#include <guessedvalue.h> - -#include <QObject> -#define WIN32_LEAN_AND_MEAN -#include <Windows.h> -#include <archive.h> -#include <QProgressDialog> -#include <set> -#include <errorcodes.h> - - -/** - * @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 - * - "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 - -public: - - /** - * @brief constructor - * - * @param parent parent object. - **/ - explicit InstallationManager(QWidget *parent); - - ~InstallationManager(); - - /** - * @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<QString> &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<QString> &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<QString> &modName, bool *merge = NULL) const; - -private: - - void queryPassword(LPSTR password); - void updateProgress(float percentage); - void updateProgressFile(LPCWSTR fileName); - void report7ZipError(LPCWSTR errorMessage); - - void dummyProgressFile(LPCWSTR) {} - - 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); - - // recursive worker function for mapToArchive - void mapToArchive(const MOBase::DirectoryTree::Node *node, std::wstring 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 testOverwrite(const QString &modsDirectory, MOBase::GuessedValue<QString> &modName, bool *merge = NULL); - - bool doInstall(MOBase::GuessedValue<QString> &modName, - int modID, const QString &version, const QString &newestVersion, int categoryID); - - QString generateBackupName(const QString &directoryName) const; - - bool ensureValidModName(MOBase::GuessedValue<QString> &name) const; - - void postInstallCleanup() const; - -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; - } - }; - -private: - - QWidget *m_ParentWidget; - - QString m_ModsDirectory; - QString m_DownloadsDirectory; - - std::vector<MOBase::IPluginInstaller*> m_Installers; - std::set<QString, CaseInsensitive> m_SupportedExtensions; - - Archive *m_CurrentArchive; - QString m_CurrentFile; - - QProgressDialog m_InstallationProgress; - - std::set<QString> m_TempFilesToDelete; - -}; - - -#endif // INSTALLATIONMANAGER_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef INSTALLATIONMANAGER_H
+#define INSTALLATIONMANAGER_H
+
+
+#include <iinstallationmanager.h>
+#include <iplugininstaller.h>
+#include <guessedvalue.h>
+
+#include <QObject>
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <archive.h>
+#include <QProgressDialog>
+#include <set>
+#include <errorcodes.h>
+
+
+/**
+ * @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
+ * - "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
+
+public:
+
+ /**
+ * @brief constructor
+ *
+ * @param parent parent object.
+ **/
+ explicit InstallationManager(QWidget *parent);
+
+ ~InstallationManager();
+
+ /**
+ * @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<QString> &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<QString> &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<QString> &modName, bool *merge = NULL) const;
+
+private:
+
+ void queryPassword(LPSTR password);
+ void updateProgress(float percentage);
+ void updateProgressFile(LPCWSTR fileName);
+ void report7ZipError(LPCWSTR errorMessage);
+
+ void dummyProgressFile(LPCWSTR) {}
+
+ 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);
+
+ // recursive worker function for mapToArchive
+ void mapToArchive(const MOBase::DirectoryTree::Node *node, std::wstring 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 testOverwrite(const QString &modsDirectory, MOBase::GuessedValue<QString> &modName, bool *merge = NULL);
+
+ bool doInstall(MOBase::GuessedValue<QString> &modName,
+ int modID, const QString &version, const QString &newestVersion, int categoryID);
+
+ QString generateBackupName(const QString &directoryName) const;
+
+ bool ensureValidModName(MOBase::GuessedValue<QString> &name) const;
+
+ void postInstallCleanup() const;
+
+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;
+ }
+ };
+
+private:
+
+ QWidget *m_ParentWidget;
+
+ QString m_ModsDirectory;
+ QString m_DownloadsDirectory;
+
+ std::vector<MOBase::IPluginInstaller*> m_Installers;
+ std::set<QString, CaseInsensitive> m_SupportedExtensions;
+
+ Archive *m_CurrentArchive;
+ QString m_CurrentFile;
+
+ QProgressDialog m_InstallationProgress;
+
+ std::set<QString> m_TempFilesToDelete;
+
+};
+
+
+#endif // INSTALLATIONMANAGER_H
diff --git a/src/json.cpp b/src/json.cpp index ca2d728f..a280ba34 100644 --- a/src/json.cpp +++ b/src/json.cpp @@ -1,522 +1,522 @@ -/** - * QtJson - A simple class for parsing JSON data into a QVariant hierarchies and vice-versa. - * Copyright (C) 2011 Eeli Reilin - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/** - * \file json.cpp - */ - -#include "json.h" - -namespace QtJson { - static QString sanitizeString(QString str); - static QByteArray join(const QList<QByteArray> &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<typename T> - QByteArray serializeMap(const T &map, bool &success) { - QByteArray str = "{ "; - QList<QByteArray> 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; - } - - 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) { - 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<QByteArray> values; - const QVariantList list = data.toList(); - Q_FOREACH(const QVariant& v, list) { - QByteArray serializedValue = serialize(v); - if (serializedValue.isNull()) { - success = false; - break; - } - 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"; - } - } 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<qulonglong>()); - } else if (data.canConvert<qlonglong>()) { // any signed number? - str = QByteArray::number(data.value<qlonglong>()); - } else if (data.canConvert<long>()) { //TODO: this code is never executed - str = QString::number(data.value<long>()).toUtf8(); - } else if (data.canConvert<QString>()) { // 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(); - } - } - - QString serializeStr(const QVariant &data) { - return QString::fromUtf8(serialize(data)); - } - - 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<QByteArray> &list, const QByteArray &sep) { - QByteArray res; - Q_FOREACH(const QByteArray &i, list) { - if (!res.isEmpty()) { - res += sep; - } - res += i; - } - 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; - } - - // If there were no tokens, flag the failure and return an empty QVariant - success = false; - return QVariant(); - } - - /** - * parseObject - */ - static QVariant parseObject(const QString &json, int &index, bool &success) { - QVariantMap map; - int token; - - // Get rid of the whitespace and increment index - nextToken(json, index); - - // 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 (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); - - // 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); - - if (!success) { - return QVariantMap(); - } - - // Assign the value to the key in the map - map[name] = value; - } - } - - // Return the map successfully - return QVariant(map); - } - - /** - * parseArray - */ - static QVariant parseArray(const QString &json, int &index, bool &success) { - QVariantList list; - - nextToken(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) { - return QVariantList(); - } - list.push_back(value); - } - } - - return QVariant(list); - } - - /** - * parseString - */ - static QVariant parseString(const QString &json, int &index, bool &success) { - QString s; - QChar c; - - eatWhitespace(json, index); - - c = json[index++]; - - bool complete = false; - while(!complete) { - if (index == json.size()) { - break; - } - - c = json[index++]; - - if (c == '\"') { - complete = true; - break; - } else if (c == '\\') { - 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); - } - } - - if (!complete) { - success = false; - return QVariant(); - } - - return QVariant(s); - } - - /** - * parseNumber - */ - static QVariant parseNumber(const QString &json, int &index) { - eatWhitespace(json, index); - - int lastIndex = lastIndexOfNumber(json, index); - int charLength = (lastIndex - index) + 1; - QString numberStr; - - numberStr = json.mid(index, charLength); - - index = lastIndex + 1; - bool ok; - - if (numberStr.contains('.')) { - return QVariant(numberStr.toDouble(NULL)); - } 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; - } - } - - /** - * 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; - } - } - - 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; - } - } - } - - /** - * 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); - - 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? - - 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; - } - } - - // 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; - } - } - - return JsonTokenNone; - } -} //end namespace +/**
+ * QtJson - A simple class for parsing JSON data into a QVariant hierarchies and vice-versa.
+ * Copyright (C) 2011 Eeli Reilin
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file json.cpp
+ */
+
+#include "json.h"
+
+namespace QtJson {
+ static QString sanitizeString(QString str);
+ static QByteArray join(const QList<QByteArray> &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<typename T>
+ QByteArray serializeMap(const T &map, bool &success) {
+ QByteArray str = "{ ";
+ QList<QByteArray> 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;
+ }
+
+ 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) {
+ 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<QByteArray> values;
+ const QVariantList list = data.toList();
+ Q_FOREACH(const QVariant& v, list) {
+ QByteArray serializedValue = serialize(v);
+ if (serializedValue.isNull()) {
+ success = false;
+ break;
+ }
+ 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";
+ }
+ } 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<qulonglong>());
+ } else if (data.canConvert<qlonglong>()) { // any signed number?
+ str = QByteArray::number(data.value<qlonglong>());
+ } else if (data.canConvert<long>()) { //TODO: this code is never executed
+ str = QString::number(data.value<long>()).toUtf8();
+ } else if (data.canConvert<QString>()) { // 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();
+ }
+ }
+
+ QString serializeStr(const QVariant &data) {
+ return QString::fromUtf8(serialize(data));
+ }
+
+ 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<QByteArray> &list, const QByteArray &sep) {
+ QByteArray res;
+ Q_FOREACH(const QByteArray &i, list) {
+ if (!res.isEmpty()) {
+ res += sep;
+ }
+ res += i;
+ }
+ 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;
+ }
+
+ // If there were no tokens, flag the failure and return an empty QVariant
+ success = false;
+ return QVariant();
+ }
+
+ /**
+ * parseObject
+ */
+ static QVariant parseObject(const QString &json, int &index, bool &success) {
+ QVariantMap map;
+ int token;
+
+ // Get rid of the whitespace and increment index
+ nextToken(json, index);
+
+ // 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 (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);
+
+ // 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);
+
+ if (!success) {
+ return QVariantMap();
+ }
+
+ // Assign the value to the key in the map
+ map[name] = value;
+ }
+ }
+
+ // Return the map successfully
+ return QVariant(map);
+ }
+
+ /**
+ * parseArray
+ */
+ static QVariant parseArray(const QString &json, int &index, bool &success) {
+ QVariantList list;
+
+ nextToken(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) {
+ return QVariantList();
+ }
+ list.push_back(value);
+ }
+ }
+
+ return QVariant(list);
+ }
+
+ /**
+ * parseString
+ */
+ static QVariant parseString(const QString &json, int &index, bool &success) {
+ QString s;
+ QChar c;
+
+ eatWhitespace(json, index);
+
+ c = json[index++];
+
+ bool complete = false;
+ while(!complete) {
+ if (index == json.size()) {
+ break;
+ }
+
+ c = json[index++];
+
+ if (c == '\"') {
+ complete = true;
+ break;
+ } else if (c == '\\') {
+ 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);
+ }
+ }
+
+ if (!complete) {
+ success = false;
+ return QVariant();
+ }
+
+ return QVariant(s);
+ }
+
+ /**
+ * parseNumber
+ */
+ static QVariant parseNumber(const QString &json, int &index) {
+ eatWhitespace(json, index);
+
+ int lastIndex = lastIndexOfNumber(json, index);
+ int charLength = (lastIndex - index) + 1;
+ QString numberStr;
+
+ numberStr = json.mid(index, charLength);
+
+ index = lastIndex + 1;
+ bool ok;
+
+ if (numberStr.contains('.')) {
+ return QVariant(numberStr.toDouble(NULL));
+ } 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;
+ }
+ }
+
+ /**
+ * 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;
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+
+ /**
+ * 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);
+
+ 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?
+
+ 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;
+ }
+ }
+
+ // 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;
+ }
+ }
+
+ return JsonTokenNone;
+ }
+} //end namespace
@@ -1,94 +1,94 @@ -/** - * QtJson - A simple class for parsing JSON data into a QVariant hierarchies and vice-versa. - * Copyright (C) 2011 Eeli Reilin - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <http://www.gnu.org/licenses/>. - */ - -/** - * \file json.h - */ - -#ifndef JSON_H -#define JSON_H - -#include <QVariant> -#include <QString> - - -/** - * \namespace QtJson - * \brief A JSON data parser - * - * Json parses a JSON data into a QVariant hierarchy. - */ -namespace QtJson { - 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 - * \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. - * \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. - * \param success The success of the serialization - * - * \return QString Textual JSON representation - */ - QString serializeStr(const QVariant &data, bool &success); -} - -#endif //JSON_H +/**
+ * QtJson - A simple class for parsing JSON data into a QVariant hierarchies and vice-versa.
+ * Copyright (C) 2011 Eeli Reilin
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/**
+ * \file json.h
+ */
+
+#ifndef JSON_H
+#define JSON_H
+
+#include <QVariant>
+#include <QString>
+
+
+/**
+ * \namespace QtJson
+ * \brief A JSON data parser
+ *
+ * Json parses a JSON data into a QVariant hierarchy.
+ */
+namespace QtJson {
+ 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
+ * \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.
+ * \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.
+ * \param success The success of the serialization
+ *
+ * \return QString Textual JSON representation
+ */
+ QString serializeStr(const QVariant &data, bool &success);
+}
+
+#endif //JSON_H
diff --git a/src/loadmechanism.cpp b/src/loadmechanism.cpp index 426b3763..9d7e4e28 100644 --- a/src/loadmechanism.cpp +++ b/src/loadmechanism.cpp @@ -1,290 +1,290 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "loadmechanism.h" -#include "utility.h" -#include "util.h" -#include <gameinfo.h> -#include <appconfig.h> -#include <QFile> -#include <QFileInfo> -#include <QDir> -#include <QString> -#include <QMessageBox> -#include <QCryptographicHash> - - -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(ToString(GameInfo::instance().getOrganizerDirectory(), true).c_str()); - hintFile.close(); -} - - -void LoadMechanism::removeHintFile(QDir &targetDirectory) -{ - targetDirectory.remove("mo_path.txt"); -} - - -bool LoadMechanism::isDirectLoadingSupported() -{ - if (GameInfo::instance().getType() == GameInfo::TYPE_OBLIVION) { - // oblivion can be loaded directly if it's not the steam variant - QString gamePath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); - QDir gameDir(gamePath); - // test if this is the steam version of oblivion - if (QFile(gameDir.absoluteFilePath("steam_api.dll")).exists()) { - return false; - } - } - - // all other games work afaik - return true; -} - - -bool LoadMechanism::isScriptExtenderSupported() -{ - QString targetPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); - if ((QFile(targetPath.mid(0).append("/").append(ToQString(GameInfo::instance().getSEName())).append("_loader.exe")).exists()) || - (QFile(targetPath.mid(0).append("/").append(ToQString(GameInfo::instance().getSEName())).append("_steam_loader.dll")).exists())) { - return true; - } else { - return false; - } -} - - -bool LoadMechanism::isProxyDLLSupported() -{ - QString targetPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); - targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget())); - return QFile(targetPath).exists(); -} - - -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(); - - return hashLHS == hashRHS; -} - - -void LoadMechanism::deactivateScriptExtender() -{ - try { - QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); - - QString pluginsDirPath = gameDirectory; - pluginsDirPath.append("/data/").append(ToQString(GameInfo::instance().getSEName())).append("/plugins"); - - QDir pluginsDir(pluginsDirPath); - - QString hookDLLName = ToQString(AppConfig::hookDLLName()); - if (QFile(pluginsDir.absoluteFilePath(hookDLLName)).exists()) { - // remove dll from SE plugins directory - if (!pluginsDir.remove(hookDLLName)) { - throw MyException(QObject::tr("Failed to delete %1").arg(pluginsDir.absoluteFilePath(hookDLLName))); - } - } - - removeHintFile(pluginsDir); - } catch (const std::exception &e) { - QMessageBox::critical(NULL, QObject::tr("Failed to deactivate script extender loading"), e.what()); - } -} - - -void LoadMechanism::deactivateProxyDLL() -{ - try { - QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); - - QString targetPath = gameDirectory; - targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget())); - - QFile targetDLL(targetPath); - if (targetDLL.exists()) { - QString origFile = gameDirectory.mid(0).append(ToQString(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)); - } - } - } - - QDir dir(gameDirectory); - removeHintFile(dir); - } catch (const std::exception &e) { - QMessageBox::critical(NULL, QObject::tr("Failed to deactivate proxy-dll loading"), e.what()); - } -} - - -void LoadMechanism::activateScriptExtender() -{ - try { - QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); - - QString pluginsDirPath = gameDirectory; - pluginsDirPath.append("/data/").append(ToQString(GameInfo::instance().getSEName())).append("/plugins"); - - QDir pluginsDir(pluginsDirPath); - - if (!pluginsDir.exists()) { - pluginsDir.mkpath("."); - } - - QString targetPath = pluginsDir.absoluteFilePath(ToQString(AppConfig::hookDLLName())); - QString hookDLLPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())).append("/").append(ToQString(AppConfig::hookDLLName())); - - QFile dllFile(targetPath); - - if (dllFile.exists()) { - // may be outdated - if (!hashIdentical(targetPath, hookDLLPath)) { - dllFile.remove(); - } - } - - if (!dllFile.exists()) { - // install dll to SE plugins - if (!QFile::copy(hookDLLPath, targetPath)) { - throw MyException(QObject::tr("Failed to copy %1 to %2").arg(hookDLLPath, targetPath)); - } - } - writeHintFile(pluginsDir); - } catch (const std::exception &e) { - QMessageBox::critical(NULL, QObject::tr("Failed to set up script extender loading"), e.what()); - } -} - - -void LoadMechanism::activateProxyDLL() -{ - try { - QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())); - - QString targetPath = gameDirectory; - targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget())); - QFile targetDLL(targetPath); - if (!targetDLL.exists()) { - return; - } - - QString sourcePath = QDir::fromNativeSeparators( - ToQString(GameInfo::instance().getOrganizerDirectory())).append("/").append(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 = gameDirectory; - origFile.append("/").append(ToQString(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)); - } - } - writeHintFile(QDir(gameDirectory)); - } catch (const std::exception &e) { - QMessageBox::critical(NULL, QObject::tr("Failed to set up proxy-dll loading"), e.what()); - } -} - - -void LoadMechanism::activate(EMechanism mechanism) -{ - switch (mechanism) { - case LOAD_MODORGANIZER: { - deactivateProxyDLL(); - deactivateScriptExtender(); - } break; - case LOAD_SCRIPTEXTENDER: { - deactivateProxyDLL(); - activateScriptExtender(); - } break; - case LOAD_PROXYDLL: { - deactivateScriptExtender(); - activateProxyDLL(); - } break; - } -} - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "loadmechanism.h"
+#include "utility.h"
+#include "util.h"
+#include <gameinfo.h>
+#include <appconfig.h>
+#include <QFile>
+#include <QFileInfo>
+#include <QDir>
+#include <QString>
+#include <QMessageBox>
+#include <QCryptographicHash>
+
+
+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(ToString(GameInfo::instance().getOrganizerDirectory(), true).c_str());
+ hintFile.close();
+}
+
+
+void LoadMechanism::removeHintFile(QDir &targetDirectory)
+{
+ targetDirectory.remove("mo_path.txt");
+}
+
+
+bool LoadMechanism::isDirectLoadingSupported()
+{
+ if (GameInfo::instance().getType() == GameInfo::TYPE_OBLIVION) {
+ // oblivion can be loaded directly if it's not the steam variant
+ QString gamePath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
+ QDir gameDir(gamePath);
+ // test if this is the steam version of oblivion
+ if (QFile(gameDir.absoluteFilePath("steam_api.dll")).exists()) {
+ return false;
+ }
+ }
+
+ // all other games work afaik
+ return true;
+}
+
+
+bool LoadMechanism::isScriptExtenderSupported()
+{
+ QString targetPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
+ if ((QFile(targetPath.mid(0).append("/").append(ToQString(GameInfo::instance().getSEName())).append("_loader.exe")).exists()) ||
+ (QFile(targetPath.mid(0).append("/").append(ToQString(GameInfo::instance().getSEName())).append("_steam_loader.dll")).exists())) {
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+bool LoadMechanism::isProxyDLLSupported()
+{
+ QString targetPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
+ targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget()));
+ return QFile(targetPath).exists();
+}
+
+
+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();
+
+ return hashLHS == hashRHS;
+}
+
+
+void LoadMechanism::deactivateScriptExtender()
+{
+ try {
+ QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
+
+ QString pluginsDirPath = gameDirectory;
+ pluginsDirPath.append("/data/").append(ToQString(GameInfo::instance().getSEName())).append("/plugins");
+
+ QDir pluginsDir(pluginsDirPath);
+
+ QString hookDLLName = ToQString(AppConfig::hookDLLName());
+ if (QFile(pluginsDir.absoluteFilePath(hookDLLName)).exists()) {
+ // remove dll from SE plugins directory
+ if (!pluginsDir.remove(hookDLLName)) {
+ throw MyException(QObject::tr("Failed to delete %1").arg(pluginsDir.absoluteFilePath(hookDLLName)));
+ }
+ }
+
+ removeHintFile(pluginsDir);
+ } catch (const std::exception &e) {
+ QMessageBox::critical(NULL, QObject::tr("Failed to deactivate script extender loading"), e.what());
+ }
+}
+
+
+void LoadMechanism::deactivateProxyDLL()
+{
+ try {
+ QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
+
+ QString targetPath = gameDirectory;
+ targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget()));
+
+ QFile targetDLL(targetPath);
+ if (targetDLL.exists()) {
+ QString origFile = gameDirectory.mid(0).append(ToQString(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));
+ }
+ }
+ }
+
+ QDir dir(gameDirectory);
+ removeHintFile(dir);
+ } catch (const std::exception &e) {
+ QMessageBox::critical(NULL, QObject::tr("Failed to deactivate proxy-dll loading"), e.what());
+ }
+}
+
+
+void LoadMechanism::activateScriptExtender()
+{
+ try {
+ QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
+
+ QString pluginsDirPath = gameDirectory;
+ pluginsDirPath.append("/data/").append(ToQString(GameInfo::instance().getSEName())).append("/plugins");
+
+ QDir pluginsDir(pluginsDirPath);
+
+ if (!pluginsDir.exists()) {
+ pluginsDir.mkpath(".");
+ }
+
+ QString targetPath = pluginsDir.absoluteFilePath(ToQString(AppConfig::hookDLLName()));
+ QString hookDLLPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())).append("/").append(ToQString(AppConfig::hookDLLName()));
+
+ QFile dllFile(targetPath);
+
+ if (dllFile.exists()) {
+ // may be outdated
+ if (!hashIdentical(targetPath, hookDLLPath)) {
+ dllFile.remove();
+ }
+ }
+
+ if (!dllFile.exists()) {
+ // install dll to SE plugins
+ if (!QFile::copy(hookDLLPath, targetPath)) {
+ throw MyException(QObject::tr("Failed to copy %1 to %2").arg(hookDLLPath, targetPath));
+ }
+ }
+ writeHintFile(pluginsDir);
+ } catch (const std::exception &e) {
+ QMessageBox::critical(NULL, QObject::tr("Failed to set up script extender loading"), e.what());
+ }
+}
+
+
+void LoadMechanism::activateProxyDLL()
+{
+ try {
+ QString gameDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory()));
+
+ QString targetPath = gameDirectory;
+ targetPath.append("/").append(ToQString(AppConfig::proxyDLLTarget()));
+ QFile targetDLL(targetPath);
+ if (!targetDLL.exists()) {
+ return;
+ }
+
+ QString sourcePath = QDir::fromNativeSeparators(
+ ToQString(GameInfo::instance().getOrganizerDirectory())).append("/").append(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 = gameDirectory;
+ origFile.append("/").append(ToQString(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));
+ }
+ }
+ writeHintFile(QDir(gameDirectory));
+ } catch (const std::exception &e) {
+ QMessageBox::critical(NULL, QObject::tr("Failed to set up proxy-dll loading"), e.what());
+ }
+}
+
+
+void LoadMechanism::activate(EMechanism mechanism)
+{
+ switch (mechanism) {
+ case LOAD_MODORGANIZER: {
+ deactivateProxyDLL();
+ deactivateScriptExtender();
+ } break;
+ case LOAD_SCRIPTEXTENDER: {
+ deactivateProxyDLL();
+ activateScriptExtender();
+ } break;
+ case LOAD_PROXYDLL: {
+ deactivateScriptExtender();
+ activateProxyDLL();
+ } break;
+ }
+}
+
diff --git a/src/loadmechanism.h b/src/loadmechanism.h index 90ed5f4a..43a8dd6c 100644 --- a/src/loadmechanism.h +++ b/src/loadmechanism.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#ifndef LOADMECHANISM_H
#define LOADMECHANISM_H
diff --git a/src/lockeddialog.cpp b/src/lockeddialog.cpp index 065d9270..c688fff6 100644 --- a/src/lockeddialog.cpp +++ b/src/lockeddialog.cpp @@ -1,75 +1,75 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "lockeddialog.h" -#include "ui_lockeddialog.h" -#include <QResizeEvent> - - -LockedDialog::LockedDialog(QWidget *parent, const QString &text, bool unlockButton) - : QDialog(parent) - , ui(new Ui::LockedDialog) - , m_UnlockClicked(false) -{ - ui->setupUi(this); - - this->setWindowFlags(this->windowFlags() | Qt::ToolTip | Qt::FramelessWindowHint); - - if (parent != NULL) { - QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); - position.rx() -= this->width() / 2; - position.ry() -= this->height() / 2; - move(position); - } - - if (text.length() > 0) { - ui->label->setText(text); - } - if (!unlockButton) { - ui->unlockButton->hide(); - } -} - -LockedDialog::~LockedDialog() -{ - delete ui; -} - - -void LockedDialog::setProcessName(const QString &name) -{ - ui->processLabel->setText(name); -} - - -void LockedDialog::resizeEvent(QResizeEvent *event) -{ - QWidget *par = parentWidget(); - if (par != NULL) { - QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); - position.rx() -= event->size().width() / 2; - position.ry() -= event->size().height() / 2; - move(position); - } -} - -void LockedDialog::on_unlockButton_clicked() -{ - m_UnlockClicked = true; -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "lockeddialog.h"
+#include "ui_lockeddialog.h"
+#include <QResizeEvent>
+
+
+LockedDialog::LockedDialog(QWidget *parent, const QString &text, bool unlockButton)
+ : QDialog(parent)
+ , ui(new Ui::LockedDialog)
+ , m_UnlockClicked(false)
+{
+ ui->setupUi(this);
+
+ this->setWindowFlags(this->windowFlags() | Qt::ToolTip | Qt::FramelessWindowHint);
+
+ if (parent != NULL) {
+ QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2));
+ position.rx() -= this->width() / 2;
+ position.ry() -= this->height() / 2;
+ move(position);
+ }
+
+ if (text.length() > 0) {
+ ui->label->setText(text);
+ }
+ if (!unlockButton) {
+ ui->unlockButton->hide();
+ }
+}
+
+LockedDialog::~LockedDialog()
+{
+ delete ui;
+}
+
+
+void LockedDialog::setProcessName(const QString &name)
+{
+ ui->processLabel->setText(name);
+}
+
+
+void LockedDialog::resizeEvent(QResizeEvent *event)
+{
+ QWidget *par = parentWidget();
+ if (par != NULL) {
+ QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2));
+ position.rx() -= event->size().width() / 2;
+ position.ry() -= event->size().height() / 2;
+ move(position);
+ }
+}
+
+void LockedDialog::on_unlockButton_clicked()
+{
+ m_UnlockClicked = true;
+}
diff --git a/src/lockeddialog.h b/src/lockeddialog.h index 0f3b29b0..60af425d 100644 --- a/src/lockeddialog.h +++ b/src/lockeddialog.h @@ -1,67 +1,67 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef LOCKEDDIALOG_H -#define LOCKEDDIALOG_H - -#include <QDialog> - -namespace Ui { - class LockedDialog; -} - -/** - * a small borderless dialog displayed while the Mod Organizer UI is locked - * The dialog contains only a label and a button to force the UI to be unlocked - * - * The UI gets locked while running external applications since they may modify the - * data on which Mod Organizer works. After the UI is unlocked (manually or after the - * external application closed) MO will refresh all of its data sources - **/ -class LockedDialog : public QDialog -{ - Q_OBJECT - -public: - explicit LockedDialog(QWidget *parent = 0, const QString &text = "", bool unlockButton = true); - ~LockedDialog(); - - /** - * @brief see if the user clicked the unlock-button - * - * @return true if the user clicked the unlock button - **/ - bool unlockClicked() const { return m_UnlockClicked; } - - void setProcessName(const QString &name); - -protected: - - virtual void resizeEvent(QResizeEvent *event); - -private slots: - - void on_unlockButton_clicked(); - -private: - Ui::LockedDialog *ui; - bool m_UnlockClicked; -}; - -#endif // LOCKEDDIALOG_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef LOCKEDDIALOG_H
+#define LOCKEDDIALOG_H
+
+#include <QDialog>
+
+namespace Ui {
+ class LockedDialog;
+}
+
+/**
+ * a small borderless dialog displayed while the Mod Organizer UI is locked
+ * The dialog contains only a label and a button to force the UI to be unlocked
+ *
+ * The UI gets locked while running external applications since they may modify the
+ * data on which Mod Organizer works. After the UI is unlocked (manually or after the
+ * external application closed) MO will refresh all of its data sources
+ **/
+class LockedDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit LockedDialog(QWidget *parent = 0, const QString &text = "", bool unlockButton = true);
+ ~LockedDialog();
+
+ /**
+ * @brief see if the user clicked the unlock-button
+ *
+ * @return true if the user clicked the unlock button
+ **/
+ bool unlockClicked() const { return m_UnlockClicked; }
+
+ void setProcessName(const QString &name);
+
+protected:
+
+ virtual void resizeEvent(QResizeEvent *event);
+
+private slots:
+
+ void on_unlockButton_clicked();
+
+private:
+ Ui::LockedDialog *ui;
+ bool m_UnlockClicked;
+};
+
+#endif // LOCKEDDIALOG_H
diff --git a/src/logbuffer.cpp b/src/logbuffer.cpp index ef0549b0..17417bf9 100644 --- a/src/logbuffer.cpp +++ b/src/logbuffer.cpp @@ -1,261 +1,261 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "logbuffer.h" -#include "report.h" -#include <QMutexLocker> -#include <QFile> -#include <QIcon> -#include <QDateTime> -#include <Windows.h> - -QScopedPointer<LogBuffer> LogBuffer::s_Instance; -QMutex LogBuffer::s_Mutex; - - -LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType, const QString &outputFileName) - : QAbstractItemModel(NULL), m_OutFileName(outputFileName), m_ShutDown(false), - m_MinMsgType(minMsgType), m_NumMessages(0) -{ - m_Messages.resize(messageCount); -} - -LogBuffer::~LogBuffer() -{ -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - qInstallMessageHandler(0); -#else - qInstallMsgHandler(0); -#endif -// if (!m_ShutDown) { - 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(), m_NumMessages, 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(m_Messages.size(), 0)); - } - ++m_NumMessages; - if (type >= QtCriticalMsg) { - write(); - } - } -} - - -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()) ? 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); - - if (!s_Instance.isNull()) { - s_Instance.reset(); - } - s_Instance.reset(new LogBuffer(messageCount, minMsgType, outputFileName)); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - qInstallMessageHandler(LogBuffer::log); -#else - qInstallMsgHandler(LogBuffer::log); -#endif -} - -char LogBuffer::msgTypeID(QtMsgType type) -{ - switch (type) { - case QtDebugMsg: return 'D'; - case QtWarningMsg: return 'W'; - case QtCriticalMsg: return 'C'; - case QtFatalMsg: return 'F'; - default: return '?'; - } -} - -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - -void LogBuffer::log(QtMsgType type, const QMessageLogContext &context, const QString &message) -{ - QMutexLocker guard(&s_Mutex); - if (!s_Instance.isNull()) { - s_Instance->logMessage(type, message); - } -// fprintf(stdout, "(%s:%u) %s\n", context.file, context.line, qPrintable(message)); - if (type == QtDebugMsg) { - fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), qPrintable(message)); - } else { - fprintf(stdout, "%s [%c] (%s:%u) %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), - context.file, context.line, qPrintable(message)); - } - fflush(stdout); -} - -#else - -void LogBuffer::log(QtMsgType type, const char *message) -{ - QMutexLocker guard(&s_Mutex); - if (!s_Instance.isNull()) { - s_Instance->logMessage(type, message); - } - fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), message); - fflush(stdout); -} - -#endif - - -QModelIndex LogBuffer::index(int row, int column, const QModelIndex&) const -{ - return createIndex(row, column, row); -} - -QModelIndex LogBuffer::parent(const QModelIndex&) const -{ - return QModelIndex(); -} - -int LogBuffer::rowCount(const QModelIndex &parent) const -{ - if (parent.isValid()) - return 0; - else - return std::min(m_NumMessages, m_Messages.size()); -} - -int LogBuffer::columnCount(const QModelIndex&) const -{ - return 2; -} - - -QVariant LogBuffer::data(const QModelIndex &index, int role) const -{ - unsigned offset = m_NumMessages < m_Messages.size() ? 0 - : 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.at(msgIndex).time; - } else if (index.column() == 1) { - const QString &msg = m_Messages.at(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.at(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.at(msgIndex).type) { - case QtDebugMsg: return "D"; - case QtWarningMsg: return "W"; - case QtCriticalMsg: return "C"; - case QtFatalMsg: return "F"; - } - } - } break; - } - return QVariant(); -} - -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 log(const char *format, ...) -{ - va_list argList; - va_start(argList, format); - - static const int BUFFERSIZE = 1000; - - char buffer[BUFFERSIZE + 1]; - buffer[BUFFERSIZE] = '\0'; - - vsnprintf(buffer, BUFFERSIZE, format, argList); - - qCritical("%s", buffer); - - va_end(argList); -} - - - -QString LogBuffer::Message::toString() const -{ - return QString("%1 [%2] %3").arg(time.toString()).arg(msgTypeID(type)).arg(message); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "logbuffer.h"
+#include "report.h"
+#include <QMutexLocker>
+#include <QFile>
+#include <QIcon>
+#include <QDateTime>
+#include <Windows.h>
+
+QScopedPointer<LogBuffer> LogBuffer::s_Instance;
+QMutex LogBuffer::s_Mutex;
+
+
+LogBuffer::LogBuffer(int messageCount, QtMsgType minMsgType, const QString &outputFileName)
+ : QAbstractItemModel(NULL), m_OutFileName(outputFileName), m_ShutDown(false),
+ m_MinMsgType(minMsgType), m_NumMessages(0)
+{
+ m_Messages.resize(messageCount);
+}
+
+LogBuffer::~LogBuffer()
+{
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+ qInstallMessageHandler(0);
+#else
+ qInstallMsgHandler(0);
+#endif
+// if (!m_ShutDown) {
+ 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(), m_NumMessages, 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(m_Messages.size(), 0));
+ }
+ ++m_NumMessages;
+ if (type >= QtCriticalMsg) {
+ write();
+ }
+ }
+}
+
+
+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()) ? 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);
+
+ if (!s_Instance.isNull()) {
+ s_Instance.reset();
+ }
+ s_Instance.reset(new LogBuffer(messageCount, minMsgType, outputFileName));
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+ qInstallMessageHandler(LogBuffer::log);
+#else
+ qInstallMsgHandler(LogBuffer::log);
+#endif
+}
+
+char LogBuffer::msgTypeID(QtMsgType type)
+{
+ switch (type) {
+ case QtDebugMsg: return 'D';
+ case QtWarningMsg: return 'W';
+ case QtCriticalMsg: return 'C';
+ case QtFatalMsg: return 'F';
+ default: return '?';
+ }
+}
+
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+
+void LogBuffer::log(QtMsgType type, const QMessageLogContext &context, const QString &message)
+{
+ QMutexLocker guard(&s_Mutex);
+ if (!s_Instance.isNull()) {
+ s_Instance->logMessage(type, message);
+ }
+// fprintf(stdout, "(%s:%u) %s\n", context.file, context.line, qPrintable(message));
+ if (type == QtDebugMsg) {
+ fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), qPrintable(message));
+ } else {
+ fprintf(stdout, "%s [%c] (%s:%u) %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type),
+ context.file, context.line, qPrintable(message));
+ }
+ fflush(stdout);
+}
+
+#else
+
+void LogBuffer::log(QtMsgType type, const char *message)
+{
+ QMutexLocker guard(&s_Mutex);
+ if (!s_Instance.isNull()) {
+ s_Instance->logMessage(type, message);
+ }
+ fprintf(stdout, "%s [%c] %s\n", qPrintable(QTime::currentTime().toString()), msgTypeID(type), message);
+ fflush(stdout);
+}
+
+#endif
+
+
+QModelIndex LogBuffer::index(int row, int column, const QModelIndex&) const
+{
+ return createIndex(row, column, row);
+}
+
+QModelIndex LogBuffer::parent(const QModelIndex&) const
+{
+ return QModelIndex();
+}
+
+int LogBuffer::rowCount(const QModelIndex &parent) const
+{
+ if (parent.isValid())
+ return 0;
+ else
+ return std::min(m_NumMessages, m_Messages.size());
+}
+
+int LogBuffer::columnCount(const QModelIndex&) const
+{
+ return 2;
+}
+
+
+QVariant LogBuffer::data(const QModelIndex &index, int role) const
+{
+ unsigned offset = m_NumMessages < m_Messages.size() ? 0
+ : 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.at(msgIndex).time;
+ } else if (index.column() == 1) {
+ const QString &msg = m_Messages.at(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.at(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.at(msgIndex).type) {
+ case QtDebugMsg: return "D";
+ case QtWarningMsg: return "W";
+ case QtCriticalMsg: return "C";
+ case QtFatalMsg: return "F";
+ }
+ }
+ } break;
+ }
+ return QVariant();
+}
+
+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 log(const char *format, ...)
+{
+ va_list argList;
+ va_start(argList, format);
+
+ static const int BUFFERSIZE = 1000;
+
+ char buffer[BUFFERSIZE + 1];
+ buffer[BUFFERSIZE] = '\0';
+
+ vsnprintf(buffer, BUFFERSIZE, format, argList);
+
+ qCritical("%s", buffer);
+
+ va_end(argList);
+}
+
+
+
+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 caada1d9..f69d33fa 100644 --- a/src/logbuffer.h +++ b/src/logbuffer.h @@ -1,99 +1,99 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef LOGBUFFER_H -#define LOGBUFFER_H - -#include <QObject> -#include <QMutex> -#include <QScopedPointer> -#include <QStringListModel> -#include <QTime> -#include <vector> - - -class LogBuffer : public QAbstractItemModel -{ - Q_OBJECT - -public: - - static void init(int messageCount, QtMsgType minMsgType, const QString &outputFileName); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - static void log(QtMsgType type, const QMessageLogContext &context, const QString &message); -#else - static void log(QtMsgType type, const char *message); -#endif - - static void writeNow(); - static void cleanQuit(); - - static LogBuffer *instance() { return s_Instance.data(); } - -public: - - virtual ~LogBuffer(); - - void logMessage(QtMsgType type, const QString &message); - - // 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; - -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; - - static char msgTypeID(QtMsgType type); - -private: - - struct Message { - QtMsgType type; - QTime time; - QString message; - QString toString() const; - }; - -private: - - static QScopedPointer<LogBuffer> s_Instance; - static QMutex s_Mutex; - - QString m_OutFileName; - bool m_ShutDown; - QtMsgType m_MinMsgType; - unsigned int m_NumMessages; - std::vector<Message> m_Messages; - -}; - -#endif // LOGBUFFER_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef LOGBUFFER_H
+#define LOGBUFFER_H
+
+#include <QObject>
+#include <QMutex>
+#include <QScopedPointer>
+#include <QStringListModel>
+#include <QTime>
+#include <vector>
+
+
+class LogBuffer : public QAbstractItemModel
+{
+ Q_OBJECT
+
+public:
+
+ static void init(int messageCount, QtMsgType minMsgType, const QString &outputFileName);
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+ static void log(QtMsgType type, const QMessageLogContext &context, const QString &message);
+#else
+ static void log(QtMsgType type, const char *message);
+#endif
+
+ static void writeNow();
+ static void cleanQuit();
+
+ static LogBuffer *instance() { return s_Instance.data(); }
+
+public:
+
+ virtual ~LogBuffer();
+
+ void logMessage(QtMsgType type, const QString &message);
+
+ // 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;
+
+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;
+
+ static char msgTypeID(QtMsgType type);
+
+private:
+
+ struct Message {
+ QtMsgType type;
+ QTime time;
+ QString message;
+ QString toString() const;
+ };
+
+private:
+
+ static QScopedPointer<LogBuffer> s_Instance;
+ static QMutex s_Mutex;
+
+ QString m_OutFileName;
+ bool m_ShutDown;
+ QtMsgType m_MinMsgType;
+ unsigned int m_NumMessages;
+ std::vector<Message> m_Messages;
+
+};
+
+#endif // LOGBUFFER_H
diff --git a/src/loghighlighter.cpp b/src/loghighlighter.cpp index 1c95fc2f..f43e5662 100644 --- a/src/loghighlighter.cpp +++ b/src/loghighlighter.cpp @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#include "loghighlighter.h"
#include <QRegExp>
diff --git a/src/loghighlighter.h b/src/loghighlighter.h index 127d3228..55c15142 100644 --- a/src/loghighlighter.h +++ b/src/loghighlighter.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#ifndef LOGHIGHLIGHTER_H
#define LOGHIGHLIGHTER_H
diff --git a/src/main.cpp b/src/main.cpp index ecd12345..1ae3e932 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,581 +1,581 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - - -#ifdef LEAK_CHECK_WITH_VLD -#include <wchar.h> -#include <vld.h> -#endif // LEAK_CHECK_WITH_VLD - -#include <QApplication> -#include <QPushButton> -#include <QListWidget> -#include <QComboBox> -#include <QCheckBox> -#include <QDir> -#include <QFileInfo> -#include <QSettings> -#include <QWhatsThis> -#include <QToolBar> -#include <QFileDialog> -#define WIN32_LEAN_AND_MEAN -#include <windows.h> -#include <DbgHelp.h> -#include <cstdarg> -#include <inject.h> -#include <appconfig.h> -#include <utility.h> -#include <stdexcept> -#include "mainwindow.h" -#include "report.h" -#include "modlist.h" -#include "profile.h" -#include "gameinfo.h" -#include "fallout3info.h" -#include "falloutnvinfo.h" -#include "oblivioninfo.h" -#include "skyriminfo.h" -#include "spawn.h" -#include "executableslist.h" -#include "singleinstance.h" -#include "utility.h" -#include "helper.h" -#include "logbuffer.h" -#include "selectiondialog.h" -#include "moapplication.h" -#include "tutorialmanager.h" -#include <iostream> -#include <QMessageBox> -#include <QSharedMemory> -#include <QBuffer> -#include <QSplashScreen> -#include <QDirIterator> -#include <QDesktopServices> -#include <ShellAPI.h> -#include <eh.h> -#include <windows_error.h> -#include <boost/scoped_array.hpp> - - -#pragma comment(linker, "/manifestDependency:\"name='dlls' processorArchitecture='x86' version='1.0.0.0' type='win32' \"") - - -using namespace MOBase; -using namespace MOShared; - - -// set up required folders (for a first install or after an update or to fix a broken installation) -bool bootstrap() -{ - GameInfo &gameInfo = GameInfo::instance(); - - // remove the temporary backup directory in case we're restarting after an update - QString moDirectory = QDir::fromNativeSeparators(ToQString(gameInfo.getOrganizerDirectory())); - QString backupDirectory = moDirectory.mid(0).append("/update_backup"); - if (QDir(backupDirectory).exists()) { - shellDelete(QStringList(backupDirectory)); - } - - // cycle logfile - removeOldFiles(ToQString(GameInfo::instance().getLogDir()), "ModOrganizer*.log", 5, QDir::Name); - - // create organizer directories - QString dirNames[] = { - QDir::fromNativeSeparators(ToQString(gameInfo.getProfilesDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getModsDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getDownloadDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getOverwriteDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getLogDir())), - QDir::fromNativeSeparators(ToQString(gameInfo.getTutorialDir())) - }; - static const int NUM_DIRECTORIES = sizeof(dirNames) / sizeof(QString); - - // optimistic run: try to simply create the directories: - for (int i = 0; i < NUM_DIRECTORIES; ++i) { - if (!QDir(dirNames[i]).exists()) { - QDir().mkdir(dirNames[i]); - } - } - - // verify all directories exist and are writable, - // otherwise invoke the helper to create them and make them writable - for (int i = 0; i < NUM_DIRECTORIES; ++i) { - QFileInfo fileInfo(dirNames[i]); - if (!fileInfo.exists() || !fileInfo.isWritable()) { - if (QMessageBox::question(NULL, QObject::tr("Permissions required"), - QObject::tr("The current user account doesn't have the required access rights to run " - "Mod Organizer. The neccessary changes can be made automatically (the MO directory " - "will be made writable for the current user account). You will be asked to run " - "\"helper.exe\" with administrative rights."), - QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) { - if (!Helper::init(GameInfo::instance().getOrganizerDirectory())) { - return false; - } - } else { - return false; - } - // no matter which directory didn't exist/wasn't writable, the helper - // should have created them all so we can break the loop - break; - } - } - - // verify the hook-dll exists - QString dllName = qApp->applicationDirPath() + "/" + ToQString(AppConfig::hookDLLName()); - - if (::GetModuleHandleW(ToWString(dllName).c_str()) != NULL) { - throw std::runtime_error("hook.dll already loaded! You can't start Mod Organizer from within itself (not even indirectly)"); - } - - HMODULE dllMod = ::LoadLibraryW(ToWString(dllName).c_str()); - if (dllMod == NULL) { - throw windows_error("hook.dll is missing or invalid"); - } - ::FreeLibrary(dllMod); - - return true; -} - - -void cleanupDir() -{ - // files from previous versions of MO that are no longer - // required (in that location) - QString fileNames[] = { - "ModOrganiser.exe", - "ModOrganizer.log", - "ModOrganizer.log.old", - "7z.dll", - "mo1.dll", - "mo_archive.dll", - "mo_helper.exe", - "msvcp90.dll", - "msvcr90.dll", - "phonon4.dll", - "QtCore4.dll", - "QtGui4.dll", - "QtNetwork4.dll", - "QtXml4.dll", - "QtWebKit4.dll", - "qjpeg4.dll", - "NCC/GamebryoBase.dll", - "plugins/helloWorld.dll", - "plugins/testnexus.py" - }; - - static const int NUM_FILES = sizeof(fileNames) / sizeof(QString); - - qDebug("cleaning up unused files"); - - for (int i = 0; i < NUM_FILES; ++i) { - if (QFile::remove(QApplication::applicationDirPath().append("/").append(fileNames[i]))) { - qDebug("%s removed in cleanup", - QApplication::applicationDirPath().append("/").append(fileNames[i]).toUtf8().constData()); - } - } -} - - -bool isNxmLink(const QString &link) -{ - return link.left(6).toLower() == "nxm://"; -} - -LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionPtrs) -{ - typedef BOOL (WINAPI *FuncMiniDumpWriteDump)(HANDLE process, DWORD pid, HANDLE file, MINIDUMP_TYPE dumpType, - const PMINIDUMP_EXCEPTION_INFORMATION exceptionParam, - const PMINIDUMP_USER_STREAM_INFORMATION userStreamParam, - const PMINIDUMP_CALLBACK_INFORMATION callbackParam); - LONG result = EXCEPTION_CONTINUE_SEARCH; - - HMODULE dbgDLL = ::LoadLibrary(L"dbghelp.dll"); - - static const int errorLen = 200; - char errorBuffer[errorLen + 1]; - memset(errorBuffer, '\0', errorLen + 1); - - if (dbgDLL) { - FuncMiniDumpWriteDump funcDump = (FuncMiniDumpWriteDump)::GetProcAddress(dbgDLL, "MiniDumpWriteDump"); - if (funcDump) { - - if (QMessageBox::question(NULL, QObject::tr("Woops"), - QObject::tr("ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file " - "(%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. " - "Please include a short description of what you were doing when the crash happened").arg(qApp->applicationFilePath().append(".dmp")), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - - std::wstring dumpName = ToWString(qApp->applicationFilePath().append(".dmp")); - - HANDLE dumpFile = ::CreateFile(dumpName.c_str(), - GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); - if (dumpFile != INVALID_HANDLE_VALUE) { - _MINIDUMP_EXCEPTION_INFORMATION exceptionInfo; - exceptionInfo.ThreadId = ::GetCurrentThreadId(); - exceptionInfo.ExceptionPointers = exceptionPtrs; - exceptionInfo.ClientPointers = NULL; - - BOOL success = funcDump(::GetCurrentProcess(), ::GetCurrentProcessId(), dumpFile, MiniDumpNormal, &exceptionInfo, NULL, NULL); - - ::CloseHandle(dumpFile); - if (success) { - return EXCEPTION_EXECUTE_HANDLER; - } - _snprintf(errorBuffer, errorLen, "failed to save minidump to %ls (error %lu)", - dumpName.c_str(), ::GetLastError()); - } else { - _snprintf(errorBuffer, errorLen, "failed to create %ls (error %lu)", - dumpName.c_str(), ::GetLastError()); - } - } else { - return result; - } - } else { - _snprintf(errorBuffer, errorLen, "dbghelp.dll outdated"); - } - } else { - _snprintf(errorBuffer, errorLen, "dbghelp.dll not found"); - } - - QMessageBox::critical(NULL, QObject::tr("Woops"), - QObject::tr("ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1").arg(errorBuffer)); - return result; -} - -void registerMetaTypes() -{ - registerExecutable(); -} - -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, NULL, NULL, &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 = NULL; - 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 = NULL; - 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); - } - } - return writable; -} - - -int main(int argc, char *argv[]) -{ - MOApplication application(argc, argv); - - application.addLibraryPath(application.applicationDirPath() + "/dlls"); - - SetUnhandledExceptionFilter(MyUnhandledExceptionFilter); - - if (!HaveWriteAccess(ToWString(application.applicationDirPath()))) { - QStringList arguments = application.arguments(); - arguments.pop_front(); - ::ShellExecuteW( NULL - , L"runas" - , ToWString(QString("\"%1\"").arg(QCoreApplication::applicationFilePath())).c_str() - , ToWString(arguments.join(" ")).c_str() - , ToWString(QDir::currentPath()).c_str(), SW_SHOWNORMAL); - return 1; - } - LogBuffer::init(100, QtDebugMsg, application.applicationDirPath() + "/logs/mo_interface.log"); - - qDebug("Working directory: %s", qPrintable(QDir::toNativeSeparators(QDir::currentPath()))); - qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators(application.applicationDirPath()))); - QPixmap pixmap(":/MO/gui/splash"); - QSplashScreen splash(pixmap); - splash.show(); - - { // extend path to include dll directory so plugins don't need a manifest - static const int BUFSIZE = 4096; - - boost::scoped_array<TCHAR> 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::tstring newPath(oldPath.get()); - newPath += TEXT(";"); - newPath += ToWString(QDir::toNativeSeparators(QCoreApplication::applicationDirPath())).c_str(); - newPath += TEXT("\\dlls"); - - ::SetEnvironmentVariable(TEXT("PATH"), newPath.c_str()); - } - - registerMetaTypes(); - - QStringList arguments = application.arguments(); - - bool forcePrimary = false; - if (arguments.contains("update")) { - arguments.removeAll("update"); - forcePrimary = true; - } - - try { - SingleInstance instance(forcePrimary); - if (!instance.primaryInstance()) { - if ((arguments.size() == 2) && isNxmLink(arguments.at(1))) { - qDebug("not primary instance, sending download message"); - instance.sendMessage(arguments.at(1)); - return 0; - } else if (arguments.size() == 1) { - QMessageBox::information(NULL, 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 has been called with parameters - - - // TODO: this should be MAX_PATH_UNICODE! - wchar_t moPath[MAX_PATH]; - memset(moPath, 0, sizeof(TCHAR) * MAX_PATH); - ::GetModuleFileNameW(NULL, moPath, MAX_PATH); - wchar_t *lastBSlash = wcsrchr(moPath, TEXT('\\')); - if (lastBSlash != NULL) { - *lastBSlash = TEXT('\0'); - } - QSettings settings(ToQString(std::wstring(moPath).append(L"\\ModOrganizer.ini")), QSettings::IniFormat); - - QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray()); - - bool done = false; - while (!done) { - if (!GameInfo::init(moPath, ToWString(QDir::toNativeSeparators(gamePath)))) { - if (!gamePath.isEmpty()) { - reportError(QObject::tr("No game identified in \"%1\". The directory is required to contain " - "the game binary and its launcher.").arg(gamePath)); - } - SelectionDialog selection(QObject::tr("Please select the game to manage"), NULL); - - { // add options - QString skyrimPath = ToQString(SkyrimInfo::getRegPathStatic()); - QString falloutNVPath = ToQString(FalloutNVInfo::getRegPathStatic()); - QString fallout3Path = ToQString(Fallout3Info::getRegPathStatic()); - QString oblivionPath = ToQString(OblivionInfo::getRegPathStatic()); - if (skyrimPath.length() != 0) { - selection.addChoice(QString("Skyrim"), skyrimPath, skyrimPath); - } - if (falloutNVPath.length() != 0) { - selection.addChoice(QString("Fallout NV"), falloutNVPath, falloutNVPath); - } - if (fallout3Path.length() != 0) { - selection.addChoice(QString("Fallout 3"), fallout3Path, fallout3Path); - } - if (oblivionPath.length() != 0) { - selection.addChoice(QString("Oblivion"), oblivionPath, oblivionPath); - } - - selection.addChoice(QString("Browse..."), QString(), QString()); - } - - if (selection.exec() == QDialog::Rejected) { - gamePath = ""; - done = true; - } else { - gamePath = QDir::cleanPath(selection.getChoiceData().toString()); - if (gamePath.isEmpty()) { - gamePath = QFileDialog::getExistingDirectory(NULL, QObject::tr("Please select the game to manage"), QString(), - QFileDialog::ShowDirsOnly); - } - } - } else { - done = true; - gamePath = ToQString(GameInfo::instance().getGameDirectory()); - } - } - - if (gamePath.isEmpty()) { - // game not found and user canceled - return -1; - } else if (gamePath.length() != 0) { - // user selected a folder and game was initialised with it - settings.setValue("gamePath", gamePath.toUtf8().constData()); - } - - int edition = 0; - if (settings.contains("game_edition")) { - edition = settings.value("game_edition").toInt(); - } else { - std::vector<std::wstring> editions = GameInfo::instance().getSteamVariants(); - if (editions.size() < 2) { - edition = 0; - } else { - SelectionDialog selection(QObject::tr("Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!)"), NULL); - int index = 0; - for (auto iter = editions.begin(); iter != editions.end(); ++iter) { - selection.addChoice(ToQString(*iter), "", index++); - } - if (selection.exec() == QDialog::Rejected) { - return -1; - } else { - settings.setValue("game_edition", selection.getChoiceData().toInt()); - } - } - } - - qDebug("managing game at %s", qPrintable(QDir::toNativeSeparators(gamePath))); - - ExecutablesList executablesList; - - executablesList.init(); - - if (!bootstrap()) { // requires gameinfo to be initialised! - return -1; - } - - cleanupDir(); - - qDebug("setting up configured executables"); - - int numCustomExecutables = settings.beginReadArray("customExecutables"); - for (int i = 0; i < numCustomExecutables; ++i) { - settings.setArrayIndex(i); - CloseMOStyle closeMO = settings.value("closeOnStart").toBool() ? DEFAULT_CLOSE : DEFAULT_STAY; - executablesList.addExecutable(settings.value("title").toString(), - settings.value("binary").toString(), - settings.value("arguments").toString(), - settings.value("workingDirectory", "").toString(), - closeMO, - settings.value("steamAppID", "").toString(), - settings.value("custom", true).toBool(), - settings.value("toolbar", false).toBool()); - } - - settings.endArray(); - - qDebug("initializing tutorials"); - TutorialManager::init(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getTutorialDir())).append("/")); - - 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(argv[0], settings); - QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, SLOT(setStyleFile(QString))); - QObject::connect(&instance, SIGNAL(messageSent(QString)), &mainWindow, SLOT(externalMessage(QString))); - - mainWindow.setExecutablesList(executablesList); - mainWindow.readSettings(); - - 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"); - } else { - qDebug("configured profile: %s", qPrintable(selectedProfileName)); - } - - // if we have a command line parameter, it is either a nxm link or - // a binary to start - if ((arguments.size() > 1) && (!isNxmLink(arguments.at(1)))) { - QString exeName = arguments.at(1); - qDebug("starting %s from command line", qPrintable(exeName)); - arguments.removeFirst(); // remove application name (ModOrganizer.exe) - arguments.removeFirst(); // remove binary name - // pass the remaining parameters to the binary - try { - mainWindow.startApplication(exeName, arguments, QString(), selectedProfileName); - } catch (const std::exception &e) { - reportError(QObject::tr("failed to start application: %1").arg(e.what())); - } - - return 0; - } - - mainWindow.createFirstProfile(); - - if (selectedProfileName.length() != 0) { - if (!mainWindow.setCurrentProfile(selectedProfileName)) { - mainWindow.setCurrentProfile(1); - qWarning("failed to set profile: %s", - selectedProfileName.toUtf8().constData()); - } - } else { - mainWindow.setCurrentProfile(1); - } - - qDebug("displaying main window"); - mainWindow.show(); - - if ((arguments.size() > 1) && - (isNxmLink(arguments.at(1)))) { - qDebug("starting download from command line: %s", qPrintable(arguments.at(1))); - mainWindow.externalMessage(arguments.at(1)); - } - splash.finish(&mainWindow); - res = application.exec(); - } - return res; - } catch (const std::exception &e) { - reportError(e.what()); - return 1; - } -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+
+#ifdef LEAK_CHECK_WITH_VLD
+#include <wchar.h>
+#include <vld.h>
+#endif // LEAK_CHECK_WITH_VLD
+
+#include <QApplication>
+#include <QPushButton>
+#include <QListWidget>
+#include <QComboBox>
+#include <QCheckBox>
+#include <QDir>
+#include <QFileInfo>
+#include <QSettings>
+#include <QWhatsThis>
+#include <QToolBar>
+#include <QFileDialog>
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <DbgHelp.h>
+#include <cstdarg>
+#include <inject.h>
+#include <appconfig.h>
+#include <utility.h>
+#include <stdexcept>
+#include "mainwindow.h"
+#include "report.h"
+#include "modlist.h"
+#include "profile.h"
+#include "gameinfo.h"
+#include "fallout3info.h"
+#include "falloutnvinfo.h"
+#include "oblivioninfo.h"
+#include "skyriminfo.h"
+#include "spawn.h"
+#include "executableslist.h"
+#include "singleinstance.h"
+#include "utility.h"
+#include "helper.h"
+#include "logbuffer.h"
+#include "selectiondialog.h"
+#include "moapplication.h"
+#include "tutorialmanager.h"
+#include <iostream>
+#include <QMessageBox>
+#include <QSharedMemory>
+#include <QBuffer>
+#include <QSplashScreen>
+#include <QDirIterator>
+#include <QDesktopServices>
+#include <ShellAPI.h>
+#include <eh.h>
+#include <windows_error.h>
+#include <boost/scoped_array.hpp>
+
+
+#pragma comment(linker, "/manifestDependency:\"name='dlls' processorArchitecture='x86' version='1.0.0.0' type='win32' \"")
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+// set up required folders (for a first install or after an update or to fix a broken installation)
+bool bootstrap()
+{
+ GameInfo &gameInfo = GameInfo::instance();
+
+ // remove the temporary backup directory in case we're restarting after an update
+ QString moDirectory = QDir::fromNativeSeparators(ToQString(gameInfo.getOrganizerDirectory()));
+ QString backupDirectory = moDirectory.mid(0).append("/update_backup");
+ if (QDir(backupDirectory).exists()) {
+ shellDelete(QStringList(backupDirectory));
+ }
+
+ // cycle logfile
+ removeOldFiles(ToQString(GameInfo::instance().getLogDir()), "ModOrganizer*.log", 5, QDir::Name);
+
+ // create organizer directories
+ QString dirNames[] = {
+ QDir::fromNativeSeparators(ToQString(gameInfo.getProfilesDir())),
+ QDir::fromNativeSeparators(ToQString(gameInfo.getModsDir())),
+ QDir::fromNativeSeparators(ToQString(gameInfo.getDownloadDir())),
+ QDir::fromNativeSeparators(ToQString(gameInfo.getOverwriteDir())),
+ QDir::fromNativeSeparators(ToQString(gameInfo.getLogDir())),
+ QDir::fromNativeSeparators(ToQString(gameInfo.getTutorialDir()))
+ };
+ static const int NUM_DIRECTORIES = sizeof(dirNames) / sizeof(QString);
+
+ // optimistic run: try to simply create the directories:
+ for (int i = 0; i < NUM_DIRECTORIES; ++i) {
+ if (!QDir(dirNames[i]).exists()) {
+ QDir().mkdir(dirNames[i]);
+ }
+ }
+
+ // verify all directories exist and are writable,
+ // otherwise invoke the helper to create them and make them writable
+ for (int i = 0; i < NUM_DIRECTORIES; ++i) {
+ QFileInfo fileInfo(dirNames[i]);
+ if (!fileInfo.exists() || !fileInfo.isWritable()) {
+ if (QMessageBox::question(NULL, QObject::tr("Permissions required"),
+ QObject::tr("The current user account doesn't have the required access rights to run "
+ "Mod Organizer. The neccessary changes can be made automatically (the MO directory "
+ "will be made writable for the current user account). You will be asked to run "
+ "\"helper.exe\" with administrative rights."),
+ QMessageBox::Yes | QMessageBox::Cancel) == QMessageBox::Yes) {
+ if (!Helper::init(GameInfo::instance().getOrganizerDirectory())) {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ // no matter which directory didn't exist/wasn't writable, the helper
+ // should have created them all so we can break the loop
+ break;
+ }
+ }
+
+ // verify the hook-dll exists
+ QString dllName = qApp->applicationDirPath() + "/" + ToQString(AppConfig::hookDLLName());
+
+ if (::GetModuleHandleW(ToWString(dllName).c_str()) != NULL) {
+ throw std::runtime_error("hook.dll already loaded! You can't start Mod Organizer from within itself (not even indirectly)");
+ }
+
+ HMODULE dllMod = ::LoadLibraryW(ToWString(dllName).c_str());
+ if (dllMod == NULL) {
+ throw windows_error("hook.dll is missing or invalid");
+ }
+ ::FreeLibrary(dllMod);
+
+ return true;
+}
+
+
+void cleanupDir()
+{
+ // files from previous versions of MO that are no longer
+ // required (in that location)
+ QString fileNames[] = {
+ "ModOrganiser.exe",
+ "ModOrganizer.log",
+ "ModOrganizer.log.old",
+ "7z.dll",
+ "mo1.dll",
+ "mo_archive.dll",
+ "mo_helper.exe",
+ "msvcp90.dll",
+ "msvcr90.dll",
+ "phonon4.dll",
+ "QtCore4.dll",
+ "QtGui4.dll",
+ "QtNetwork4.dll",
+ "QtXml4.dll",
+ "QtWebKit4.dll",
+ "qjpeg4.dll",
+ "NCC/GamebryoBase.dll",
+ "plugins/helloWorld.dll",
+ "plugins/testnexus.py"
+ };
+
+ static const int NUM_FILES = sizeof(fileNames) / sizeof(QString);
+
+ qDebug("cleaning up unused files");
+
+ for (int i = 0; i < NUM_FILES; ++i) {
+ if (QFile::remove(QApplication::applicationDirPath().append("/").append(fileNames[i]))) {
+ qDebug("%s removed in cleanup",
+ QApplication::applicationDirPath().append("/").append(fileNames[i]).toUtf8().constData());
+ }
+ }
+}
+
+
+bool isNxmLink(const QString &link)
+{
+ return link.left(6).toLower() == "nxm://";
+}
+
+LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *exceptionPtrs)
+{
+ typedef BOOL (WINAPI *FuncMiniDumpWriteDump)(HANDLE process, DWORD pid, HANDLE file, MINIDUMP_TYPE dumpType,
+ const PMINIDUMP_EXCEPTION_INFORMATION exceptionParam,
+ const PMINIDUMP_USER_STREAM_INFORMATION userStreamParam,
+ const PMINIDUMP_CALLBACK_INFORMATION callbackParam);
+ LONG result = EXCEPTION_CONTINUE_SEARCH;
+
+ HMODULE dbgDLL = ::LoadLibrary(L"dbghelp.dll");
+
+ static const int errorLen = 200;
+ char errorBuffer[errorLen + 1];
+ memset(errorBuffer, '\0', errorLen + 1);
+
+ if (dbgDLL) {
+ FuncMiniDumpWriteDump funcDump = (FuncMiniDumpWriteDump)::GetProcAddress(dbgDLL, "MiniDumpWriteDump");
+ if (funcDump) {
+
+ if (QMessageBox::question(NULL, QObject::tr("Woops"),
+ QObject::tr("ModOrganizer has crashed! Should a diagnostic file be created? If you send me this file "
+ "(%1) to sherb@gmx.net, the bug is a lot more likely to be fixed. "
+ "Please include a short description of what you were doing when the crash happened").arg(qApp->applicationFilePath().append(".dmp")),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+
+ std::wstring dumpName = ToWString(qApp->applicationFilePath().append(".dmp"));
+
+ HANDLE dumpFile = ::CreateFile(dumpName.c_str(),
+ GENERIC_WRITE, FILE_SHARE_WRITE, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (dumpFile != INVALID_HANDLE_VALUE) {
+ _MINIDUMP_EXCEPTION_INFORMATION exceptionInfo;
+ exceptionInfo.ThreadId = ::GetCurrentThreadId();
+ exceptionInfo.ExceptionPointers = exceptionPtrs;
+ exceptionInfo.ClientPointers = NULL;
+
+ BOOL success = funcDump(::GetCurrentProcess(), ::GetCurrentProcessId(), dumpFile, MiniDumpNormal, &exceptionInfo, NULL, NULL);
+
+ ::CloseHandle(dumpFile);
+ if (success) {
+ return EXCEPTION_EXECUTE_HANDLER;
+ }
+ _snprintf(errorBuffer, errorLen, "failed to save minidump to %ls (error %lu)",
+ dumpName.c_str(), ::GetLastError());
+ } else {
+ _snprintf(errorBuffer, errorLen, "failed to create %ls (error %lu)",
+ dumpName.c_str(), ::GetLastError());
+ }
+ } else {
+ return result;
+ }
+ } else {
+ _snprintf(errorBuffer, errorLen, "dbghelp.dll outdated");
+ }
+ } else {
+ _snprintf(errorBuffer, errorLen, "dbghelp.dll not found");
+ }
+
+ QMessageBox::critical(NULL, QObject::tr("Woops"),
+ QObject::tr("ModOrganizer has crashed! Unfortunately I was not able to write a diagnostic file: %1").arg(errorBuffer));
+ return result;
+}
+
+void registerMetaTypes()
+{
+ registerExecutable();
+}
+
+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, NULL, NULL, &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 = NULL;
+ 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 = NULL;
+ 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);
+ }
+ }
+ return writable;
+}
+
+
+int main(int argc, char *argv[])
+{
+ MOApplication application(argc, argv);
+
+ application.addLibraryPath(application.applicationDirPath() + "/dlls");
+
+ SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
+
+ if (!HaveWriteAccess(ToWString(application.applicationDirPath()))) {
+ QStringList arguments = application.arguments();
+ arguments.pop_front();
+ ::ShellExecuteW( NULL
+ , L"runas"
+ , ToWString(QString("\"%1\"").arg(QCoreApplication::applicationFilePath())).c_str()
+ , ToWString(arguments.join(" ")).c_str()
+ , ToWString(QDir::currentPath()).c_str(), SW_SHOWNORMAL);
+ return 1;
+ }
+ LogBuffer::init(100, QtDebugMsg, application.applicationDirPath() + "/logs/mo_interface.log");
+
+ qDebug("Working directory: %s", qPrintable(QDir::toNativeSeparators(QDir::currentPath())));
+ qDebug("MO at: %s", qPrintable(QDir::toNativeSeparators(application.applicationDirPath())));
+ QPixmap pixmap(":/MO/gui/splash");
+ QSplashScreen splash(pixmap);
+ splash.show();
+
+ { // extend path to include dll directory so plugins don't need a manifest
+ static const int BUFSIZE = 4096;
+
+ boost::scoped_array<TCHAR> 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::tstring newPath(oldPath.get());
+ newPath += TEXT(";");
+ newPath += ToWString(QDir::toNativeSeparators(QCoreApplication::applicationDirPath())).c_str();
+ newPath += TEXT("\\dlls");
+
+ ::SetEnvironmentVariable(TEXT("PATH"), newPath.c_str());
+ }
+
+ registerMetaTypes();
+
+ QStringList arguments = application.arguments();
+
+ bool forcePrimary = false;
+ if (arguments.contains("update")) {
+ arguments.removeAll("update");
+ forcePrimary = true;
+ }
+
+ try {
+ SingleInstance instance(forcePrimary);
+ if (!instance.primaryInstance()) {
+ if ((arguments.size() == 2) && isNxmLink(arguments.at(1))) {
+ qDebug("not primary instance, sending download message");
+ instance.sendMessage(arguments.at(1));
+ return 0;
+ } else if (arguments.size() == 1) {
+ QMessageBox::information(NULL, 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 has been called with parameters
+
+
+ // TODO: this should be MAX_PATH_UNICODE!
+ wchar_t moPath[MAX_PATH];
+ memset(moPath, 0, sizeof(TCHAR) * MAX_PATH);
+ ::GetModuleFileNameW(NULL, moPath, MAX_PATH);
+ wchar_t *lastBSlash = wcsrchr(moPath, TEXT('\\'));
+ if (lastBSlash != NULL) {
+ *lastBSlash = TEXT('\0');
+ }
+ QSettings settings(ToQString(std::wstring(moPath).append(L"\\ModOrganizer.ini")), QSettings::IniFormat);
+
+ QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray());
+
+ bool done = false;
+ while (!done) {
+ if (!GameInfo::init(moPath, ToWString(QDir::toNativeSeparators(gamePath)))) {
+ if (!gamePath.isEmpty()) {
+ reportError(QObject::tr("No game identified in \"%1\". The directory is required to contain "
+ "the game binary and its launcher.").arg(gamePath));
+ }
+ SelectionDialog selection(QObject::tr("Please select the game to manage"), NULL);
+
+ { // add options
+ QString skyrimPath = ToQString(SkyrimInfo::getRegPathStatic());
+ QString falloutNVPath = ToQString(FalloutNVInfo::getRegPathStatic());
+ QString fallout3Path = ToQString(Fallout3Info::getRegPathStatic());
+ QString oblivionPath = ToQString(OblivionInfo::getRegPathStatic());
+ if (skyrimPath.length() != 0) {
+ selection.addChoice(QString("Skyrim"), skyrimPath, skyrimPath);
+ }
+ if (falloutNVPath.length() != 0) {
+ selection.addChoice(QString("Fallout NV"), falloutNVPath, falloutNVPath);
+ }
+ if (fallout3Path.length() != 0) {
+ selection.addChoice(QString("Fallout 3"), fallout3Path, fallout3Path);
+ }
+ if (oblivionPath.length() != 0) {
+ selection.addChoice(QString("Oblivion"), oblivionPath, oblivionPath);
+ }
+
+ selection.addChoice(QString("Browse..."), QString(), QString());
+ }
+
+ if (selection.exec() == QDialog::Rejected) {
+ gamePath = "";
+ done = true;
+ } else {
+ gamePath = QDir::cleanPath(selection.getChoiceData().toString());
+ if (gamePath.isEmpty()) {
+ gamePath = QFileDialog::getExistingDirectory(NULL, QObject::tr("Please select the game to manage"), QString(),
+ QFileDialog::ShowDirsOnly);
+ }
+ }
+ } else {
+ done = true;
+ gamePath = ToQString(GameInfo::instance().getGameDirectory());
+ }
+ }
+
+ if (gamePath.isEmpty()) {
+ // game not found and user canceled
+ return -1;
+ } else if (gamePath.length() != 0) {
+ // user selected a folder and game was initialised with it
+ settings.setValue("gamePath", gamePath.toUtf8().constData());
+ }
+
+ int edition = 0;
+ if (settings.contains("game_edition")) {
+ edition = settings.value("game_edition").toInt();
+ } else {
+ std::vector<std::wstring> editions = GameInfo::instance().getSteamVariants();
+ if (editions.size() < 2) {
+ edition = 0;
+ } else {
+ SelectionDialog selection(QObject::tr("Please select the game edition you have (MO can't start the game correctly if this is set incorrectly!)"), NULL);
+ int index = 0;
+ for (auto iter = editions.begin(); iter != editions.end(); ++iter) {
+ selection.addChoice(ToQString(*iter), "", index++);
+ }
+ if (selection.exec() == QDialog::Rejected) {
+ return -1;
+ } else {
+ settings.setValue("game_edition", selection.getChoiceData().toInt());
+ }
+ }
+ }
+
+ qDebug("managing game at %s", qPrintable(QDir::toNativeSeparators(gamePath)));
+
+ ExecutablesList executablesList;
+
+ executablesList.init();
+
+ if (!bootstrap()) { // requires gameinfo to be initialised!
+ return -1;
+ }
+
+ cleanupDir();
+
+ qDebug("setting up configured executables");
+
+ int numCustomExecutables = settings.beginReadArray("customExecutables");
+ for (int i = 0; i < numCustomExecutables; ++i) {
+ settings.setArrayIndex(i);
+ CloseMOStyle closeMO = settings.value("closeOnStart").toBool() ? DEFAULT_CLOSE : DEFAULT_STAY;
+ executablesList.addExecutable(settings.value("title").toString(),
+ settings.value("binary").toString(),
+ settings.value("arguments").toString(),
+ settings.value("workingDirectory", "").toString(),
+ closeMO,
+ settings.value("steamAppID", "").toString(),
+ settings.value("custom", true).toBool(),
+ settings.value("toolbar", false).toBool());
+ }
+
+ settings.endArray();
+
+ qDebug("initializing tutorials");
+ TutorialManager::init(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getTutorialDir())).append("/"));
+
+ 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(argv[0], settings);
+ QObject::connect(&mainWindow, SIGNAL(styleChanged(QString)), &application, SLOT(setStyleFile(QString)));
+ QObject::connect(&instance, SIGNAL(messageSent(QString)), &mainWindow, SLOT(externalMessage(QString)));
+
+ mainWindow.setExecutablesList(executablesList);
+ mainWindow.readSettings();
+
+ 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");
+ } else {
+ qDebug("configured profile: %s", qPrintable(selectedProfileName));
+ }
+
+ // if we have a command line parameter, it is either a nxm link or
+ // a binary to start
+ if ((arguments.size() > 1) && (!isNxmLink(arguments.at(1)))) {
+ QString exeName = arguments.at(1);
+ qDebug("starting %s from command line", qPrintable(exeName));
+ arguments.removeFirst(); // remove application name (ModOrganizer.exe)
+ arguments.removeFirst(); // remove binary name
+ // pass the remaining parameters to the binary
+ try {
+ mainWindow.startApplication(exeName, arguments, QString(), selectedProfileName);
+ } catch (const std::exception &e) {
+ reportError(QObject::tr("failed to start application: %1").arg(e.what()));
+ }
+
+ return 0;
+ }
+
+ mainWindow.createFirstProfile();
+
+ if (selectedProfileName.length() != 0) {
+ if (!mainWindow.setCurrentProfile(selectedProfileName)) {
+ mainWindow.setCurrentProfile(1);
+ qWarning("failed to set profile: %s",
+ selectedProfileName.toUtf8().constData());
+ }
+ } else {
+ mainWindow.setCurrentProfile(1);
+ }
+
+ qDebug("displaying main window");
+ mainWindow.show();
+
+ if ((arguments.size() > 1) &&
+ (isNxmLink(arguments.at(1)))) {
+ qDebug("starting download from command line: %s", qPrintable(arguments.at(1)));
+ mainWindow.externalMessage(arguments.at(1));
+ }
+ splash.finish(&mainWindow);
+ res = application.exec();
+ }
+ return res;
+ } catch (const std::exception &e) {
+ reportError(e.what());
+ return 1;
+ }
+}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index cf283b49..c3182321 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,5601 +1,5601 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "mainwindow.h" -#include "ui_mainwindow.h" -#include <archive.h> -#include "spawn.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 "utility.h" -#include "modinfodialog.h" -#include "overwriteinfodialog.h" -#include "activatemodsdialog.h" -#include "downloadlist.h" -#include "downloadlistwidget.h" -#include "downloadlistwidgetcompact.h" -#include "messagedialog.h" -#include "installationmanager.h" -#include "lockeddialog.h" -#include "syncoverwritedialog.h" -#include "logbuffer.h" -#include "downloadlistsortproxy.h" -#include "motddialog.h" -#include "filedialogmemory.h" -#include "questionboxmemory.h" -#include "tutorialmanager.h" -#include "modflagicondelegate.h" -#include "pluginflagicondelegate.h" -#include "credentialsdialog.h" -#include "selectiondialog.h" -#include "csvbuilder.h" -#include "gameinfoimpl.h" -#include "savetextasdialog.h" -#include "problemsdialog.h" -#include "previewdialog.h" -#include "browserdialog.h" -#include "aboutdialog.h" -#include "safewritefile.h" -#include "organizerproxy.h" -#include <gameinfo.h> -#include <appconfig.h> -#include <utility.h> -#include <ipluginproxy.h> -#include <questionboxmemory.h> -#include <map> -#include <ctime> -#include <util.h> -#include <wchar.h> -#include <utility.h> -#include <QTime> -#include <QInputDialog> -#include <QSettings> -#include <QWhatsThis> -#include <sstream> -#include <QProcess> -#include <QMenu> -#include <QBuffer> -#include <QInputDialog> -#include <QDirIterator> -#include <QHelpEvent> -#include <QToolTip> -#include <QFileDialog> -#include <QTimer> -#include <QMessageBox> -#include <QDebug> -#include <QBuffer> -#include <QWidgetAction> -#include <QToolButton> -#include <QGraphicsObject> -#include <QPluginLoader> -#include <QRadioButton> -#include <QDesktopWidget> -#include <QtPlugin> -#include <QIdentityProxyModel> -#include <QClipboard> -#include <boost/bind.hpp> -#include <boost/foreach.hpp> -#include <boost/assign.hpp> -#include <Psapi.h> -#include <shlobj.h> -#include <ShellAPI.h> -#include <TlHelp32.h> -#include <QNetworkInterface> -#include <QNetworkProxy> -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) -#include <QtConcurrent/QtConcurrentRun> -#else -#include <QtConcurrentRun> -#endif -#include <QCoreApplication> -#include <QProgressDialog> -#include <scopeguard.h> -#include <boost/thread.hpp> -#include <boost/algorithm/string.hpp> -#include <regex> - -#ifdef TEST_MODELS -#include "modeltest.h" -#endif // TEST_MODELS - -#pragma warning( disable : 4428 ) - -using namespace MOBase; -using namespace MOShared; - - - - -static bool isOnline() -{ - QList<QNetworkInterface> 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; -} - - -MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget *parent) - : QMainWindow(parent), ui(new Ui::MainWindow), m_Tutorial(this, "MainWindow"), - m_ExeName(exeName), m_OldProfileIndex(-1), - m_DirectoryStructure(new DirectoryEntry(L"data", NULL, 0)), - m_ModList(this), m_ModListGroupingProxy(NULL), m_ModListSortProxy(NULL), - m_PluginList(this), m_OldExecutableIndex(-1), m_GamePath(ToQString(GameInfo::instance().getGameDirectory())), - m_DownloadManager(NexusInterface::instance(), this), m_InstallationManager(this), - m_Updater(NexusInterface::instance(), this), m_CategoryFactory(CategoryFactory::instance()), - m_CurrentProfile(NULL), m_AskForNexusPW(false), - m_ArchivesInit(false), m_DirectoryUpdate(false), m_ContextItem(NULL), m_ContextAction(NULL), m_CurrentSaveView(NULL), - m_GameInfo(new GameInfoImpl()), m_AboutToRun(), m_ModInstalled(), m_DidUpdateMasterList(false) -{ - ui->setupUi(this); - this->setWindowTitle(ToQString(GameInfo::instance().getGameName()) + " Mod Organizer v" + m_Updater.getVersion().displayString()); - - languageChange(m_Settings.language()); - - 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<int>() << 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); - statusBar()->addWidget(m_RefreshProgress, 1000); - statusBar()->clearMessage(); - - ui->actionEndorseMO->setVisible(false); - - MOBase::QuestionBoxMemory::init(initSettings.fileName()); - - updateProblemsButton(); - - updateToolBar(); - - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign()); - - // set up mod list - m_ModListSortProxy = new ModListSortProxy(m_CurrentProfile, this); - m_ModListSortProxy->setSourceModel(&m_ModList); - -#ifdef TEST_MODELS - new ModelTest(&m_ModList, this); - new ModelTest(m_ModListSortProxy, this); -#endif //TEST_MODELS - - ui->modList->setModel(m_ModListSortProxy); - - ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder); - ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(ui->modList)); - //ui->modList->setAcceptDrops(true); - ui->modList->header()->installEventFilter(&m_ModList); - if (initSettings.contains("mod_list_state")) { - ui->modList->header()->restoreState(initSettings.value("mod_list_state").toByteArray()); - } else { - // hide these columns by default - 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_ModList); - - // set up plugin list - m_PluginListSortProxy = new PluginListSortProxy(this); - m_PluginListSortProxy->setSourceModel(&m_PluginList); - - ui->espList->setModel(m_PluginListSortProxy); - ui->espList->sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder); - ui->espList->setItemDelegateForColumn(PluginList::COL_FLAGS, new PluginFlagIconDelegate(ui->espList)); - if (initSettings.contains("plugin_list_state")) { - ui->espList->header()->restoreState(initSettings.value("plugin_list_state").toByteArray()); - } - ui->espList->installEventFilter(&m_PluginList); - - ui->bsaList->setLocalMoveOnly(true); - - ui->splitter->setStretchFactor(0, 3); - ui->splitter->setStretchFactor(1, 2); - - resizeLists(initSettings.contains("mod_list_state"), initSettings.contains("plugin_list_state")); - - QMenu *linkMenu = new QMenu(this); - 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->listOptionsBtn->setMenu(modListContextMenu()); - - m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); - m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers()); - - NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory()); - NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion()); - - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); - - updateDownloadListDelegate(); - - ui->savegameList->installEventFilter(this); - ui->savegameList->setMouseTracking(true); - connect(&m_DownloadManager, SIGNAL(showMessage(QString)), this, SLOT(showMessage(QString))); - connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString,int)), this, SLOT(downloadSpeed(QString,int))); - connect(&m_DownloadManager, SIGNAL(downloadAdded()), ui->downloadView, SLOT(scrollToBottom())); - - connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(saveSelectionChanged(QListWidgetItem*))); - - connect(&m_ModList, SIGNAL(modorder_changed()), this, SLOT(modorder_changed())); - connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, SLOT(removeOrigin(QString))); - connect(&m_ModList, SIGNAL(showMessage(QString)), this, SLOT(showMessage(QString))); - connect(&m_ModList, SIGNAL(modRenamed(QString,QString)), this, SLOT(modRenamed(QString,QString))); - connect(&m_ModList, SIGNAL(modUninstalled(QString)), this, SLOT(modRemoved(QString))); - connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), this, SLOT(modlistChanged(QModelIndex, int))); - connect(&m_ModList, SIGNAL(removeSelectedMods()), this, SLOT(removeMod_clicked())); - connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), this, SLOT(displayColumnSelection(QPoint))); - connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), this, SLOT(fileMoved(QString, QString, QString))); - connect(ui->modList, SIGNAL(dropModeUpdate(bool)), &m_ModList, SLOT(dropModeUpdate(bool))); - connect(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(ui->espFilterEdit, SIGNAL(textChanged(QString)), m_PluginListSortProxy, SLOT(updateFilter(QString))); - connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString))); - connect(&m_PluginList, SIGNAL(saveTimer()), this, SLOT(savePluginList())); - - connect(ui->bsaList, SIGNAL(itemsMoved()), this, SLOT(bsaList_itemMoved())); - - connect(ui->dataTree, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(expandDataTreeItem(QTreeWidgetItem*))); - - connect(&m_DirectoryRefresher, SIGNAL(refreshed()), this, SLOT(directory_refreshed())); - connect(&m_DirectoryRefresher, SIGNAL(progress(int)), this, SLOT(refresher_progress(int))); - connect(&m_DirectoryRefresher, SIGNAL(error(QString)), this, SLOT(showError(QString))); - - connect(&m_SavesWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(refreshSavesIfOpen())); - - connect(&m_Settings, SIGNAL(languageChanged(QString)), this, SLOT(languageChange(QString))); - connect(&m_Settings, SIGNAL(styleChanged(QString)), this, SIGNAL(styleChanged(QString))); - - connect(&m_Updater, SIGNAL(restart()), this, SLOT(close())); - connect(&m_Updater, SIGNAL(updateAvailable()), this, SLOT(updateAvailable())); - connect(&m_Updater, SIGNAL(motdAvailable(QString)), this, SLOT(motdReceived(QString))); - -// connect(ExitProxy::instance(), SIGNAL(exit()), this, SLOT(close())); - - connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool))); - connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString))); - connect(NexusInterface::instance(), SIGNAL(requestNXMDownload(QString)), this, 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()), this, SLOT(nexusLogin())); - - connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString))); - 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_IntegratedBrowser, SIGNAL(requestDownload(QUrl,QNetworkReply*)), this, SLOT(requestDownload(QUrl,QNetworkReply*))); - - connect(this, SIGNAL(styleChanged(QString)), this, SLOT(updateStyle(QString))); - - m_CheckBSATimer.setSingleShot(true); - connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList())); - - m_UpdateProblemsTimer.setSingleShot(true); - connect(&m_UpdateProblemsTimer, SIGNAL(timeout()), this, SLOT(updateProblemsButton())); - - m_SaveMetaTimer.setSingleShot(false); - connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas())); - m_SaveMetaTimer.start(5000); - - m_DirectoryRefresher.moveToThread(&m_RefresherThread); - m_RefresherThread.start(); - - m_AskForNexusPW = initSettings.value("ask_for_nexuspw", true).toBool(); - setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool()); - FileDialogMemory::restore(initSettings); - - fixCategories(); - - if (isOnline() && !m_Settings.offlineMode()) { - m_Updater.testForUpdate(); - } else { - qDebug("user doesn't seem to be connected to the internet"); - } - - m_StartTime = QTime::currentTime(); - - m_Tutorial.expose("modList", &m_ModList); - m_Tutorial.expose("espList", &m_PluginList); - - // before we start loading plugins we, add the dll path to the dll search order - ::SetDllDirectoryW(ToWString(QDir::toNativeSeparators(qApp->applicationDirPath() + "/dlls")).c_str()); - loadPlugins(); -} - - -MainWindow::~MainWindow() -{ - m_RefresherThread.exit(); - m_RefresherThread.wait(); - m_IntegratedBrowser.close(); - delete ui; - delete m_GameInfo; - delete m_DirectoryStructure; -} - - -void MainWindow::resizeLists(bool modListCustom, bool pluginListCustom) -{ - if (!modListCustom) { - // resize mod list to fit content -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - 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); -#else - for (int i = 0; i < ui->modList->header()->count(); ++i) { - ui->modList->header()->setResizeMode(i, QHeaderView::ResizeToContents); - } - ui->modList->header()->setResizeMode(ModList::COL_NAME, QHeaderView::Stretch); -#endif - } - - if (!pluginListCustom) { - // resize plugin list to fit content -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - for (int i = 0; i < ui->espList->header()->count(); ++i) { - ui->espList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents); - } - ui->espList->header()->setSectionResizeMode(0, QHeaderView::Stretch); -#else - for (int i = 0; i < ui->espList->header()->count(); ++i) { - ui->espList->header()->setResizeMode(i, QHeaderView::ResizeToContents); - } - ui->espList->header()->setResizeMode(0, QHeaderView::Stretch); -#endif - } -} - - -void MainWindow::allowListResize() -{ - // allow resize on mod list -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - 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); -#else - for (int i = 0; i < ui->modList->header()->count(); ++i) { - ui->modList->header()->setResizeMode(i, QHeaderView::Interactive); - } - ui->modList->header()->setResizeMode(ui->modList->header()->count() - 1, QHeaderView::Stretch); -#endif - - - // allow resize on plugin list -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - 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); -#else - for (int i = 0; i < ui->espList->header()->count(); ++i) { - ui->espList->header()->setResizeMode(i, QHeaderView::Interactive); - } - ui->espList->header()->setResizeMode(ui->espList->header()->count() - 1, QHeaderView::Stretch); -#endif - -} - -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 == NULL) { - return QModelIndex(); - } - const QAbstractProxyModel *proxyModel = qobject_cast<const QAbstractProxyModel*>(model); - if (proxyModel == NULL) { - 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() -{ - foreach (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<QToolButton*>(widget); - - if (toolBtn->menu() == NULL) { - actionToToolButton(ui->actionTool); - } - - actionToToolButton(ui->actionHelp); - createHelpWidget(); - - foreach (QAction *action, ui->toolBar->actions()) { - if (action->isSeparator()) { - // insert spacers - ui->toolBar->insertWidget(action, spacer); - - std::vector<Executable>::iterator begin, end; - m_ExecutablesList.getExecutables(begin, end); - for (auto iter = begin; iter != end; ++iter) { - if (iter->m_Toolbar) { - QAction *exeAction = new QAction(iconForExecutable(iter->m_BinaryInfo.filePath()), - iter->m_Title, - ui->toolBar); - QVariant temp; - temp.setValue(*iter); - exeAction->setData(temp); - 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); - } - } - } - } -} - - -void MainWindow::scheduleUpdateButton() -{ - if (!m_UpdateProblemsTimer.isActive()) { - m_UpdateProblemsTimer.start(1000); - } -} - - -void MainWindow::updateProblemsButton() -{ - int 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")); - - 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<long long>(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")); - } -} - - -bool MainWindow::errorReported(QString &logFile) -{ - QDir dir(ToQString(GameInfo::instance().getLogDir())); - 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; - } - } - } - } - - return false; -} - - -int MainWindow::checkForProblems() -{ - int numProblems = 0; - foreach (IPluginDiagnose *diagnose, m_DiagnosisPlugins) { - numProblems += diagnose->activeProblems().size(); - } - return numProblems; -} - -void MainWindow::about() -{ - AboutDialog dialog(m_Updater.getVersion().displayString(), this); - dialog.exec(); -} - - -void MainWindow::createHelpWidget() -{ - QToolButton *toolBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionHelp)); - QMenu *buttonMenu = toolBtn->menu(); - if (buttonMenu == NULL) { - return; - } - buttonMenu->clear(); - - 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); - - QAction *issueAction = new QAction(tr("Report Issue"), buttonMenu); - connect(issueAction, SIGNAL(triggered()), this, SLOT(issueTriggered())); - buttonMenu->addAction(issueAction); - - QMenu *tutorialMenu = new QMenu(tr("Tutorials"), buttonMenu); - - typedef std::vector<std::pair<int, QAction*> > ActionList; - - ActionList tutorials; - - 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("//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)); - } - } - - std::sort(tutorials.begin(), tutorials.end(), - [](const ActionList::value_type &LHS, const ActionList::value_type &RHS) { - return LHS.first < RHS.first; } ); - - for (auto iter = tutorials.begin(); iter != tutorials.end(); ++iter) { - connect(iter->second, SIGNAL(triggered()), this, SLOT(tutorialTriggered())); - tutorialMenu->addAction(iter->second); - } - - buttonMenu->addMenu(tutorialMenu); - buttonMenu->addAction(tr("About"), this, SLOT(about())); - buttonMenu->addAction(tr("About Qt"), qApp, SLOT(aboutQt())); -} - - -bool MainWindow::saveArchiveList() -{ - if (m_ArchivesInit) { - SafeWriteFile archiveFile(m_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) { - // in managed mode, "register" all enabled archives, otherwise register only the files registered in the ini - if (ui->manageArchivesBox->isChecked() - || item->data(0, Qt::UserRole).toBool()) { - archiveFile->write(item->text(0).toUtf8().append("\r\n")); - } - } - } - } - if (archiveFile.commitIfDifferent(m_ArchiveListHash)) { - qDebug("%s saved", qPrintable(QDir::toNativeSeparators(m_CurrentProfile->getArchivesFileName()))); - return true; - } - } else { - qWarning("archive list not initialised"); - } - return false; -} - -void MainWindow::savePluginList() -{ - m_PluginList.saveTo(m_CurrentProfile->getPluginsFileName(), - m_CurrentProfile->getLoadOrderFileName(), - m_CurrentProfile->getLockedOrderFileName(), - m_CurrentProfile->getDeleterFileName(), - m_Settings.hideUncheckedPlugins()); - m_PluginList.saveLoadOrder(*m_DirectoryStructure); -} - -void MainWindow::modFilterActive(bool filterActive) -{ - if (filterActive) { - 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::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; - } - } -} - -bool MainWindow::saveCurrentLists() -{ - if (m_DirectoryUpdate) { - qWarning("not saving lists during directory update"); - return false; - } - - try { - savePluginList(); - saveArchiveList(); - } catch (const std::exception &e) { - reportError(tr("failed to save load order: %1").arg(e.what())); - } - - return true; -} - - -bool MainWindow::addProfile() -{ - QComboBox *profileBox = findChild<QComboBox*>("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_Settings.directInterface().value("CompletedWindowTutorials/" + windowName, false).toBool()) { - TutorialManager::instance().activateTutorial(windowName, fileName); - } - } - } -} - - -void MainWindow::showEvent(QShowEvent *event) -{ - refreshFilters(); - - QMainWindow::showEvent(event); - m_Tutorial.registerControl(); - - hookUpWindowTutorials(); - - if (m_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_Settings.directInterface().setValue("first_start", false); - } - - // this has no visible impact when called before the ui is visible - int grouping = m_Settings.directInterface().value("group_state").toInt(); - ui->groupCombo->setCurrentIndex(grouping); - - allowListResize(); - - m_Settings.registerAsNXMHandler(false); -} - - -void MainWindow::closeEvent(QCloseEvent* event) -{ - if (m_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_DownloadManager.pauseAll(); - } - } - - setCursor(Qt::WaitCursor); - - m_IntegratedBrowser.close(); - - storeSettings(); - - // profile has to be cleaned up before the modinfo-buffer is cleared - delete m_CurrentProfile; - m_CurrentProfile = NULL; - - ModInfo::clear(); - LogBuffer::cleanQuit(); - m_ModList.setProfile(NULL); -} - - -void MainWindow::createFirstProfile() -{ - if (!refreshProfiles(false)) { - qDebug("creating default profile"); - Profile newProf("Default", false); - refreshProfiles(false); - } -} - - -void MainWindow::setBrowserGeometry(const QByteArray &geometry) -{ - m_IntegratedBrowser.restoreGeometry(geometry); -} - - -SaveGameGamebryo *MainWindow::getSaveGame(const QString &name) -{ - return new SaveGameGamebryo(this, name); -} - - -SaveGameGamebryo *MainWindow::getSaveGame(QListWidgetItem *item) -{ - try { - SaveGameGamebryo *saveGame = getSaveGame(item->data(Qt::UserRole).toString()); - saveGame->setParent(item->listWidget()); - return saveGame; - } catch (const std::exception &e) { - reportError(tr("failed to read savegame: %1").arg(e.what())); - return NULL; - } -} - - -void MainWindow::displaySaveGameInfo(const SaveGameGamebryo *save, QPoint pos) -{ - if (m_CurrentSaveView == NULL) { - m_CurrentSaveView = new SaveGameInfoWidgetGamebryo(save, &m_PluginList, this); - } else { - m_CurrentSaveView->setSave(save); - } - - QRect screenRect = QApplication::desktop()->availableGeometry(m_CurrentSaveView); - - 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); - - m_CurrentSaveView->show(); - ui->savegameList->activateWindow(); - connect(m_CurrentSaveView, SIGNAL(closeSaveInfo()), this, SLOT(hideSaveGameInfo())); -} - - -void MainWindow::saveSelectionChanged(QListWidgetItem *newItem) -{ - if (newItem == NULL) { - hideSaveGameInfo(); - } else if ((m_CurrentSaveView == NULL) || (newItem != m_CurrentSaveView->property("displayItem").value<void*>())) { - const SaveGameGamebryo *save = getSaveGame(newItem); - if (save != NULL) { - displaySaveGameInfo(save, QCursor::pos()); - m_CurrentSaveView->setProperty("displayItem", qVariantFromValue((void*)newItem)); - } - } -} - - - -void MainWindow::hideSaveGameInfo() -{ - if (m_CurrentSaveView != NULL) { - disconnect(m_CurrentSaveView, SIGNAL(closeSaveInfo()), this, SLOT(hideSaveGameInfo())); - m_CurrentSaveView->deleteLater(); - m_CurrentSaveView = NULL; - } -} - -bool MainWindow::eventFilter(QObject *object, QEvent *event) -{ - if ((object == ui->savegameList) && - ((event->type() == QEvent::Leave) || (event->type() == QEvent::WindowDeactivate))) { - hideSaveGameInfo(); - } - return false; -} - - -bool MainWindow::testForSteam() -{ - DWORD processIDs[1024]; - DWORD bytesReturned; - if (!::EnumProcesses(processIDs, sizeof(processIDs), &bytesReturned)) { - qWarning("failed to determine if steam is running"); - return 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 != NULL) { - 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; -} - - -bool MainWindow::verifyPlugin(IPlugin *plugin) -{ - if (plugin == NULL) { - return false; - } else if (!plugin->init(new OrganizerProxy(this, plugin->name()))) { - qWarning("plugin failed to initialize"); - return false; - } - return true; -} - - -void MainWindow::toolPluginInvoke() -{ - QAction *triggeredAction = qobject_cast<QAction*>(sender()); - IPluginTool *plugin = (IPluginTool*)triggeredAction->data().value<void*>(); - 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::requestDownload(const QUrl &url, QNetworkReply *reply) -{ - QToolButton *browserBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionNexus)); - if (browserBtn->menu() != NULL) { - // go through modpage plugins, find one to handle the download. - QList<QAction*> browserActions = browserBtn->menu()->actions(); - foreach (QAction *action, browserActions) { - // the nexus action doesn't have a plugin connected currently - if (action->data().isValid()) { - IPluginModPage *plugin = qobject_cast<IPluginModPage*>(qvariant_cast<QObject*>(action->data())); - if (plugin == NULL) { - qCritical("invalid mod page. This is a bug"); - continue; - } - ModRepositoryFileInfo *fileInfo = new ModRepositoryFileInfo(); - if (plugin->handlesDownload(url, reply->url(), *fileInfo)) { - fileInfo->repository = plugin->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(this, 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()); - } - } -} - - -void MainWindow::modPagePluginInvoke() -{ - QAction *triggeredAction = qobject_cast<QAction*>(sender()); - IPluginModPage *plugin = qobject_cast<IPluginModPage*>(triggeredAction->data().value<QObject*>()); - if (plugin->useIntegratedBrowser()) { - m_IntegratedBrowser.setWindowTitle(plugin->displayName()); - m_IntegratedBrowser.openUrl(plugin->pageURL()); - } else { - ::ShellExecuteW(NULL, L"open", ToWString(plugin->pageURL().toString()).c_str(), NULL, NULL, 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((void*)tool)); - connect(action, SIGNAL(triggered()), this, SLOT(toolPluginInvoke()), Qt::QueuedConnection); - QToolButton *toolBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionTool)); - toolBtn->menu()->addAction(action); -} - - -void MainWindow::registerModPage(IPluginModPage *modPage) -{ - QToolButton *browserBtn = NULL; - // turn the browser action into a drop-down menu if necessary - if (ui->actionNexus->menu() == NULL) { - 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); - - browserBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionNexus)); - browserBtn->menu()->addAction(nexusAction); - } else { - browserBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionNexus)); - } - - QAction *action = new QAction(modPage->icon(), modPage->displayName(), ui->toolBar); - modPage->setParentWidget(this); - action->setData(qVariantFromValue(reinterpret_cast<QObject*>(modPage))); - - connect(action, SIGNAL(triggered()), this, SLOT(modPagePluginInvoke()), Qt::QueuedConnection); - QToolButton *toolBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionNexus)); - toolBtn->menu()->addAction(action); -} - - -bool MainWindow::registerPlugin(QObject *plugin, const QString &fileName) -{ - { // generic treatment for all plugins - IPlugin *pluginObj = qobject_cast<IPlugin*>(plugin); - if (pluginObj == NULL) { - qDebug("not an IPlugin"); - return false; - } - plugin->setProperty("filename", fileName); - m_Settings.registerPlugin(pluginObj); - installTranslator(QFileInfo(fileName).baseName()); - } - - { // diagnosis plugins - IPluginDiagnose *diagnose = qobject_cast<IPluginDiagnose*>(plugin); - if (diagnose != NULL) { - m_DiagnosisPlugins.push_back(diagnose); - diagnose->onInvalidated([&] () { this->scheduleUpdateButton(); }); - } - } - { // mod page plugin - IPluginModPage *modPage = qobject_cast<IPluginModPage*>(plugin); - if (verifyPlugin(modPage)) { - registerModPage(modPage); - return true; - } - } - { // tool plugins - IPluginTool *tool = qobject_cast<IPluginTool*>(plugin); - if (verifyPlugin(tool)) { - registerPluginTool(tool); - return true; - } - } - { // installer plugins - IPluginInstaller *installer = qobject_cast<IPluginInstaller*>(plugin); - if (verifyPlugin(installer)) { - installer->setParentWidget(this); - m_InstallationManager.registerInstaller(installer); - return true; - } - } - { // preview plugins - IPluginPreview *preview = qobject_cast<IPluginPreview*>(plugin); - if (verifyPlugin(preview)) { - m_PreviewGenerator.registerPlugin(preview); - } - } - { // proxy plugins - IPluginProxy *proxy = qobject_cast<IPluginProxy*>(plugin); - if (verifyPlugin(proxy)) { - proxy->setParentWidget(this); - QStringList pluginNames = proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath())); - foreach (const QString &pluginName, pluginNames) { - try { - QObject *proxiedPlugin = proxy->instantiate(pluginName); - if (proxiedPlugin != NULL) { - if (registerPlugin(proxiedPlugin, pluginName)) { - qDebug("loaded plugin \"%s\"", qPrintable(pluginName)); - } else { - qWarning("plugin \"%s\" failed to load", qPrintable(pluginName)); - } - } - } catch (const std::exception &e) { - reportError(tr("failed to init plugin %1: %2").arg(pluginName).arg(e.what())); - } - } - return true; - } - } - - { // dummy plugins - // only initialize these, no processing otherwise - IPlugin *dummy = qobject_cast<IPlugin*>(plugin); - if (verifyPlugin(dummy)) { - return true; - } - } - - qDebug("no matching plugin interface"); - - return false; -} - - -void MainWindow::loadPlugins() -{ - m_DiagnosisPlugins.clear(); - - m_Settings.clearPlugins(); - - foreach (QObject *plugin, QPluginLoader::staticInstances()) { - registerPlugin(plugin, ""); - } - - QFile loadCheck(QCoreApplication::applicationDirPath() + "/plugin_loadcheck.tmp"); - if (loadCheck.exists() && loadCheck.open(QIODevice::ReadOnly)) { - // oh, there was a failed plugin load last time. Find out which plugin was loaded last - QString fileName; - while (!loadCheck.atEnd()) { - fileName = QString::fromUtf8(loadCheck.readLine().constData()).trimmed(); - } - if (QMessageBox::question(this, tr("Plugin error"), - tr("It appears the plugin \"%1\" failed to load last startup and caused MO to crash. Do you want to disable it?\n" - "(Please note: If this is the first time you see this message for this plugin you may want to give it another try. " - "The plugin may be able to recover from the problem)").arg(fileName), - QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) { - m_Settings.addBlacklistPlugin(fileName); - } - loadCheck.close(); - } - - loadCheck.open(QIODevice::WriteOnly); - - QString pluginPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())) + "/" + ToQString(AppConfig::pluginPath()); - qDebug("looking for plugins in %s", QDir::toNativeSeparators(pluginPath).toUtf8().constData()); - QDirIterator iter(pluginPath, QDir::Files | QDir::NoDotAndDotDot); - - while (iter.hasNext()) { - iter.next(); - if (m_Settings.pluginBlacklisted(iter.fileName())) { - qDebug("plugin \"%s\" blacklisted", qPrintable(iter.fileName())); - continue; - } - loadCheck.write(iter.fileName().toUtf8()); - loadCheck.write("\n"); - loadCheck.flush(); - QString pluginName = iter.filePath(); - if (QLibrary::isLibrary(pluginName)) { - QPluginLoader pluginLoader(pluginName); - if (pluginLoader.instance() == NULL) { - m_UnloadedPlugins.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(pluginName)); - } else { - m_UnloadedPlugins.push_back(pluginName); - qWarning("plugin \"%s\" failed to load", qPrintable(pluginName)); - } - } - } - } - - // remove the load check file on success - loadCheck.remove(); - - m_DownloadManager.setSupportedExtensions(m_InstallationManager.getSupportedExtensions()); - - m_DiagnosisPlugins.push_back(this); -} - - -void MainWindow::startSteam() -{ - QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam", - QSettings::NativeFormat); - QString exe = steamSettings.value("SteamExe", "").toString(); - if (!exe.isEmpty()) { - QString temp = QString("\"%1\"").arg(exe); - if (!QProcess::startDetached(temp)) { - reportError(tr("Failed to start \"%1\"").arg(temp)); - } else { - QMessageBox::information(this, tr("Waiting"), tr("Please press OK once you're logged into steam.")); - } - } -} - - -HANDLE MainWindow::spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, - const QDir ¤tDirectory, const QString &steamAppID) -{ - storeSettings(); - - if (!binary.exists()) { - reportError(tr("Executable \"%1\" not found").arg(binary.fileName())); - return INVALID_HANDLE_VALUE; - } - - if (!steamAppID.isEmpty()) { - ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str()); - } else { - ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(m_Settings.getSteamAppID()).c_str()); - } - - if ((GameInfo::instance().requiresSteam()) && - (m_Settings.getLoadMechanism() == LoadMechanism::LOAD_MODORGANIZER)) { - if (!testForSteam()) { - if (QuestionBoxMemory::query(this->isVisible() ? this : NULL, - "steamQuery", 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(); - } - } - } - - 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())) { - return startBinary(binary, arguments, profileName, m_Settings.logLevel(), currentDirectory, true); - } else { - qDebug("start of \"%s\" canceled by plugin", qPrintable(binary.absoluteFilePath())); - return INVALID_HANDLE_VALUE; - } -} - - -void MainWindow::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, bool closeAfterStart, const QString &steamAppID) -{ - LockedDialog *dialog = new LockedDialog(this); - dialog->show(); - ON_BLOCK_EXIT([&] () { dialog->hide(); dialog->deleteLater(); }); - - HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->getName(), currentDirectory, steamAppID); - if (processHandle != INVALID_HANDLE_VALUE) { - if (closeAfterStart) { - close(); - } else { - this->setEnabled(false); - // re-enable the locked dialog because what'd be the point otherwise? - dialog->setEnabled(true); - - QCoreApplication::processEvents(); - - DWORD retLen; - JOBOBJECT_BASIC_PROCESS_ID_LIST info; - - bool isJobHandle = true; - - DWORD res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); - while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0) && !dialog->unlockClicked()) { - if (isJobHandle) { - if (::QueryInformationJobObject(processHandle, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) { - if (info.NumberOfProcessIdsInList == 0) { - break; - } - } 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; - } - } - } - - // keep processing events so the app doesn't appear dead - QCoreApplication::processEvents(); - - res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE); - } - ::CloseHandle(processHandle); - - this->setEnabled(true); - refreshDirectoryStructure(); - if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) { - QFile::remove(m_CurrentProfile->getLoadOrderFileName()); - } - } - } -} - - -void MainWindow::startExeAction() -{ - QAction *action = qobject_cast<QAction*>(sender()); - if (action != NULL) { - Executable selectedExecutable = action->data().value<Executable>(); - spawnBinary(selectedExecutable.m_BinaryInfo, - selectedExecutable.m_Arguments, - selectedExecutable.m_WorkingDirectory.length() != 0 ? selectedExecutable.m_WorkingDirectory - : selectedExecutable.m_BinaryInfo.absolutePath(), - selectedExecutable.m_CloseMO == DEFAULT_CLOSE, - selectedExecutable.m_SteamAppID); - } else { - qCritical("not an action?"); - } -} - - -void MainWindow::setExecutablesList(const ExecutablesList &executablesList) -{ - m_ExecutablesList = executablesList; - refreshExecutablesList(); - updateToolBar(); -} - -void MainWindow::setExecutableIndex(int index) -{ - QComboBox *executableBox = findChild<QComboBox*>("executablesListBox"); - - if ((index != 0) && (executableBox->count() > index)) { - executableBox->setCurrentIndex(index); - } else { - executableBox->setCurrentIndex(1); - } -} - -void MainWindow::activateSelectedProfile() -{ - QString profileName = ui->profileBox->currentText(); - qDebug("activate profile \"%s\"", qPrintable(profileName)); - QString profileDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())) - .append("/").append(profileName); - delete m_CurrentProfile; - m_CurrentProfile = new Profile(QDir(profileDir)); - m_ModList.setProfile(m_CurrentProfile); - - m_ModListSortProxy->setProfile(m_CurrentProfile); - - connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint))); - - refreshSaveList(); - refreshModList(); -} - -void MainWindow::on_profileBox_currentIndexChanged(int index) -{ - if (ui->profileBox->isEnabled()) { - int previousIndex = m_OldProfileIndex; - m_OldProfileIndex = index; - - if ((previousIndex != -1) && - (m_CurrentProfile != NULL) && - m_CurrentProfile->exists()) { - saveCurrentLists(); - } - - // 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) { - ProfilesDialog(m_GamePath).exec(); - while (!refreshProfiles()) { - ProfilesDialog(m_GamePath).exec(); - } - ui->profileBox->setCurrentIndex(previousIndex); - } else { - activateSelectedProfile(); - } - } -} - - -void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const DirectoryEntry &directoryEntry, bool conflictsOnly) -{ - { - std::vector<FileEntry::Ptr> files = directoryEntry.getFiles(); - for (auto iter = files.begin(); iter != files.end(); ++iter) { - FileEntry::Ptr current = *iter; - if (conflictsOnly && (current->getAlternatives().size() == 0)) { - continue; - } - - QString fileName = ToQString(current->getName()); - QStringList columns(fileName); - bool isArchive = false; - int originID = current->getOrigin(isArchive); - QString source = ToQString(m_DirectoryStructure->getOriginByID(originID).getName()); - 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<int> alternatives = current->getAlternatives(); - - if (!alternatives.empty()) { - std::wostringstream altString; - altString << ToWString(tr("Also in: <br>")); - for (std::vector<int>::iterator altIter = alternatives.begin(); - altIter != alternatives.end(); ++altIter) { - if (altIter != alternatives.begin()) { - altString << " , "; - } - altString << "<span style=\"white-space: nowrap;\"><i>" << m_DirectoryStructure->getOriginByID(*altIter).getName() << "</font></span>"; - } - 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<DirectoryEntry*>::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); -} - -void MainWindow::delayedRemove() -{ - foreach (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(); - - std::wstring virtualPath = (path + L"\\").substr(6) + ToWString(item->text(0)); - DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(virtualPath); - if (dir != NULL) { - 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<QComboBox*>("profileBox"); - - QString currentProfileName = profileBox->currentText(); - - profileBox->blockSignals(true); - profileBox->clear(); - profileBox->addItem(QObject::tr("<Manage...>")); - - QDir profilesDir(ToQString(GameInfo::instance().getProfilesDir())); - profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); - - QDirIterator profileIter(profilesDir); - - int newIndex = profileIter.hasNext() ? 1 : 0; - int currentIndex = 0; - while (profileIter.hasNext()) { - profileIter.next(); - ++currentIndex; - try { - profileBox->addItem(profileIter.fileName()); - if (currentProfileName == profileIter.fileName()) { - newIndex = currentIndex; - } - } 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) { - if (currentProfileName.length() != 0) { - if ((newIndex != 0) && (profileBox->count() > newIndex)) { - profileBox->setCurrentIndex(newIndex); - } else { - profileBox->setCurrentIndex(1); - } - } - return true; - } else { - return false; - } - } else { - return profileBox->count() > 1; - } -} - -std::set<QString> MainWindow::enabledArchives() -{ - std::set<QString> result; - QFile archiveFile(m_CurrentProfile->getArchivesFileName()); - if (archiveFile.open(QIODevice::ReadOnly)) { - while (!archiveFile.atEnd()) { - result.insert(QString::fromUtf8(archiveFile.readLine()).trimmed()); - } - archiveFile.close(); - } - return result; -} - -void MainWindow::refreshDirectoryStructure() -{ - m_DirectoryUpdate = true; - std::vector<std::tuple<QString, QString, int> > activeModList = m_CurrentProfile->getActiveMods(); - - m_DirectoryRefresher.setMods(activeModList, enabledArchives()); - - statusBar()->show(); - m_RefreshProgress->setRange(0, 100); - - QTimer::singleShot(0, &m_DirectoryRefresher, SLOT(refresh())); -} - -#if QT_VERSION >= 0x050000 -extern QPixmap qt_pixmapFromWinHICON(HICON icon); -#else -#define qt_pixmapFromWinHICON(icon) QPixmap::fromWinHICON(icon) -#endif - -QIcon MainWindow::iconForExecutable(const QString &filePath) -{ - HICON winIcon; - UINT res = ::ExtractIconExW(ToWString(filePath).c_str(), 0, &winIcon, NULL, 1); - if (res == 1) { - QIcon result = QIcon(qt_pixmapFromWinHICON(winIcon)); - ::DestroyIcon(winIcon); - return result; - } else { - return QIcon(":/MO/gui/executable"); - } -} - -void MainWindow::refreshExecutablesList() -{ - QComboBox* executablesList = findChild<QComboBox*>("executablesListBox"); - executablesList->setEnabled(false); - executablesList->clear(); - executablesList->addItem(tr("<Edit...>")); - - QAbstractItemModel *model = executablesList->model(); - - std::vector<Executable>::const_iterator current, end; - m_ExecutablesList.getExecutables(current, end); - for(int i = 0; current != end; ++current, ++i) { - QVariant temp; - temp.setValue(*current); - QIcon icon = iconForExecutable(current->m_BinaryInfo.filePath()); - executablesList->addItem(icon, current->m_Title, temp); - 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<QCheckBox*>("conflictsCheckBox"); - QTreeWidget *tree = findChild<QTreeWidget*>("dataTree"); - tree->clear(); - QStringList columns("data"); - columns.append(""); - QTreeWidgetItem *subTree = new QTreeWidgetItem(columns); - updateTo(subTree, L"", *m_DirectoryStructure, conflictsBox->isChecked()); - tree->insertTopLevelItem(0, subTree); - subTree->setExpanded(true); - tree->header()->resizeSection(0, 200); -} - - -void MainWindow::refreshSavesIfOpen() -{ - if (ui->tabWidget->currentIndex() == 3) { - refreshSaveList(); - } -} - - -void MainWindow::refreshSaveList() -{ - ui->savegameList->clear(); - - QDir savesDir; - if (m_CurrentProfile->localSavesEnabled()) { - savesDir.setPath(m_CurrentProfile->getPath() + "/saves"); - } else { - wchar_t path[MAX_PATH]; - ::GetPrivateProfileStringW(L"General", L"SLocalSavePath", L"Saves", - path, MAX_PATH, - (ToWString(m_CurrentProfile->getPath()) + L"\\" + GameInfo::instance().getIniFileNames().at(0)).c_str()); - savesDir.setPath(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getDocumentsDir() + L"\\" + path))); - } - - if (m_SavesWatcher.directories().length() > 0) { - m_SavesWatcher.removePaths(m_SavesWatcher.directories()); - } - m_SavesWatcher.addPath(savesDir.absolutePath()); - - QStringList filters; - filters << ToQString(GameInfo::instance().getSaveGameExtension()); - savesDir.setNameFilters(filters); - - QFileInfoList files = savesDir.entryInfoList(QDir::Files, QDir::Time); - - foreach (const QFileInfo &file, files) { - QListWidgetItem *item = new QListWidgetItem(file.fileName()); - item->setData(Qt::UserRole, file.absoluteFilePath()); - ui->savegameList->addItem(item); - } -} - - -void MainWindow::refreshLists() -{ - if ((m_CurrentProfile != NULL) && m_DirectoryStructure->isPopulated()) { - refreshESPList(); - refreshBSAList(); - } // no point in refreshing lists if no files have been added to the directory tree -} - - -void MainWindow::refreshESPList() -{ - m_CurrentProfile->writeModlist(); - - // clear list - try { - m_PluginList.refresh(m_CurrentProfile->getName(), - *m_DirectoryStructure, - m_CurrentProfile->getPluginsFileName(), - m_CurrentProfile->getLoadOrderFileName(), - m_CurrentProfile->getLockedOrderFileName()); - } catch (const std::exception &e) { - reportError(tr("Failed to refresh list of esps: %1").arg(e.what())); - } -} - -void MainWindow::refreshModList(bool saveChanges) -{ - // don't lose changes! - if (saveChanges) { - m_CurrentProfile->writeModlistNow(true); - } - ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign()); - - m_CurrentProfile->refreshModStatus(); - - m_ModList.notifyChange(-1); - - refreshDirectoryStructure(); -} - - -static bool BySortValue(const std::pair<UINT32, QTreeWidgetItem*> &LHS, const std::pair<UINT32, QTreeWidgetItem*> &RHS) -{ - return LHS.first < RHS.first; -} - - -template <typename InputIterator> -QStringList toStringList(InputIterator current, InputIterator end) -{ - QStringList result; - for (; current != end; ++current) { - result.append(*current); - } - return result; -} - - -void MainWindow::refreshBSAList() -{ - m_ArchivesInit = false; - ui->bsaList->clear(); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - ui->bsaList->header()->setSectionResizeMode(QHeaderView::ResizeToContents); -#else - ui->bsaList->header()->setResizeMode(QHeaderView::ResizeToContents); -#endif - - m_DefaultArchives.clear(); - - wchar_t buffer[256]; - std::wstring iniFileName = ToWString(QDir::toNativeSeparators(m_CurrentProfile->getIniFileName())); - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - L"", buffer, 256, iniFileName.c_str()) != 0) { - m_DefaultArchives = ToQString(buffer).split(','); - } else { - std::vector<std::wstring> vanillaBSAs = GameInfo::instance().getVanillaBSAs(); - for (auto iter = vanillaBSAs.begin(); iter != vanillaBSAs.end(); ++iter) { - m_DefaultArchives.append(ToQString(*iter)); - } - } - - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().append(L"2").c_str(), - L"", buffer, 256, iniFileName.c_str()) != 0) { - m_DefaultArchives.append(ToQString(buffer).split(',')); - } - - for (int i = 0; i < m_DefaultArchives.count(); ++i) { - m_DefaultArchives[i] = m_DefaultArchives[i].trimmed(); - } - - m_ActiveArchives.clear(); - - auto iter = enabledArchives(); - m_ActiveArchives = toStringList(iter.begin(), iter.end()); - if (m_ActiveArchives.isEmpty()) { - m_ActiveArchives = m_DefaultArchives; - } - - std::vector<std::pair<UINT32, QTreeWidgetItem*> > items; - - std::vector<FileEntry::Ptr> files = m_DirectoryStructure->getFiles(); - for (auto iter = files.begin(); iter != files.end(); ++iter) { - FileEntry::Ptr current = *iter; - - QString filename = ToQString(current->getName().c_str()); - QString extension = filename.right(3).toLower(); - - if (extension == "bsa") { - int index = m_ActiveArchives.indexOf(filename); - if (index == -1) { - index = 0xFFFF; - } - QString basename = filename.left(filename.indexOf(".")); - QStringList strings(filename); - bool isArchive = false; - int origin = current->getOrigin(isArchive); - strings.append(ToQString(m_DirectoryStructure->getOriginByID(origin).getName())); - QTreeWidgetItem *newItem = new QTreeWidgetItem(strings); - newItem->setData(0, Qt::UserRole, index); - newItem->setData(1, Qt::UserRole, origin); - newItem->setFlags(newItem->flags() & ~Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable); - newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked); - newItem->setData(0, Qt::UserRole, false); - if (m_Settings.forceEnableCoreFiles() - && m_DefaultArchives.contains(filename)) { - newItem->setCheckState(0, Qt::Checked); - newItem->setDisabled(true); - newItem->setData(0, Qt::UserRole, true); - } else if ((m_PluginList.state(basename + ".esp") == IPluginList::STATE_ACTIVE) - || (m_PluginList.state(basename + ".esm") == IPluginList::STATE_ACTIVE)) { - newItem->setCheckState(0, Qt::Checked); - newItem->setDisabled(true); - } else { - if (ui->manageArchivesBox->isChecked()) { - newItem->setCheckState(0, (index != 0xFFFF) ? Qt::Checked : Qt::Unchecked); - } else { - newItem->setCheckState(0, Qt::Unchecked); - newItem->setDisabled(true); - } - } - - if (index < 0) index = 0; - - UINT32 sortValue = ((m_DirectoryStructure->getOriginByID(origin).getPriority() & 0xFFFF) << 16) | (index & 0xFFFF); - items.push_back(std::make_pair(sortValue, newItem)); - } - } - - std::sort(items.begin(), items.end(), BySortValue); - - for (std::vector<std::pair<UINT32, QTreeWidgetItem*> >::iterator iter = items.begin(); iter != items.end(); ++iter) { - int originID = iter->second->data(1, Qt::UserRole).toInt(); - FilesOrigin origin = m_DirectoryStructure->getOriginByID(originID); - QList<QTreeWidgetItem*> items = ui->bsaList->findItems(ToQString(origin.getName()), Qt::MatchFixedString); - QTreeWidgetItem *subItem = NULL; - if (items.length() > 0) { - subItem = items.at(0); - } else { - subItem = new QTreeWidgetItem(QStringList(ToQString(origin.getName()))); - subItem->setFlags(subItem->flags() & ~Qt::ItemIsDragEnabled); - ui->bsaList->addTopLevelItem(subItem); - } - subItem->addChild(iter->second); - subItem->setExpanded(true); - } - - checkBSAList(); - m_ArchivesInit = true; -} - - -void MainWindow::checkBSAList() -{ - ui->bsaList->blockSignals(true); - - 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 (m_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()); - } - - ui->bsaList->blockSignals(false); -} - - -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<int> categories = modInfo->getCategories(); - for (std::set<int>::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<QNetworkProxy> 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<void> future = QtConcurrent::run(MainWindow::setupNetworkProxy, activate); - while (!future.isFinished()) { - QCoreApplication::processEvents(); - ::Sleep(100); - } - busyDialog.hide(); -} - -void MainWindow::readSettings() -{ - QSettings settings(ToQString(GameInfo::instance().getIniFilename()), QSettings::IniFormat); - - 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("log_split")) { - ui->topLevelSplitter->restoreState(settings.value("log_split").toByteArray()); - } - - bool filtersVisible = settings.value("filters_visible", false).toBool(); - setCategoryListVisible(filtersVisible); - ui->displayCategoriesBtn->setChecked(filtersVisible); - - int selectedExecutable = settings.value("selected_executable").toInt(); - setExecutableIndex(selectedExecutable); - - if (settings.value("Settings/use_proxy", false).toBool()) { - activateProxy(true); - } - - ui->manageArchivesBox->blockSignals(true); - ui->manageArchivesBox->setChecked(settings.value("manage_bsas", true).toBool()); - ui->manageArchivesBox->blockSignals(false); -} - - -void MainWindow::storeSettings() -{ - if (m_CurrentProfile == NULL) { - return; - } - m_CurrentProfile->writeModlist(); - m_CurrentProfile->createTweakedIniFile(); - saveCurrentLists(); - m_Settings.setupLoadMechanism(); - - QString iniFile = ToQString(GameInfo::instance().getIniFilename()); - shellCopy(iniFile, iniFile + ".new", true, this); - - QSettings::Status result = QSettings::NoError; - { - QSettings settings(iniFile + ".new", QSettings::IniFormat); - if (m_CurrentProfile != NULL) { - settings.setValue("selected_profile", m_CurrentProfile->getName().toUtf8().constData()); - } else { - settings.remove("selected_profile"); - } - - settings.setValue("mod_list_state", ui->modList->header()->saveState()); - settings.setValue("plugin_list_state", ui->espList->header()->saveState()); - - settings.setValue("group_state", ui->groupCombo->currentIndex()); - - settings.setValue("ask_for_nexuspw", m_AskForNexusPW); - - 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("filters_visible", ui->displayCategoriesBtn->isChecked()); - settings.setValue("manage_bsas", ui->manageArchivesBox->isChecked()); - - settings.remove("customExecutables"); - settings.beginWriteArray("customExecutables"); - std::vector<Executable>::const_iterator current, end; - m_ExecutablesList.getExecutables(current, end); - int count = 0; - for (; current != end; ++current) { - const Executable &item = *current; - if (item.m_Custom || item.m_Toolbar) { - settings.setArrayIndex(count++); - settings.setValue("binary", item.m_BinaryInfo.absoluteFilePath()); - settings.setValue("title", item.m_Title); - settings.setValue("arguments", item.m_Arguments); - settings.setValue("workingDirectory", item.m_WorkingDirectory); - settings.setValue("closeOnStart", item.m_CloseMO == DEFAULT_CLOSE); - settings.setValue("steamAppID", item.m_SteamAppID); - settings.setValue("custom", item.m_Custom); - settings.setValue("toolbar", item.m_Toolbar); - } - } - settings.endArray(); - - QComboBox *executableBox = findChild<QComboBox*>("executablesListBox"); - settings.setValue("selected_executable", executableBox->currentIndex()); - - FileDialogMemory::save(settings); - - settings.sync(); - result = settings.status(); - } - if (result == QSettings::NoError) { - if (!shellRename(iniFile + ".new", iniFile, true, this)) { - QMessageBox::critical(this, tr("Failed to write settings"), - tr("An error occured trying to write back MO settings: %1").arg(windowsErrorString(::GetLastError()))); - } - } else { - 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(this, tr("Failed to write settings"), - tr("An error occured trying to write back MO settings: %1").arg(reason)); - } -} - - -void MainWindow::on_btnRefreshData_clicked() -{ - if (!m_DirectoryUpdate) { - refreshDirectoryStructure(); - } else { - qDebug("directory update"); - } -} - -void MainWindow::on_tabWidget_currentChanged(int index) -{ - if (index == 0) { - refreshESPList(); - } else if (index == 1) { - refreshBSAList(); - } else if (index == 2) { - refreshDataTree(); - } else if (index == 3) { - refreshSaveList(); - } else if (index == 4) { - ui->downloadView->scrollToBottom(); - } -} - -std::vector<unsigned int> MainWindow::activeProblems() const -{ - std::vector<unsigned int> problems; - if (m_UnloadedPlugins.size() != 0) { - problems.push_back(PROBLEM_PLUGINSNOTLOADED); - } - if (m_PluginList.enabledCount() > 255) { - problems.push_back(PROBLEM_TOOMANYPLUGINS); - } - return problems; -} - -QString MainWindow::shortDescription(unsigned int key) const -{ - switch (key) { - case PROBLEM_PLUGINSNOTLOADED: { - return tr("Some plugins could not be loaded"); - } break; - case PROBLEM_TOOMANYPLUGINS: { - return tr("Too many esps and esms enabled"); - } break; - default: { - return tr("Description missing"); - } break; - } -} - -QString MainWindow::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:") + "<ul>"; - foreach (const QString &plugin, m_UnloadedPlugins) { - result += "<li>" + plugin + "</li>"; - } - result += "<ul>"; - return result; - } break; - case PROBLEM_TOOMANYPLUGINS: { - return tr("The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or " - "merge some plugins into one. You can find a guide here: <a href=\"http://wiki.step-project.com/Guide:Merging_Plugins\">http://wiki.step-project.com/Guide:Merging_Plugins</a>"); - } break; - default: { - return tr("Description missing"); - } break; - } -} - -bool MainWindow::hasGuidedFix(unsigned int) const -{ - return false; -} - -void MainWindow::startGuidedFix(unsigned int) const -{ -} - -void MainWindow::installMod() -{ - try { - QStringList extensions = m_InstallationManager.getSupportedExtensions(); - for (auto iter = extensions.begin(); iter != extensions.end(); ++iter) { - *iter = "*." + *iter; - } - - QString fileName = FileDialogMemory::getOpenFileName("installMod", this, tr("Choose Mod"), QString(), - tr("Mod Archive").append(QString(" (%1)").arg(extensions.join(" ")))); - - if (fileName.length() == 0) { - return; - } else { - installMod(fileName); - } - } catch (const std::exception &e) { - reportError(e.what()); - } -} - -IModInterface *MainWindow::installMod(const QString &fileName) -{ - if (m_CurrentProfile == NULL) { - return NULL; - } - - bool hasIniTweaks = false; - GuessedValue<QString> modName; - m_CurrentProfile->writeModlistNow(); - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) { - MessageDialog::showMessage(tr("Installation successful"), this); - refreshModList(); - - QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, static_cast<const QString&>(modName)); - if (posList.count() == 1) { - ui->modList->scrollTo(posList.at(0)); - } - int modIndex = ModInfo::getIndex(modName); - if (modIndex != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - if (hasIniTweaks && - (QMessageBox::question(this, tr("Configure Mod"), - tr("This mod contains ini tweaks. Do you want to configure them now?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - 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(this, tr("Installation cancelled"), tr("The mod was not installed completely."), QMessageBox::Ok); - } - return NULL; -} - -IModInterface *MainWindow::getMod(const QString &name) -{ - unsigned int index = ModInfo::getIndex(name); - if (index == UINT_MAX) { - return NULL; - } else { - return ModInfo::getByIndex(index).data(); - } -} - -IModInterface *MainWindow::createMod(GuessedValue<QString> &name) -{ - if (!m_InstallationManager.testOverwrite(name)) { - return NULL; - } - - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - - QString targetDirectory = QDir::fromNativeSeparators(m_Settings.getModDirectory()).append("/").append(name); - - QSettings settingsFile(targetDirectory.mid(0).append("/meta.ini"), QSettings::IniFormat); - - 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(); -} - -bool MainWindow::removeMod(IModInterface *mod) -{ - unsigned int index = ModInfo::getIndex(mod->name()); - if (index == UINT_MAX) { - return mod->remove(); - } else { - return ModInfo::removeMod(index); - } -} - -QList<IOrganizer::FileInfo> MainWindow::findFileInfos(const QString &path, const std::function<bool (const IOrganizer::FileInfo &)> &filter) const -{ - QList<IOrganizer::FileInfo> result; - DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path)); - if (dir != NULL) { - std::vector<FileEntry::Ptr> 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 (int idx, file->getAlternatives()) { - info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(idx).getName())); - } - - if (filter(info)) { - result.append(info); - } - } - } - return result; -} - -void MainWindow::on_startButton_clicked() -{ - QComboBox* executablesList = findChild<QComboBox*>("executablesListBox"); - - Executable selectedExecutable = executablesList->itemData(executablesList->currentIndex()).value<Executable>(); - - spawnBinary(selectedExecutable.m_BinaryInfo, - selectedExecutable.m_Arguments, - selectedExecutable.m_WorkingDirectory.length() != 0 ? selectedExecutable.m_WorkingDirectory - : selectedExecutable.m_BinaryInfo.absolutePath(), - selectedExecutable.m_CloseMO == DEFAULT_CLOSE, - selectedExecutable.m_SteamAppID); -} - - -static HRESULT CreateShortcut(LPCWSTR targetFileName, LPCWSTR arguments, - LPCSTR linkFileName, LPCWSTR description, - LPCWSTR currentDirectory) -{ - HRESULT result = E_INVALIDARG; - if ((targetFileName != NULL) && (wcslen(targetFileName) > 0) && - (arguments != NULL) && - (linkFileName != NULL) && (strlen(linkFileName) > 0) && - (description != NULL) && - (currentDirectory != NULL)) { - - IShellLink* shellLink; - result = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER, - IID_IShellLink, (LPVOID*)&shellLink); - - if (!SUCCEEDED(result)) { - qCritical("failed to create IShellLink instance"); - return result; - } - if (!SUCCEEDED(result)) 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; - } - } - - IPersistFile* persistFile; - result = shellLink->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile); - if (SUCCEEDED(result)) { - wchar_t linkFileNameW[MAX_PATH]; - MultiByteToWideChar(CP_ACP, 0, linkFileName, -1, linkFileNameW, MAX_PATH); - result = persistFile->Save(linkFileNameW, TRUE); - persistFile->Release(); - } else { - qCritical("failed to create IPersistFile instance"); - } - - shellLink->Release(); - } - return result; -} - - -bool MainWindow::modifyExecutablesDialog() -{ - bool result = false; - try { - EditExecutablesDialog dialog(m_ExecutablesList); - if (dialog.exec() == QDialog::Accepted) { - m_ExecutablesList = 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<QComboBox*>("executablesListBox"); - - int previousIndex = m_OldExecutableIndex; - m_OldExecutableIndex = index; - - if (executablesList->isEnabled()) { - - if (executablesList->itemData(index).isNull()) { - if (modifyExecutablesDialog()) { - setExecutableIndex(previousIndex); -// executablesList->setCurrentIndex(previousIndex); - } - } else { - setExecutableIndex(index); - } - } -} - -void MainWindow::helpTriggered() -{ - QWhatsThis::enterWhatsThisMode(); -} - -void MainWindow::wikiTriggered() -{ -// ::ShellExecuteW(NULL, L"open", L"http://issue.tannin.eu/tbg/wiki/Modorganizer%3AMainPage", NULL, NULL, SW_SHOWNORMAL); - ::ShellExecuteW(NULL, L"open", L"http://wiki.step-project.com/Guide:Mod_Organizer", NULL, NULL, SW_SHOWNORMAL); -} - -void MainWindow::issueTriggered() -{ - ::ShellExecuteW(NULL, L"open", L"http://issue.tannin.eu/tbg", NULL, NULL, SW_SHOWNORMAL); -} - -void MainWindow::tutorialTriggered() -{ - QAction *tutorialAction = qobject_cast<QAction*>(sender()); - if (tutorialAction != NULL) { - 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() -{ - bool repeat = true; - while (repeat) { - ProfilesDialog profilesDialog(m_GamePath); - profilesDialog.exec(); - if (refreshProfiles() && !profilesDialog.failed()) { - repeat = false; - } - } -// 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; - } -} - - -bool MainWindow::queryLogin(QString &username, QString &password) -{ - CredentialsDialog dialog(this); - 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 MainWindow::setCurrentProfile(int index) -{ - QComboBox *profilesBox = findChild<QComboBox*>("profileBox"); - if (index >= profilesBox->count()) { - return false; - } else { - profilesBox->setCurrentIndex(index); - return true; - } -} - -bool MainWindow::setCurrentProfile(const QString &name) -{ - QComboBox *profilesBox = findChild<QComboBox*>("profileBox"); - for (int i = 0; i < profilesBox->count(); ++i) { - if (QString::compare(profilesBox->itemText(i), name, Qt::CaseInsensitive) == 0) { - profilesBox->setCurrentIndex(i); - return true; - } - } - // profile not valid - profilesBox->setCurrentIndex(1); - return false; -} - - -void MainWindow::refresher_progress(int percent) -{ - m_RefreshProgress->setValue(percent); -} - - -void MainWindow::directory_refreshed() -{ - DirectoryEntry *newStructure = m_DirectoryRefresher.getDirectoryStructure(); - Q_ASSERT(newStructure != m_DirectoryStructure); - if (newStructure != NULL) { - std::swap(m_DirectoryStructure, newStructure); - delete newStructure; - refreshDataTree(); - } else { - // TODO: don't know why this happens, this slot seems to get called twice with only one emit - return; - } - m_DirectoryUpdate = false; - if (m_CurrentProfile != NULL) { - refreshLists(); - } - - // some problem-reports may rely on the virtual directory tree so they need to be updated - // now - updateProblemsButton(); - - for (int i = 0; i < m_ModList.rowCount(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - modInfo->clearCaches(); - } - statusBar()->hide(); -} - -void MainWindow::externalMessage(const QString &message) -{ - if (message.left(6).toLower() == "nxm://") { - MessageDialog::showMessage(tr("Download started"), this); - downloadRequestedNXM(message); - } -} - -void MainWindow::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(); - saveArchiveList(); - m_DirectoryRefresher.setMods(m_CurrentProfile->getActiveMods(), enabledArchives()); - - // 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 MainWindow::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); - } - } - 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 MainWindow::removeOrigin(const QString &name) -{ - FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(name)); - origin.enable(false); - refreshLists(); -} - - -void MainWindow::modorder_changed() -{ - for (unsigned int i = 0; i < m_CurrentProfile->numMods(); ++i) { - int priority = m_CurrentProfile->getModPriority(i); - if (m_CurrentProfile->modEnabled(i)) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - // priorities in the directory structure are one higher because data is 0 - m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())).setPriority(priority + 1); - } - } - refreshBSAList(); - m_CurrentProfile->writeModlist(); - saveArchiveList(); - m_DirectoryStructure->getFileRegister()->sortOrigins(); -} - -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::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()); - m_CurrentProfile->refreshModStatus(); - - refreshModList(); -} - -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::renameModInList(QFile &modList, const QString &oldName, const QString &newName) -{ - //TODO this code needs to be merged with ModList::readFrom - if (!modList.open(QIODevice::ReadWrite)) { - reportError(tr("failed to open %1").arg(modList.fileName())); - return; - } - - QBuffer outBuffer; - outBuffer.open(QIODevice::WriteOnly); - - 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; - } - - QString modName = QString::fromUtf8(line).mid(1).trimmed(); - - if (modName.isEmpty()) { - // file broken? - qWarning("mod list contained invalid data: missing mod name"); - continue; - } - - outBuffer.write(QByteArray(1, spec)); - if (modName == oldName) { - modName = newName; - } - outBuffer.write(modName.toUtf8().constData()); - outBuffer.write("\r\n"); - } - - modList.resize(0); - modList.write(outBuffer.buffer()); - modList.close(); -} - - -void MainWindow::modRenamed(const QString &oldName, const QString &newName) -{ - // fix the profiles directly on disc - for (int i = 0; i < ui->profileBox->count(); ++i) { - QString profileName = ui->profileBox->itemText(i); - - //TODO this functionality should be in the Profile class - QString modlistName = QString("%1/%2/modlist.txt") - .arg(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))) - .arg(profileName); - - QFile modList(modlistName); - if (modList.exists()) { - qDebug("rewrite modlist %s", QDir::toNativeSeparators(modlistName).toUtf8().constData()); - renameModInList(modList, oldName, newName); - } - } - - // immediately refresh the active profile because the data in memory is invalid - m_CurrentProfile->refreshModStatus(); - - // also fix the directory structure - try { - if (m_DirectoryStructure->originExists(ToWString(oldName))) { - FilesOrigin &origin = m_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::modlistChanged(int) -{ - m_ModListSortProxy->invalidate(); -} - - -void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName) -{ - const FileEntry::Ptr filePtr = m_DirectoryStructure->findFile(ToWString(filePath)); - if (filePtr.get() != NULL) { - try { - if (m_DirectoryStructure->originExists(ToWString(newOriginName))) { - FilesOrigin &newOrigin = m_DirectoryStructure->getOriginByName(ToWString(newOriginName)); - - QString fullNewPath = ToQString(newOrigin.getPath()) + "\\" + filePath; - WIN32_FIND_DATAW findData; - ::FindFirstFileW(ToWString(fullNewPath).c_str(), &findData); - - filePtr->addOrigin(newOrigin.getID(), findData.ftCreationTime, L""); - } - if (m_DirectoryStructure->originExists(ToWString(oldOriginName))) { - FilesOrigin &oldOrigin = m_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) -{ - QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(name)); - item->setData(0, Qt::UserRole, categoryID); - if (root != NULL) { - root->addChild(item); - } else { - ui->categoriesList->addTopLevelItem(item); - } - return item; -} - - -void MainWindow::addCategoryFilters(QTreeWidgetItem *root, const std::set<int> &categoriesUsed, int targetID) -{ - for (unsigned i = 1; i < 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); - if (m_CategoryFactory.hasChildren(i)) { - addCategoryFilters(item, categoriesUsed, categoryID); - } - } - } - } -} - - -void MainWindow::refreshFilters() -{ - QItemSelection currentSelection = ui->modList->selectionModel()->selection(); - - ui->modList->setCurrentIndex(QModelIndex()); - - QStringList selectedItems; - foreach (QTreeWidgetItem *item, ui->categoriesList->selectedItems()) { - selectedItems.append(item->text(0)); - } - - ui->categoriesList->clear(); - addFilterItem(NULL, tr("<Checked>"), CategoryFactory::CATEGORY_SPECIAL_CHECKED); - addFilterItem(NULL, tr("<Unchecked>"), CategoryFactory::CATEGORY_SPECIAL_UNCHECKED); - addFilterItem(NULL, tr("<Update>"), CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE); - addFilterItem(NULL, tr("<Managed by MO>"), CategoryFactory::CATEGORY_SPECIAL_MANAGED); - addFilterItem(NULL, tr("<Managed outside MO>"), CategoryFactory::CATEGORY_SPECIAL_UNMANAGED); - addFilterItem(NULL, tr("<No category>"), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY); - addFilterItem(NULL, tr("<Conflicted>"), CategoryFactory::CATEGORY_SPECIAL_CONFLICT); - addFilterItem(NULL, tr("<Not Endorsed>"), CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED); - - std::set<int> categoriesUsed; - for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIdx); - BOOST_FOREACH (int categoryID, modInfo->getCategories()) { - int currentID = categoryID; - // also add parents so they show up in the tree - while (currentID != 0) { - categoriesUsed.insert(currentID); - currentID = m_CategoryFactory.getParentID(m_CategoryFactory.getCategoryIndex(currentID)); - } - } - } - - addCategoryFilters(NULL, categoriesUsed, 0); - - foreach (const QString &item, selectedItems) { - QList<QTreeWidgetItem*> 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); -} - - -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_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_Settings.getModDirectory()) + "/" + regName; - if (!modDir.rename(modInfo->absolutePath(), destinationPath)) { - reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath)); - } - refreshModList(); - } - } - } -} - -void MainWindow::updateModActiveState(int index, bool active) -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - - QDir dir(modInfo->absolutePath()); - foreach (const QString &esm, dir.entryList(QStringList("*.esm"), QDir::Files)) { - m_PluginList.enableESP(esm, active); - } - int enabled = 0; - QStringList esps = dir.entryList(QStringList("*.esp"), QDir::Files); - foreach (const QString &esp, esps) { - if (active != m_PluginList.isEnabled(esp)) { - m_PluginList.enableESP(esp, active); - ++enabled; - } - } - if (active && (enabled > 1)) { - MessageDialog::showMessage(tr("Multiple esps activated, please check that they don't conflict."), this); - } - m_PluginList.refreshLoadOrder(); - // immediately save affected lists - savePluginList(); -// refreshBSAList(); -} - -void MainWindow::modlistChanged(const QModelIndex&, int) -{ - m_CurrentProfile->writeModlist(); -} - -void MainWindow::modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex&) -{ - ModInfo::Ptr selectedMod = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt()); - m_ModList.setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten()); - if (m_ModListSortProxy != NULL) { - m_ModListSortProxy->invalidate(); - } - 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; - foreach (QModelIndex idx, selection->selectedRows()) { -// QString name = ModInfo::getByIndex(m_ModListGroupProxy->mapToSource(idx).row())->name(); - QString name = idx.data().toString(); - if (!ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->isRegular()) { - continue; - } - mods += "<li>" + name + "</li>"; - modNames.append(name); - } - if (QMessageBox::question(this, tr("Confirm"), - tr("Remove the following mods?<br><ul>%1</ul>").arg(mods), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - // use mod names instead of indexes because those become invalid during the removal - foreach (QString name, modNames) { - m_ModList.removeRowForce(ModInfo::getIndex(name)); - } - } - } else { - m_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_DownloadManager.indexByName(fileName); - if (index >= 0) { - m_DownloadManager.markUninstalled(index); - } - } -} - - -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_DownloadManager.getOutputDirectory().append("/").append(fileInfo.fileName()); - } - } else { - fullInstallationFile = m_DownloadManager.getOutputDirectory().append("/").append(installationFile); - } - if (QFile::exists(fullInstallationFile)) { - installMod(fullInstallationFile); - } 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::resumeDownload(int downloadIndex) -{ - if (NexusInterface::instance()->getAccessManager()->loggedIn()) { - m_DownloadManager.resumeDownload(downloadIndex); - } else { - QString username, password; - if (m_Settings.getNexusLogin(username, password)) { - m_PostLoginTasks.push_back(boost::bind(&MainWindow::resumeDownload, _1, downloadIndex)); - NexusInterface::instance()->getAccessManager()->login(username, password); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to resume a download"), this); - } - } -} - - -void MainWindow::endorseMod(ModInfo::Ptr mod) -{ - if (NexusInterface::instance()->getAccessManager()->loggedIn()) { - mod->endorse(true); - } else { - QString username, password; - if (m_Settings.getNexusLogin(username, password)) { - m_PostLoginTasks.push_back(boost::bind(&MainWindow::endorseMod, _1, mod)); - NexusInterface::instance()->getAccessManager()->login(username, password); - } else { - MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); - } - } -} - - -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); - } else { - if (m_Settings.getNexusLogin(username, password)) { - m_PostLoginTasks.push_back(boost::mem_fn(&MainWindow::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::overwriteClosed(int) -{ - OverwriteInfoDialog *dialog = this->findChild<OverwriteInfoDialog*>("__overwriteDialog"); - if (dialog != NULL) { - m_ModList.modInfoChanged(dialog->modInfo()); - dialog->deleteLater(); - } -} - - -void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab) -{ - m_ModList.modInfoAboutToChange(modInfo); - std::vector<ModInfo::EFlag> flags = modInfo->getFlags(); - if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) { - QDialog *dialog = this->findChild<QDialog*>("__overwriteDialog"); - try { - if (dialog == NULL) { - dialog = new OverwriteInfoDialog(modInfo, this); - dialog->setObjectName("__overwriteDialog"); - } else { - qobject_cast<OverwriteInfoDialog*>(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_DirectoryStructure, modInfo->hasFlag(ModInfo::FLAG_FOREIGN), this); - connect(&dialog, SIGNAL(nexusLinkActivated(QString)), this, SLOT(nexusLinkActivated(QString))); - connect(&dialog, SIGNAL(downloadRequest(QString)), this, 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_Settings.directInterface().value("mod_info_tabs").toByteArray()); - dialog.exec(); - m_Settings.directInterface().setValue("mod_info_tabs", dialog.saveTabState()); - - modInfo->saveMeta(); - emit modInfoDisplayed(); - m_ModList.modInfoChanged(modInfo); - } - - if (m_CurrentProfile->modEnabled(index)) { - FilesOrigin& origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); - origin.enable(false); - - if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) { - FilesOrigin& origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())); - origin.enable(false); - - m_DirectoryRefresher.addModToStructure(m_DirectoryStructure - , modInfo->name() - , m_CurrentProfile->getModPriority(index) - , modInfo->absolutePath() - , modInfo->stealFiles() - , modInfo->archives()); - DirectoryRefresher::cleanStructure(m_DirectoryStructure); - refreshLists(); - } - } -} - - -void MainWindow::modOpenNext() -{ - QModelIndex index = m_ModListSortProxy->mapFromSource(m_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<ModInfo::EFlag> 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::modOpenPrev() -{ - QModelIndex index = m_ModListSortProxy->mapFromSource(m_ModList.index(m_ContextRow, 0)); - int row = index.row() - 1; - if (row == -1) { - row = m_ModListSortProxy->rowCount() - 1; - } - - m_ContextRow = m_ModListSortProxy->mapToSource(m_ModListSortProxy->index(row, 0)).row(); - ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow); - std::vector<ModInfo::EFlag> 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); - } -} - -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::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_ModList, SIGNAL(dataChanged(QModelIndex,QModelIndex))); - - emit modListDataChanged(m_ModList.index(m_ContextRow, 0), m_ModList.index(m_ContextRow, m_ModList.columnCount() - 1)); -} - - -void MainWindow::visitOnNexus_clicked() -{ - int modID = m_ModList.data(m_ModList.index(m_ContextRow, 0), Qt::UserRole).toInt(); - if (modID > 0) { - nexusLinkActivated(QString("%1/mods/%2").arg(ToQString(GameInfo::instance().getNexusPage(false))).arg(modID)); - } else { - MessageDialog::showMessage(tr("Nexus ID for this Mod is unknown"), this); - } -} - -void MainWindow::openExplorer_clicked() -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - - ::ShellExecuteW(NULL, L"explore", ToWString(modInfo->absolutePath()).c_str(), NULL, NULL, SW_SHOWNORMAL); -} - -void MainWindow::information_clicked() -{ - try { - displayModInformation(m_ContextRow); - } catch (const std::exception &e) { - reportError(e.what()); - } -} - -void MainWindow::syncOverwrite() -{ - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure, this); - if (syncDialog.exec() == QDialog::Accepted) { - syncDialog.apply(QDir::fromNativeSeparators(m_Settings.getModDirectory())); - modInfo->testValid(); - refreshDirectoryStructure(); - } - -} - -void MainWindow::createModFromOverwrite() -{ - GuessedValue<QString> 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 (getMod(name) != NULL) { - reportError(tr("A mod with this name already exists")); - return; - } - - IModInterface *newMod = createMod(name); - if (newMod == NULL) { - return; - } - - ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(m_ContextRow); - shellMove(QStringList(QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"), - QStringList(QDir::toNativeSeparators(newMod->absolutePath())), this); - - refreshModList(); -} - -void MainWindow::cancelModListEditor() -{ - ui->modList->setEnabled(false); - ui->modList->setEnabled(true); -} - -void MainWindow::on_modList_doubleClicked(const QModelIndex &index) -{ - if (!index.isValid()) { - return; - } - QModelIndex sourceIdx = mapToModel(&m_ModList, index); - if (!sourceIdx.isValid()) { - return; - } - - try { - m_ContextRow = m_ModListSortProxy->mapToSource(index).row(); -// displayModInformation(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<int> &categories = modInfo->getCategories(); - - bool childEnabled = false; - - for (unsigned 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<QCheckBox> 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<QWidgetAction> 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); - foreach (QAction* action, menu->actions()) { - if (action->menu() != NULL) { - replaceCategoriesFromMenu(action->menu(), modRow); - } else { - QWidgetAction *widgetAction = qobject_cast<QWidgetAction*>(action); - if (widgetAction != NULL) { - QCheckBox *checkbox = qobject_cast<QCheckBox*>(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); - foreach (QAction* action, menu->actions()) { - if (action->menu() != NULL) { - addRemoveCategoriesFromMenu(action->menu(), modRow, referenceRow); - } else { - QWidgetAction *widgetAction = qobject_cast<QWidgetAction*>(action); - if (widgetAction != NULL) { - QCheckBox *checkbox = qobject_cast<QCheckBox*>(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::addRemoveCategories_MenuHandler() { - QMenu *menu = qobject_cast<QMenu*>(sender()); - if (menu == NULL) { - qCritical("not a menu?"); - return; - } - - QModelIndexList selectedTemp = ui->modList->selectionModel()->selectedRows(); - QList<QPersistentModelIndex> selected; - foreach (const QModelIndex &idx, selectedTemp) { - selected.append(QPersistentModelIndex(idx)); - } - - if (selected.size() > 0) { - foreach (const QPersistentModelIndex &idx, selected) { - qDebug("change categories on: %s (ref: %s)", qPrintable(idx.data().toString()), qPrintable(m_ContextIdx.data().toString())); - QModelIndex modIdx = mapToModel(&m_ModList, idx); - if (modIdx.row() != m_ContextIdx.row()) { - addRemoveCategoriesFromMenu(menu, modIdx.row(), m_ContextIdx.row()); - } - } - replaceCategoriesFromMenu(menu, m_ContextIdx.row()); - - m_ModList.notifyChange(-1); - - foreach (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_ModList.notifyChange(m_ContextRow); - } - - refreshFilters(); -} - -void MainWindow::replaceCategories_MenuHandler() { - QMenu *menu = qobject_cast<QMenu*>(sender()); - if (menu == NULL) { - qCritical("not a menu?"); - return; - } - - QModelIndexList selected = ui->modList->selectionModel()->selectedRows(); - - if (selected.size() > 0) { - QStringList selectedMods; - for (int i = 0; i < selected.size(); ++i) { - QModelIndex temp = mapToModel(&m_ModList, selected.at(i)); - selectedMods.append(temp.data().toString()); - replaceCategoriesFromMenu(menu, mapToModel(&m_ModList, selected.at(i)).row()); - } - - m_ModList.notifyChange(-1); - - // find mods by their name because indices are invalidated - QAbstractItemModel *model = ui->modList->model(); - Q_FOREACH(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_ModList.notifyChange(m_ContextRow); - } - - refreshFilters(); -} - -void MainWindow::savePrimaryCategory() -{ - QMenu *menu = qobject_cast<QMenu*>(sender()); - if (menu == NULL) { - qCritical("not a menu?"); - return; - } - - foreach (QAction* action, menu->actions()) { - QWidgetAction *widgetAction = qobject_cast<QWidgetAction*>(action); - if (widgetAction != NULL) { - QRadioButton *btn = qobject_cast<QRadioButton*>(widgetAction->defaultWidget()); - if (btn->isChecked()) { - QModelIndexList selected = ui->modList->selectionModel()->selectedRows(); - for (int i = 0; i < selected.size(); ++i) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ModListSortProxy->mapToSource(selected.at(i)).row()); - modInfo->setPrimaryCategory(widgetAction->data().toInt()); - } - break; - } - } - } -} - -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_Settings.getNexusLogin(username, password)) { - m_PostLoginTasks.push_back(boost::mem_fn(&MainWindow::checkModsForUpdates)); - NexusInterface::instance()->getAccessManager()->login(username, password); - } else { // otherwise there will be no endorsement info - m_ModsToUpdate = ModInfo::checkAllForUpdate(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::ignoreUpdate() { - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - info->ignoreUpdate(true); -} - -void MainWindow::unignoreUpdate() -{ - ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow); - info->ignoreUpdate(false); -} - -void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info) -{ - const std::set<int> &categories = info->getCategories(); - foreach (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); - 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::addPrimaryCategoryCandidates() -{ - QMenu *menu = qobject_cast<QMenu*>(sender()); - if (menu == NULL) { - qCritical("not a menu?"); - return; - } - menu->clear(); - ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow); - - addPrimaryCategoryCandidates(menu, modInfo); -} - -void MainWindow::enableVisibleMods() -{ - if (QMessageBox::question(NULL, tr("Confirm"), tr("Really enable all visible mods?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_ModListSortProxy->enableAllVisible(); - } -} - -void MainWindow::disableVisibleMods() -{ - if (QMessageBox::question(NULL, tr("Confirm"), tr("Really disable all visible mods?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_ModListSortProxy->disableAllVisible(); - } -} - -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); - - if (selection.exec() == QDialog::Accepted) { - unsigned int numMods = ModInfo::getNumMods(); - - try { - QBuffer buffer; - buffer.open(QIODevice::ReadWrite); - CSVBuilder builder(&buffer); - builder.setEscapeMode(CSVBuilder::TYPE_STRING, CSVBuilder::QUOTE_ALWAYS); - std::vector<std::pair<QString, CSVBuilder::EFieldType> > fields; - fields.push_back(std::make_pair(QString("mod_id"), CSVBuilder::TYPE_INTEGER)); - fields.push_back(std::make_pair(QString("mod_installed_name"), CSVBuilder::TYPE_STRING)); - fields.push_back(std::make_pair(QString("mod_version"), CSVBuilder::TYPE_STRING)); - fields.push_back(std::make_pair(QString("file_installed_name"), CSVBuilder::TYPE_STRING)); -// fields.push_back(std::make_pair(QString("file_category"), CSVBuilder::TYPE_INTEGER)); - builder.setFields(fields); - - builder.writeHeader(); - - for (unsigned int i = 0; i < numMods; ++i) { - ModInfo::Ptr info = ModInfo::getByIndex(i); - bool enabled = m_CurrentProfile->modEnabled(i); - if ((selection.getChoiceData().toInt() == 1) && !enabled) { - continue; - } else if ((selection.getChoiceData().toInt() == 2) && !m_ModListSortProxy->filterMatchesMod(info, enabled)) { - continue; - } - std::vector<ModInfo::EFlag> 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())) { - builder.setRowField("mod_id", info->getNexusID()); - builder.setRowField("mod_installed_name", info->name()); - builder.setRowField("mod_version", info->getVersion().canonicalString()); - builder.setRowField("file_installed_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())); - } - } -} - -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); -} - -QMenu *MainWindow::modListContextMenu() -{ - QMenu *menu = new QMenu(this); - menu->addAction(tr("Install Mod..."), this, SLOT(installMod_clicked())); - - 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"), this, SLOT(profileRefresh())); - - menu->addAction(tr("Export to csv..."), this, SLOT(exportModListCSV())); - return menu; -} - -void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos) -{ - try { - QTreeView *modList = findChild<QTreeView*>("modList"); - - m_ContextIdx = mapToModel(&m_ModList, modList->indexAt(pos)); - m_ContextRow = m_ContextIdx.row(); - - QMenu *menu = NULL; - 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<ModInfo::EFlag> 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..."), this, SLOT(syncOverwrite())); - menu->addAction(tr("Create Mod..."), this, SLOT(createModFromOverwrite())); - } - } 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())); - connect(primaryCategoryMenu, SIGNAL(aboutToHide()), this, SLOT(savePrimaryCategory())); - addMenuAsPushButton(menu, primaryCategoryMenu); - - menu->addSeparator(); - if (info->downgradeAvailable()) { - menu->addAction(tr("Change versioning scheme"), this, SLOT(changeVersioningScheme())); - } - if (info->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())); - 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; - } - std::vector<ModInfo::EFlag> 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())); - } - - menu->addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked())); - menu->addAction(tr("Open in explorer"), this, SLOT(openExplorer_clicked())); - } - - 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); - } - } - - menu->exec(modList->mapToGlobal(pos)); - } catch (const std::exception &e) { - reportError(tr("Exception: ").arg(e.what())); - } catch (...) { - reportError(tr("Unknown exception")); - } -} - - -void MainWindow::on_categoriesList_itemSelectionChanged() -{ - QModelIndexList indices = ui->categoriesList->selectionModel()->selectedRows(); - std::vector<int> categories; - foreach (const QModelIndex &index, indices) { - int categoryId = index.data(Qt::UserRole).toInt(); - if (categoryId != CategoryFactory::CATEGORY_NONE) { - categories.push_back(categoryId); - } - } - - m_ModListSortProxy->setCategoryFilter(categories); - ui->clickBlankLabel->setEnabled(categories.size() > 0); - if (indices.count() == 0) { - ui->currentCategoryLabel->setText(QString("(%1)").arg(tr("<All>"))); - } else if (indices.count() > 1) { - ui->currentCategoryLabel->setText(QString("(%1)").arg(tr("<Multiple>"))); - } else { - ui->currentCategoryLabel->setText(QString("(%1)").arg(indices.first().data().toString())); - } - ui->modList->reset(); -} - - -void MainWindow::deleteSavegame_clicked() -{ - QModelIndexList selectedIndexes = ui->savegameList->selectionModel()->selectedIndexes(); - - QString savesMsgLabel; - QStringList deleteFiles; - - foreach (const QModelIndex &idx, selectedIndexes) { - QString name = idx.data().toString(); - SaveGame *save = new SaveGame(this, idx.data(Qt::UserRole).toString()); - - savesMsgLabel += "<li>" + QFileInfo(name).completeBaseName() + "</li>"; - - deleteFiles << save->saveFiles(); - } - - if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin.", "", selectedIndexes.count()) - .arg(savesMsgLabel), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - shellDelete(deleteFiles, true); // recycle bin delete. - } -} - - -void MainWindow::fixMods_clicked() -{ - QListWidgetItem *selectedItem = ui->savegameList->currentItem(); - - if (selectedItem == NULL) - return; - - // if required, parse the save game - if (selectedItem->data(Qt::UserRole).isNull()) { - QVariant temp; - SaveGameGamebryo *save = getSaveGame(selectedItem->data(Qt::UserRole).toString()); - save->setParent(selectedItem->listWidget()); - temp.setValue(save); - selectedItem->setData(Qt::UserRole, temp); - } - - const SaveGameGamebryo *save = getSaveGame(selectedItem); - - // collect the list of missing plugins - std::map<QString, std::vector<QString> > missingPlugins; - - for (int i = 0; i < save->numPlugins(); ++i) { - const QString &pluginName = save->plugin(i); - if (!m_PluginList.isEnabled(pluginName)) { - missingPlugins[pluginName] = std::vector<QString>(); - } - } - - // figure out, for each esp/esm, which mod, if any, contains it - QStringList espFilter("*.esp"); - espFilter.append("*.esm"); - - // search in data - { - QDir dataDir(m_GamePath + "/data"); - QStringList esps = dataDir.entryList(espFilter); - foreach (const QString &esp, esps) { - std::map<QString, std::vector<QString> >::iterator iter = missingPlugins.find(esp); - if (iter != missingPlugins.end()) { - iter->second.push_back("<data>"); - } - } - } - - // search in mods - for (unsigned int i = 0; i < m_CurrentProfile->numRegularMods(); ++i) { - int modIndex = m_CurrentProfile->modIndexByPriority(i); - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - - QStringList esps = QDir(modInfo->absolutePath()).entryList(espFilter); - foreach (const QString &esp, esps) { - std::map<QString, std::vector<QString> >::iterator iter = missingPlugins.find(esp); - if (iter != missingPlugins.end()) { - iter->second.push_back(modInfo->name()); - } - } - } - - // search in overwrite - { - QDir overwriteDir(ToQString(GameInfo::instance().getOverwriteDir())); - QStringList esps = overwriteDir.entryList(espFilter); - foreach (const QString &esp, esps) { - std::map<QString, std::vector<QString> >::iterator iter = missingPlugins.find(esp); - if (iter != missingPlugins.end()) { - iter->second.push_back("<overwrite>"); - } - } - } - - - ActivateModsDialog dialog(missingPlugins, this); - if (dialog.exec() == QDialog::Accepted) { - // activate the required mods, then enable all esps - std::set<QString> modsToActivate = dialog.getModsToActivate(); - for (std::set<QString>::iterator iter = modsToActivate.begin(); iter != modsToActivate.end(); ++iter) { - if ((*iter != "<data>") && (*iter != "<overwrite>")) { - unsigned int modIndex = ModInfo::getIndex(*iter); - m_CurrentProfile->setModEnabled(modIndex, true); - } - } - - m_CurrentProfile->writeModlist(); - refreshLists(); - - std::set<QString> espsToActivate = dialog.getESPsToActivate(); - for (std::set<QString>::iterator iter = espsToActivate.begin(); iter != espsToActivate.end(); ++iter) { - m_PluginList.enableESP(*iter); - } - saveCurrentLists(); - } -} - - -void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos) -{ - QItemSelectionModel *selection = ui->savegameList->selectionModel(); - - if (!selection->hasSelection()) - return; - - QMenu menu; - - if (!(selection->selectedIndexes().count() > 1)) - menu.addAction(tr("Fix Mods..."), this, SLOT(fixMods_clicked())); - - 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::linkToolbar() -{ - const Executable &selectedExecutable = ui->executablesListBox->itemData(ui->executablesListBox->currentIndex()).value<Executable>(); - Executable &exe = m_ExecutablesList.find(selectedExecutable.m_Title); - exe.m_Toolbar = !exe.m_Toolbar; - ui->linkButton->menu()->actions().at(2)->setIcon(exe.m_Toolbar ? QIcon(":/MO/gui/remove") : QIcon(":/MO/gui/link")); - updateToolBar(); -} - -void MainWindow::linkDesktop() -{ - QComboBox* executablesList = findChild<QComboBox*>("executablesListBox"); - - const Executable &selectedExecutable = executablesList->itemData(executablesList->currentIndex()).value<Executable>(); - QString linkName = getDesktopDirectory() + "\\" + selectedExecutable.m_Title + ".lnk"; - - if (QFile::exists(linkName)) { - if (QFile::remove(linkName)) { - ui->linkButton->menu()->actions().at(0)->setIcon(QIcon(":/MO/gui/link")); - } else { - reportError(tr("failed to remove %1").arg(linkName)); - } - } else { - QFileInfo exeInfo(qApp->arguments().at(0)); - // create link - std::wstring targetFile = ToWString(exeInfo.absoluteFilePath()); - std::wstring parameter = ToWString(QString("\"%1\" %2").arg(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath())) - .arg(selectedExecutable.m_Arguments)); - std::wstring description = ToWString(selectedExecutable.m_BinaryInfo.fileName()); - std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(exeInfo.absolutePath())); - if (CreateShortcut(targetFile.c_str() - , parameter.c_str() - , linkName.toUtf8().constData() - , description.c_str() - , currentDirectory.c_str()) != E_INVALIDARG) { - ui->linkButton->menu()->actions().at(0)->setIcon(QIcon(":/MO/gui/remove")); - } else { - reportError(tr("failed to create %1").arg(linkName)); - } - } -} - -void MainWindow::linkMenu() -{ - QComboBox* executablesList = findChild<QComboBox*>("executablesListBox"); - - const Executable &selectedExecutable = executablesList->itemData(executablesList->currentIndex()).value<Executable>(); - QString linkName = getStartMenuDirectory() + "\\" + selectedExecutable.m_Title + ".lnk"; - - if (QFile::exists(linkName)) { - if (QFile::remove(linkName)) { - ui->linkButton->menu()->actions().at(1)->setIcon(QIcon(":/MO/gui/link")); - } else { - reportError(tr("failed to remove %1").arg(linkName)); - } - } else { - QFileInfo exeInfo(qApp->arguments().at(0)); - // create link - std::wstring targetFile = ToWString(exeInfo.absoluteFilePath()); - std::wstring parameter = ToWString(QString("\"%1\" %2").arg(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath())) - .arg(selectedExecutable.m_Arguments)); - std::wstring description = ToWString(selectedExecutable.m_BinaryInfo.fileName()); - std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(exeInfo.absolutePath())); - - if (CreateShortcut(targetFile.c_str(), parameter.c_str(), - linkName.toUtf8().constData(), - description.c_str(), currentDirectory.c_str()) != E_INVALIDARG) { - ui->linkButton->menu()->actions().at(1)->setIcon(QIcon(":/MO/gui/remove")); - } else { - reportError(tr("failed to create %1").arg(linkName)); - } - } -} - -void MainWindow::downloadSpeed(const QString &serverName, int bytesPerSecond) -{ - m_Settings.setDownloadSpeed(serverName, bytesPerSecond); -} - - -void MainWindow::on_actionSettings_triggered() -{ - QString oldModDirectory(m_Settings.getModDirectory()); - QString oldCacheDirectory(m_Settings.getCacheDirectory()); - bool oldDisplayForeign(m_Settings.displayForeign()); - bool proxy = m_Settings.useProxy(); - m_Settings.query(this); - m_InstallationManager.setModsDirectory(m_Settings.getModDirectory()); - m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory()); - fixCategories(); - refreshFilters(); - if (QDir::fromNativeSeparators(m_DownloadManager.getOutputDirectory()) != QDir::fromNativeSeparators(m_Settings.getDownloadDirectory())) { - if (m_DownloadManager.downloadsInProgress()) { - MessageDialog::showMessage(tr("Can't change download directory while downloads are in progress!"), this); - } else { - m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory()); - } - } - m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers()); - - if ((m_Settings.getModDirectory() != oldModDirectory) - || (m_Settings.displayForeign() != oldDisplayForeign)) { - profileRefresh(); - } - - if (m_Settings.getCacheDirectory() != oldCacheDirectory) { - NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory()); - } - - if (proxy != m_Settings.useProxy()) { - activateProxy(m_Settings.useProxy()); - } - - NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion()); - - updateDownloadListDelegate(); -} - - -void MainWindow::on_actionNexus_triggered() -{ - ::ShellExecuteW(NULL, L"open", GameInfo::instance().getNexusPage(false).c_str(), NULL, NULL, SW_SHOWNORMAL); -} - - -void MainWindow::nexusLinkActivated(const QString &link) -{ - ::ShellExecuteW(NULL, L"open", ToWString(link).c_str(), NULL, NULL, SW_SHOWNORMAL); - ui->tabWidget->setCurrentIndex(4); -} - - -void MainWindow::linkClicked(const QString &url) -{ - ::ShellExecuteW(NULL, L"open", ToWString(url).c_str(), NULL, NULL, SW_SHOWNORMAL); -} - - -bool MainWindow::nexusLogin() -{ - QString username, password; - - NXMAccessManager *accessManager = NexusInterface::instance()->getAccessManager(); - - if (!accessManager->loginAttempted() - && !accessManager->loggedIn() - && (m_Settings.getNexusLogin(username, password) - || (m_AskForNexusPW - && queryLogin(username, password)))) { - accessManager->login(username, password); - return true; - } else { - return false; - } -} - - -void MainWindow::downloadRequestedNXM(const QString &url) -{ - qDebug("download requested: %s", qPrintable(url)); - if (nexusLogin()) { - m_PendingDownloads.append(url); - } else { - m_DownloadManager.addNXMDownload(url); - } -} - - -void MainWindow::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"), this); - } - } catch (const std::exception &e) { - MessageDialog::showMessage(tr("Download failed"), this); - qCritical("exception starting download: %s", e.what()); - } -} - - -void MainWindow::installTranslator(const QString &name) -{ -/* if (m_CurrentLanguage == "en_US") { - return; - }*/ - QTranslator *translator = new QTranslator(this); - QString fileName = name + "_" + m_CurrentLanguage; - if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) { - if (m_CurrentLanguage != "en_US") { - qWarning("localization file %s not found", qPrintable(fileName)); - } // we don't actually expect localization files for english - } - qApp->installTranslator(translator); - m_Translators.push_back(translator); -} - - -void MainWindow::languageChange(const QString &newLanguage) -{ - foreach (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("<Manage...>")); - - createHelpWidget(); - - updateDownloadListDelegate(); - updateProblemsButton(); - - ui->listOptionsBtn->setMenu(modListContextMenu()); -} - - -void MainWindow::installDownload(int index) -{ - try { - QString fileName = m_DownloadManager.getFilePath(index); - int modID = m_DownloadManager.getModID(index); - GuessedValue<QString> modName; - - // see if there already are mods with the specified mod id - if (modID != 0) { - std::vector<ModInfo::Ptr> modInfo = ModInfo::getByModID(modID); - for (auto iter = modInfo.begin(); iter != modInfo.end(); ++iter) { - std::vector<ModInfo::EFlag> 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"), this); - refreshModList(); - - QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, static_cast<const QString&>(modName)); - if (posList.count() == 1) { - ui->modList->scrollTo(posList.at(0)); - } - int modIndex = ModInfo::getIndex(modName); - if (modIndex != UINT_MAX) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex); - - if (hasIniTweaks && - (QMessageBox::question(this, tr("Configure Mod"), - tr("This mod contains ini tweaks. Do you want to configure them now?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) { - displayModInformation(modInfo, modIndex, ModInfoDialog::TAB_INIFILES); - } - - m_ModInstalled(modName); - } else { - reportError(tr("mod \"%1\" not found").arg(modName)); - } - m_DownloadManager.markInstalled(index); - - emit modInstalled(); - } else if (m_InstallationManager.wasCancelled()) { - QMessageBox::information(this, tr("Installation cancelled"), tr("The mod was not installed completely."), QMessageBox::Ok); - } - } catch (const std::exception &e) { - reportError(e.what()); - } -} - - -void MainWindow::writeDataToFile(QFile &file, const QString &directory, const DirectoryEntry &directoryEntry) -{ - { // list files -// std::set<FileEntry>::const_iterator current, end; -// directoryEntry.getFiles(current, end); -// for (; current != end; ++current) { - - std::vector<FileEntry::Ptr> files = directoryEntry.getFiles(); - for (auto iter = files.begin(); iter != files.end(); ++iter) { - FileEntry::Ptr current = *iter; - 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; - fullName.append("\\").append(ToQString(current->getName())); - file.write(fullName.toUtf8()); - - file.write("\t("); - file.write(ToQString(m_DirectoryStructure->getOriginByID(origin).getName()).toUtf8()); - file.write(")\r\n"); - } - } - - { // recurse into subdirectories - std::vector<DirectoryEntry*>::const_iterator current, end; - directoryEntry.getSubDirectories(current, end); - for (; current != end; ++current) { - writeDataToFile(file, directory.mid(0).append("\\").append(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_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.completeSuffix(); - if ((extension == "exe") || - (extension == "cmd") || - (extension == "com") || - (extension == "bat")) { - binaryInfo = QFileInfo("C:\\Windows\\System32\\cmd.exe"); - arguments = QString("/C \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath())); - return 1; - } else if (extension == "jar") { - // 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(), NULL, buffer) > (HINSTANCE)32) { - DWORD binaryType = 0UL; - if (!::GetBinaryTypeW(targetPathW.c_str(), &binaryType)) { - qDebug("failed to determine binary type: %lu", ::GetLastError()); - } else if (binaryType == SCS_32BIT_BINARY) { - binaryPath = ToQString(buffer); - } - } - } - if (binaryPath.isEmpty() && (extension == "jar")) { - 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 != NULL) { - 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()) { - m_ExecutablesList.addExecutable(name, binaryInfo.absoluteFilePath(), - arguments, targetInfo.absolutePath(), - DEFAULT_STAY, QString(), - true, false); - 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_DirectoryStructure->getOriginByID(originID); - origin.enable(false); - m_DirectoryStructure->addFromOrigin(origin.getName(), origin.getPath(), origin.getPriority()); - DirectoryRefresher::cleanStructure(m_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 { - return; - } - } - - if (QFile::rename(oldName, newName)) { - originModified(m_ContextItem->data(1, Qt::UserRole + 1).toInt()); - refreshDataTree(); - } else { - reportError(tr("failed to rename \"%1\" to \"%2\"").arg(oldName).arg(QDir::toNativeSeparators(newName))); - } -} - - -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()); - refreshDataTree(); - } 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_Settings.getModDirectory().size() + 1; - offset = fileName.indexOf("/", offset); - fileName = fileName.mid(offset + 1); - - const FileEntry::Ptr file = m_DirectoryStructure->searchFile(ToWString(fileName), NULL); - - if (file.get() == NULL) { - reportError(tr("file not found: %1").arg(fileName)); - return; - } - - // set up preview dialog - PreviewDialog preview(fileName); - auto addFunc = [&] (int originId) { - FilesOrigin &origin = m_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_PreviewGenerator.genPreview(filePath); - if (wid == NULL) { - reportError(tr("failed to generate preview for %1").arg(filePath)); - } else { - preview.addVariant(ToQString(origin.getName()), wid); - } - } - }; - - addFunc(file->getOrigin()); - foreach (int i, file->getAlternatives()) { - addFunc(i); - } - 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::openDataFile() -{ - if (m_ContextItem != NULL) { - QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString()); - QFileInfo binaryInfo; - QString arguments; - switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) { - case 1: { - spawnBinaryDirect(binaryInfo, arguments, m_CurrentProfile->getName(), targetInfo.absolutePath(), ""); - } break; - case 2: { - ::ShellExecuteW(NULL, L"open", ToWString(targetInfo.absoluteFilePath()).c_str(), NULL, NULL, SW_SHOWNORMAL); - } break; - default: { - // nop - } break; - } - } -} - - -void MainWindow::updateAvailable() -{ - QToolBar *toolBar = findChild<QToolBar*>("toolBar"); - foreach (QAction *action, toolBar->actions()) { - if (action->text() == tr("Update")) { - action->setEnabled(true); - action->setToolTip(tr("Update available")); - break; - } - } -} - - -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_Settings.getMotDHash()) { - MotDDialog dialog(motd); - dialog.exec(); - m_Settings.setMotDHash(hash); - } - } - - ui->actionEndorseMO->setVisible(false); -} - - -void MainWindow::notEndorsedYet() -{ - ui->actionEndorseMO->setVisible(true); -} - - -void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos) -{ - QTreeWidget *dataTree = findChild<QTreeWidget*>("dataTree"); - m_ContextItem = dataTree->itemAt(pos.x(), pos.y()); - - QMenu menu; - if ((m_ContextItem != NULL) && (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_PreviewGenerator.previewSupported(QFileInfo(fileName).completeSuffix())) { - menu.addAction(tr("Preview"), this, SLOT(previewDataFile())); - } - - // 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.addSeparator(); - } - menu.addAction(tr("Write To File..."), this, SLOT(writeDataToFile())); - menu.addAction(tr("Refresh"), this, SLOT(on_btnRefreshData_clicked())); - - menu.exec(dataTree->mapToGlobal(pos)); -} - -void MainWindow::on_conflictsCheckBox_toggled(bool) -{ - refreshDataTree(); -} - - -void MainWindow::on_actionUpdate_triggered() -{ - if (nexusLogin()) { - m_PostLoginTasks.push_back([&](MainWindow*) { m_Updater.startUpdate(); }); - } else { - m_Updater.startUpdate(); - } -} - - -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(ToQString(GameInfo::instance().getNexusPage())), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - NexusInterface::instance()->requestToggleEndorsement(GameInfo::instance().getNexusModID(), true, this, QVariant(), QString()); - } -} - - -void MainWindow::updateDownloadListDelegate() -{ - if (m_Settings.compactDownloads()) { - ui->downloadView->setItemDelegate(new DownloadListWidgetCompactDelegate(&m_DownloadManager, m_Settings.metaDownloads(), - ui->downloadView, ui->downloadView)); - } else { - ui->downloadView->setItemDelegate(new DownloadListWidgetDelegate(&m_DownloadManager, m_Settings.metaDownloads(), - ui->downloadView, ui->downloadView)); - } - - DownloadListSortProxy *sortProxy = new DownloadListSortProxy(&m_DownloadManager, ui->downloadView); - sortProxy->setSourceModel(new DownloadList(&m_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::AscendingOrder); - ui->downloadView->header()->resizeSections(QHeaderView::Fixed); - - connect(ui->downloadView->itemDelegate(), SIGNAL(installDownload(int)), this, SLOT(installDownload(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(queryInfo(int)), &m_DownloadManager, SLOT(queryInfo(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(removeDownload(int, bool)), &m_DownloadManager, SLOT(removeDownload(int, bool))); - connect(ui->downloadView->itemDelegate(), SIGNAL(restoreDownload(int)), &m_DownloadManager, SLOT(restoreDownload(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(cancelDownload(int)), &m_DownloadManager, SLOT(cancelDownload(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(pauseDownload(int)), &m_DownloadManager, SLOT(pauseDownload(int))); - connect(ui->downloadView->itemDelegate(), SIGNAL(resumeDownload(int)), this, SLOT(resumeDownload(int))); -} - - -void MainWindow::modDetailsUpdated(bool) -{ - --m_ModsToUpdate; - 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<int> &modIDs, QVariant userData, QVariant resultData, int) -{ - m_ModsToUpdate -= modIDs.size(); - QVariantList resultList = resultData.toList(); - for (auto iter = resultList.begin(); iter != resultList.end(); ++iter) { - QVariantMap result = iter->toMap(); - if (result["id"].toInt() == GameInfo::instance().getNexusModID()) { - if (!result["voted_by_user"].toBool()) { - ui->actionEndorseMO->setVisible(true); - } - } else { - std::vector<ModInfo::Ptr> 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::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<ServerInfo> servers; - foreach (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_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::loginSuccessful(bool necessary) -{ - if (necessary) { - MessageDialog::showMessage(tr("login successful"), this); - } - foreach (QString url, m_PendingDownloads) { - downloadRequestedNXM(url); - } - m_PendingDownloads.clear(); - foreach (auto task, m_PostLoginTasks) { - task(this); - } - - m_PostLoginTasks.clear(); - NexusInterface::instance()->loginCompleted(); -} - - -void MainWindow::loginSuccessfulUpdate(bool necessary) -{ - if (necessary) { - MessageDialog::showMessage(tr("login successful"), this); - } - m_Updater.startUpdate(); -} - - -void MainWindow::loginFailed(const QString &message) -{ - if (!m_PendingDownloads.isEmpty()) { - MessageDialog::showMessage(tr("login failed: %1. Trying to download anyway").arg(message), this); - foreach (QString url, m_PendingDownloads) { - downloadRequestedNXM(url); - } - m_PendingDownloads.clear(); - } else { - MessageDialog::showMessage(tr("login failed: %1").arg(message), this); - m_PostLoginTasks.clear(); - statusBar()->hide(); - } - NexusInterface::instance()->loginCompleted(); -} - - -void MainWindow::loginFailedUpdate(const QString &message) -{ - MessageDialog::showMessage(tr("login failed: %1. You need to log-in with Nexus to update MO.").arg(message), this); -} - - -void MainWindow::windowTutorialFinished(const QString &windowName) -{ - m_Settings.directInterface().setValue(QString("CompletedWindowTutorials/") + windowName, true); -} - - -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; - } - } - - 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; -} - - -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::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_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; - } - - 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::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; - foreach (const QAction *action, menu.actions()) { - const QWidgetAction *widgetAction = qobject_cast<const QWidgetAction*>(action); - if (widgetAction != NULL) { - const QCheckBox *checkBox = qobject_cast<const QCheckBox*>(widgetAction->defaultWidget()); - if (checkBox != NULL) { - ui->modList->header()->setSectionHidden(i, !checkBox->isChecked()); - } - } - ++i; - } -} - - -void MainWindow::on_bsaList_customContextMenuRequested(const QPoint &pos) -{ - m_ContextItem = ui->bsaList->itemAt(pos); - -// m_ContextRow = ui->bsaList->indexOfTopLevelItem(ui->bsaList->itemAt(pos)); - - QMenu menu; - menu.addAction(tr("Extract..."), this, SLOT(extractBSATriggered())); - - menu.exec(ui->bsaList->mapToGlobal(pos)); -} - -void MainWindow::bsaList_itemMoved() -{ - saveArchiveList(); - m_CheckBSATimer.start(500); -} - - -void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int) -{ - saveArchiveList(); - m_CheckBSATimer.start(500); -} - -void MainWindow::on_actionProblems_triggered() -{ -// QString problemDescription; -// checkForProblems(problemDescription); -// QMessageBox::information(this, tr("Problems"), problemDescription); - ProblemsDialog problems(m_DiagnosisPlugins, this); - if (problems.hasProblems()) { - problems.exec(); - updateProblemsButton(); - } -} - -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_displayCategoriesBtn_toggled(bool checked) -{ - setCategoryListVisible(checked); -} - -void MainWindow::editCategories() -{ - CategoriesDialog dialog(this); - if (dialog.exec() == QDialog::Accepted) { - dialog.commitChanges(); - } -} - -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())); - - menu.exec(ui->categoriesList->mapToGlobal(pos)); -} - - -void MainWindow::updateESPLock(bool locked) -{ - QItemSelection currentSelection = ui->espList->selectionModel()->selection(); - if (currentSelection.count() == 0) { - // this path is probably useless - m_PluginList.lockESPIndex(m_ContextRow, locked); - } else { - Q_FOREACH (const QModelIndex &idx, currentSelection.indexes()) { - m_PluginList.lockESPIndex(mapToModel(&m_PluginList, idx).row(), locked); - } - } -} - - -void MainWindow::lockESPIndex() -{ - updateESPLock(true); -} - -void MainWindow::unlockESPIndex() -{ - updateESPLock(false); -} - - -void MainWindow::removeFromToolbar() -{ - try { - Executable &exe = m_ExecutablesList.find(m_ContextAction->text()); - exe.m_Toolbar = false; - } catch (const std::runtime_error&) { - qDebug("executable doesn't exist any more"); - } - - updateToolBar(); -} - - -void MainWindow::toolBar_customContextMenuRequested(const QPoint &point) -{ - QAction *action = ui->toolBar->actionAt(point); - if (action != NULL) { - if (action->objectName().startsWith("custom_")) { - m_ContextAction = action; - QMenu menu; - menu.addAction(tr("Remove"), this, SLOT(removeFromToolbar())); - menu.exec(ui->toolBar->mapToGlobal(point)); - } - } -} - -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_PluginList, SLOT(enableAll())); - menu.addAction(tr("Disable all"), &m_PluginList, SLOT(disableAll())); - - QItemSelection currentSelection = ui->espList->selectionModel()->selection(); - bool hasLocked = false; - bool hasUnlocked = false; - Q_FOREACH (const QModelIndex &idx, currentSelection.indexes()) { - int row = m_PluginListSortProxy->mapToSource(idx).row(); - if (m_PluginList.isEnabled(row)) { - if (m_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 == NULL) { - return; - } - QAbstractProxyModel *newModel = NULL; - switch (index) { - case 1: { - newModel = new QtGroupingProxy(&m_ModList, QModelIndex(), ModList::COL_CATEGORY, Qt::UserRole, - 0, Qt::UserRole + 2); - } break; - case 2: { - newModel = new QtGroupingProxy(&m_ModList, QModelIndex(), ModList::COL_MODID, Qt::DisplayRole, - QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE, - Qt::UserRole + 2); - } break; - default: { - newModel = NULL; - } break; - } - - if (newModel != NULL) { -#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_ModList); - } - modFilterActive(m_ModListSortProxy->isFilterActive()); -} - -void MainWindow::on_linkButton_pressed() -{ - const Executable &selectedExecutable = ui->executablesListBox->itemData(ui->executablesListBox->currentIndex()).value<Executable>(); - - QIcon addIcon(":/MO/gui/link"); - QIcon removeIcon(":/MO/gui/remove"); - - QFileInfo linkDesktopFile(QDir::fromNativeSeparators(getDesktopDirectory()) + "/" + selectedExecutable.m_Title + ".lnk"); - QFileInfo linkMenuFile(QDir::fromNativeSeparators(getStartMenuDirectory()) + "/" + selectedExecutable.m_Title + ".lnk"); - - ui->linkButton->menu()->actions().at(0)->setIcon(selectedExecutable.m_Toolbar ? removeIcon : addIcon); - ui->linkButton->menu()->actions().at(1)->setIcon(linkDesktopFile.exists() ? removeIcon : addIcon); - ui->linkButton->menu()->actions().at(2)->setIcon(linkMenuFile.exists() ? removeIcon : addIcon); -} - -void MainWindow::on_showHiddenBox_toggled(bool checked) -{ - m_DownloadManager.setShowHidden(checked); -} - - -void MainWindow::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite) -{ - SECURITY_ATTRIBUTES secAttributes; - secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES); - secAttributes.bInheritHandle = TRUE; - secAttributes.lpSecurityDescriptor = NULL; - - 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; - } -} - -std::string MainWindow::readFromPipe(HANDLE stdOutRead) -{ - static const int chunkSize = 128; - std::string result; - - char buffer[chunkSize + 1]; - buffer[chunkSize] = '\0'; - - DWORD read = 1; - while (read > 0) { - if (!::ReadFile(stdOutRead, buffer, chunkSize, &read, NULL)) { - break; - } - if (read > 0) { - result.append(buffer, read); - if (read < chunkSize) { - break; - } - } - } - return result; -} - -void MainWindow::processLOOTOut(const std::string &lootOut, std::string &reportURL, std::string &errorMessages, QProgressDialog &dialog) -{ - std::vector<std::string> lines; - boost::split(lines, lootOut, boost::is_any_of("\r\n")); - - std::tr1::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\."); - - foreach (const std::string &line, lines) { - if (line.length() > 0) { - size_t progidx = line.find("[progress]"); - size_t reportidx = line.find("[report]"); - size_t erroridx = line.find("[error]"); - if (progidx != std::string::npos) { - dialog.setLabelText(line.substr(progidx + 11).c_str()); - } else if (reportidx != std::string::npos) { - reportURL = line.substr(reportidx + 9); - } 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_PluginList.addInformation(modName.c_str(), tr("depends on missing \"%1\"").arg(dependency.c_str())); - } else { - qDebug("%s", line.c_str()); - } - } - } - } -} - - -HANDLE MainWindow::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 != NULL) { - profileName = m_CurrentProfile->getName(); - } else { - throw MyException(tr("No profile set")); - } - } - QString steamAppID; - if (executable.contains('\\') || executable.contains('/')) { - // file path - binary = QFileInfo(executable); - if (binary.isRelative()) { - // relative path, should be relative to game directory - binary = QFileInfo(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/" + executable); - } - if (cwd.length() == 0) { - currentDirectory = binary.absolutePath(); - } - } else { - // only a file name, search executables list - try { - const Executable &exe = m_ExecutablesList.find(executable); - steamAppID = exe.m_SteamAppID; - 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); -} - -void MainWindow::on_bossButton_clicked() -{ - std::string reportURL; - std::string errorMessages; - - m_CurrentProfile->writeModlistNow(); - - bool success = false; - - try { - this->setEnabled(false); - ON_BLOCK_EXIT([&] () { this->setEnabled(true); }); - QProgressDialog dialog(this); - dialog.setLabelText(tr("LOOT working")); - dialog.setMaximum(0); - dialog.show(); - - QStringList parameters; - parameters << "--game" << ToQString(GameInfo::instance().getGameShortName()) - << "--gamePath" << QString("\"%1\"").arg(ToQString(GameInfo::instance().getGameDirectory())); - - if (!m_DidUpdateMasterList) { - parameters << "--updateMasterlist"; - m_DidUpdateMasterList = true; - } - - HANDLE stdOutWrite = INVALID_HANDLE_VALUE; - HANDLE stdOutRead = INVALID_HANDLE_VALUE; - createStdoutPipe(&stdOutRead, &stdOutWrite); - HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"), - parameters.join(" "), - m_CurrentProfile->getName(), - m_Settings.logLevel(), - qApp->applicationDirPath() + "/loot", - true, - stdOutWrite); - - // we don't use the write end - ::CloseHandle(stdOutWrite); - - m_PluginList.clearAdditionalInformation(); - - DWORD retLen; - JOBOBJECT_BASIC_PROCESS_ID_LIST info; - bool isJobHandle = true; - - if (loot != INVALID_HANDLE_VALUE) { - DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 1000, 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 { - // 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); - } - } - - // keep processing events so the app doesn't appear dead - QCoreApplication::processEvents(); - std::string lootOut = readFromPipe(stdOutRead); - processLOOTOut(lootOut, reportURL, errorMessages, dialog); - - res = ::MsgWaitForMultipleObjects(1, &loot, false, 1000, QS_KEY | QS_MOUSE); - } - - std::string remainder = readFromPipe(stdOutRead).c_str(); - if (remainder.length() > 0) { - processLOOTOut(remainder, reportURL, errorMessages, dialog); - } - DWORD exitCode = 0UL; - ::GetExitCodeProcess(loot, &exitCode); - if (exitCode != 0UL) { - reportError(tr("loot failed. Exit code was: %1").arg(exitCode)); - return; - } else { - success = true; - } - } else { - 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) { - if (reportURL.length() > 0) { - m_IntegratedBrowser.setWindowTitle("LOOT Report"); - QString report(reportURL.c_str()); - if (QFile::exists(report)) { - m_IntegratedBrowser.openUrl(QUrl::fromLocalFile(report)); - } else { - qWarning("report file missing"); - } - } - refreshESPList(); - - if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) { - QFile::remove(m_CurrentProfile->getLoadOrderFileName()); - } - } -} - - -const char *MainWindow::PATTERN_BACKUP_GLOB = ".????_??_??_??_??_??"; -const char *MainWindow::PATTERN_BACKUP_REGEX = "\\.(\\d\\d\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d)"; -const char *MainWindow::PATTERN_BACKUP_DATE = "yyyy_MM_dd_hh_mm_ss"; - - -bool MainWindow::createBackup(const QString &filePath, const QDateTime &time) -{ - 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() -{ - savePluginList(); - QDateTime now = QDateTime::currentDateTime(); - if (createBackup(m_CurrentProfile->getPluginsFileName(), now) - && createBackup(m_CurrentProfile->getLoadOrderFileName(), now) - && createBackup(m_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() + "\\.(.*)"); - foreach(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_CurrentProfile->getPluginsFileName(); - QString choice = queryRestore(pluginName); - if (!choice.isEmpty()) { - QString loadOrderName = m_CurrentProfile->getLoadOrderFileName(); - QString lockedName = m_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()))); - } - refreshESPList(); - } -} - -void MainWindow::on_saveModsButton_clicked() -{ - m_CurrentProfile->writeModlistNow(true); - QDateTime now = QDateTime::currentDateTime(); - if (createBackup(m_CurrentProfile->getModlistFileName(), now)) { - MessageDialog::showMessage(tr("Backup of modlist created"), this); - } -} -void MainWindow::on_restoreModsButton_clicked() -{ - QString modlistName = m_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()))); - } - 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::on_manageArchivesBox_toggled(bool) -{ - refreshBSAList(); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+#include <archive.h>
+#include "spawn.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 "utility.h"
+#include "modinfodialog.h"
+#include "overwriteinfodialog.h"
+#include "activatemodsdialog.h"
+#include "downloadlist.h"
+#include "downloadlistwidget.h"
+#include "downloadlistwidgetcompact.h"
+#include "messagedialog.h"
+#include "installationmanager.h"
+#include "lockeddialog.h"
+#include "syncoverwritedialog.h"
+#include "logbuffer.h"
+#include "downloadlistsortproxy.h"
+#include "motddialog.h"
+#include "filedialogmemory.h"
+#include "questionboxmemory.h"
+#include "tutorialmanager.h"
+#include "modflagicondelegate.h"
+#include "pluginflagicondelegate.h"
+#include "credentialsdialog.h"
+#include "selectiondialog.h"
+#include "csvbuilder.h"
+#include "gameinfoimpl.h"
+#include "savetextasdialog.h"
+#include "problemsdialog.h"
+#include "previewdialog.h"
+#include "browserdialog.h"
+#include "aboutdialog.h"
+#include "safewritefile.h"
+#include "organizerproxy.h"
+#include <gameinfo.h>
+#include <appconfig.h>
+#include <utility.h>
+#include <ipluginproxy.h>
+#include <questionboxmemory.h>
+#include <map>
+#include <ctime>
+#include <util.h>
+#include <wchar.h>
+#include <utility.h>
+#include <QTime>
+#include <QInputDialog>
+#include <QSettings>
+#include <QWhatsThis>
+#include <sstream>
+#include <QProcess>
+#include <QMenu>
+#include <QBuffer>
+#include <QInputDialog>
+#include <QDirIterator>
+#include <QHelpEvent>
+#include <QToolTip>
+#include <QFileDialog>
+#include <QTimer>
+#include <QMessageBox>
+#include <QDebug>
+#include <QBuffer>
+#include <QWidgetAction>
+#include <QToolButton>
+#include <QGraphicsObject>
+#include <QPluginLoader>
+#include <QRadioButton>
+#include <QDesktopWidget>
+#include <QtPlugin>
+#include <QIdentityProxyModel>
+#include <QClipboard>
+#include <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <boost/assign.hpp>
+#include <Psapi.h>
+#include <shlobj.h>
+#include <ShellAPI.h>
+#include <TlHelp32.h>
+#include <QNetworkInterface>
+#include <QNetworkProxy>
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+#include <QtConcurrent/QtConcurrentRun>
+#else
+#include <QtConcurrentRun>
+#endif
+#include <QCoreApplication>
+#include <QProgressDialog>
+#include <scopeguard.h>
+#include <boost/thread.hpp>
+#include <boost/algorithm/string.hpp>
+#include <regex>
+
+#ifdef TEST_MODELS
+#include "modeltest.h"
+#endif // TEST_MODELS
+
+#pragma warning( disable : 4428 )
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+
+
+static bool isOnline()
+{
+ QList<QNetworkInterface> 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;
+}
+
+
+MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget *parent)
+ : QMainWindow(parent), ui(new Ui::MainWindow), m_Tutorial(this, "MainWindow"),
+ m_ExeName(exeName), m_OldProfileIndex(-1),
+ m_DirectoryStructure(new DirectoryEntry(L"data", NULL, 0)),
+ m_ModList(this), m_ModListGroupingProxy(NULL), m_ModListSortProxy(NULL),
+ m_PluginList(this), m_OldExecutableIndex(-1), m_GamePath(ToQString(GameInfo::instance().getGameDirectory())),
+ m_DownloadManager(NexusInterface::instance(), this), m_InstallationManager(this),
+ m_Updater(NexusInterface::instance(), this), m_CategoryFactory(CategoryFactory::instance()),
+ m_CurrentProfile(NULL), m_AskForNexusPW(false),
+ m_ArchivesInit(false), m_DirectoryUpdate(false), m_ContextItem(NULL), m_ContextAction(NULL), m_CurrentSaveView(NULL),
+ m_GameInfo(new GameInfoImpl()), m_AboutToRun(), m_ModInstalled(), m_DidUpdateMasterList(false)
+{
+ ui->setupUi(this);
+ this->setWindowTitle(ToQString(GameInfo::instance().getGameName()) + " Mod Organizer v" + m_Updater.getVersion().displayString());
+
+ languageChange(m_Settings.language());
+
+ 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<int>() << 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);
+ statusBar()->addWidget(m_RefreshProgress, 1000);
+ statusBar()->clearMessage();
+
+ ui->actionEndorseMO->setVisible(false);
+
+ MOBase::QuestionBoxMemory::init(initSettings.fileName());
+
+ updateProblemsButton();
+
+ updateToolBar();
+
+ ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign());
+
+ // set up mod list
+ m_ModListSortProxy = new ModListSortProxy(m_CurrentProfile, this);
+ m_ModListSortProxy->setSourceModel(&m_ModList);
+
+#ifdef TEST_MODELS
+ new ModelTest(&m_ModList, this);
+ new ModelTest(m_ModListSortProxy, this);
+#endif //TEST_MODELS
+
+ ui->modList->setModel(m_ModListSortProxy);
+
+ ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder);
+ ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new ModFlagIconDelegate(ui->modList));
+ //ui->modList->setAcceptDrops(true);
+ ui->modList->header()->installEventFilter(&m_ModList);
+ if (initSettings.contains("mod_list_state")) {
+ ui->modList->header()->restoreState(initSettings.value("mod_list_state").toByteArray());
+ } else {
+ // hide these columns by default
+ 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_ModList);
+
+ // set up plugin list
+ m_PluginListSortProxy = new PluginListSortProxy(this);
+ m_PluginListSortProxy->setSourceModel(&m_PluginList);
+
+ ui->espList->setModel(m_PluginListSortProxy);
+ ui->espList->sortByColumn(PluginList::COL_PRIORITY, Qt::AscendingOrder);
+ ui->espList->setItemDelegateForColumn(PluginList::COL_FLAGS, new PluginFlagIconDelegate(ui->espList));
+ if (initSettings.contains("plugin_list_state")) {
+ ui->espList->header()->restoreState(initSettings.value("plugin_list_state").toByteArray());
+ }
+ ui->espList->installEventFilter(&m_PluginList);
+
+ ui->bsaList->setLocalMoveOnly(true);
+
+ ui->splitter->setStretchFactor(0, 3);
+ ui->splitter->setStretchFactor(1, 2);
+
+ resizeLists(initSettings.contains("mod_list_state"), initSettings.contains("plugin_list_state"));
+
+ QMenu *linkMenu = new QMenu(this);
+ 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->listOptionsBtn->setMenu(modListContextMenu());
+
+ m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory());
+ m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers());
+
+ NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory());
+ NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion());
+
+ m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
+ m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory());
+
+ updateDownloadListDelegate();
+
+ ui->savegameList->installEventFilter(this);
+ ui->savegameList->setMouseTracking(true);
+ connect(&m_DownloadManager, SIGNAL(showMessage(QString)), this, SLOT(showMessage(QString)));
+ connect(&m_DownloadManager, SIGNAL(downloadSpeed(QString,int)), this, SLOT(downloadSpeed(QString,int)));
+ connect(&m_DownloadManager, SIGNAL(downloadAdded()), ui->downloadView, SLOT(scrollToBottom()));
+
+ connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)), this, SLOT(saveSelectionChanged(QListWidgetItem*)));
+
+ connect(&m_ModList, SIGNAL(modorder_changed()), this, SLOT(modorder_changed()));
+ connect(&m_ModList, SIGNAL(removeOrigin(QString)), this, SLOT(removeOrigin(QString)));
+ connect(&m_ModList, SIGNAL(showMessage(QString)), this, SLOT(showMessage(QString)));
+ connect(&m_ModList, SIGNAL(modRenamed(QString,QString)), this, SLOT(modRenamed(QString,QString)));
+ connect(&m_ModList, SIGNAL(modUninstalled(QString)), this, SLOT(modRemoved(QString)));
+ connect(&m_ModList, SIGNAL(modlist_changed(QModelIndex, int)), this, SLOT(modlistChanged(QModelIndex, int)));
+ connect(&m_ModList, SIGNAL(removeSelectedMods()), this, SLOT(removeMod_clicked()));
+ connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), this, SLOT(displayColumnSelection(QPoint)));
+ connect(&m_ModList, SIGNAL(fileMoved(QString, QString, QString)), this, SLOT(fileMoved(QString, QString, QString)));
+ connect(ui->modList, SIGNAL(dropModeUpdate(bool)), &m_ModList, SLOT(dropModeUpdate(bool)));
+ connect(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(ui->espFilterEdit, SIGNAL(textChanged(QString)), m_PluginListSortProxy, SLOT(updateFilter(QString)));
+ connect(ui->espFilterEdit, SIGNAL(textChanged(QString)), this, SLOT(espFilterChanged(QString)));
+ connect(&m_PluginList, SIGNAL(saveTimer()), this, SLOT(savePluginList()));
+
+ connect(ui->bsaList, SIGNAL(itemsMoved()), this, SLOT(bsaList_itemMoved()));
+
+ connect(ui->dataTree, SIGNAL(itemExpanded(QTreeWidgetItem*)), this, SLOT(expandDataTreeItem(QTreeWidgetItem*)));
+
+ connect(&m_DirectoryRefresher, SIGNAL(refreshed()), this, SLOT(directory_refreshed()));
+ connect(&m_DirectoryRefresher, SIGNAL(progress(int)), this, SLOT(refresher_progress(int)));
+ connect(&m_DirectoryRefresher, SIGNAL(error(QString)), this, SLOT(showError(QString)));
+
+ connect(&m_SavesWatcher, SIGNAL(directoryChanged(QString)), this, SLOT(refreshSavesIfOpen()));
+
+ connect(&m_Settings, SIGNAL(languageChanged(QString)), this, SLOT(languageChange(QString)));
+ connect(&m_Settings, SIGNAL(styleChanged(QString)), this, SIGNAL(styleChanged(QString)));
+
+ connect(&m_Updater, SIGNAL(restart()), this, SLOT(close()));
+ connect(&m_Updater, SIGNAL(updateAvailable()), this, SLOT(updateAvailable()));
+ connect(&m_Updater, SIGNAL(motdAvailable(QString)), this, SLOT(motdReceived(QString)));
+
+// connect(ExitProxy::instance(), SIGNAL(exit()), this, SLOT(close()));
+
+ connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool)));
+ connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString)));
+ connect(NexusInterface::instance(), SIGNAL(requestNXMDownload(QString)), this, 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()), this, SLOT(nexusLogin()));
+
+ connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString)));
+ 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_IntegratedBrowser, SIGNAL(requestDownload(QUrl,QNetworkReply*)), this, SLOT(requestDownload(QUrl,QNetworkReply*)));
+
+ connect(this, SIGNAL(styleChanged(QString)), this, SLOT(updateStyle(QString)));
+
+ m_CheckBSATimer.setSingleShot(true);
+ connect(&m_CheckBSATimer, SIGNAL(timeout()), this, SLOT(checkBSAList()));
+
+ m_UpdateProblemsTimer.setSingleShot(true);
+ connect(&m_UpdateProblemsTimer, SIGNAL(timeout()), this, SLOT(updateProblemsButton()));
+
+ m_SaveMetaTimer.setSingleShot(false);
+ connect(&m_SaveMetaTimer, SIGNAL(timeout()), this, SLOT(saveModMetas()));
+ m_SaveMetaTimer.start(5000);
+
+ m_DirectoryRefresher.moveToThread(&m_RefresherThread);
+ m_RefresherThread.start();
+
+ m_AskForNexusPW = initSettings.value("ask_for_nexuspw", true).toBool();
+ setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool());
+ FileDialogMemory::restore(initSettings);
+
+ fixCategories();
+
+ if (isOnline() && !m_Settings.offlineMode()) {
+ m_Updater.testForUpdate();
+ } else {
+ qDebug("user doesn't seem to be connected to the internet");
+ }
+
+ m_StartTime = QTime::currentTime();
+
+ m_Tutorial.expose("modList", &m_ModList);
+ m_Tutorial.expose("espList", &m_PluginList);
+
+ // before we start loading plugins we, add the dll path to the dll search order
+ ::SetDllDirectoryW(ToWString(QDir::toNativeSeparators(qApp->applicationDirPath() + "/dlls")).c_str());
+ loadPlugins();
+}
+
+
+MainWindow::~MainWindow()
+{
+ m_RefresherThread.exit();
+ m_RefresherThread.wait();
+ m_IntegratedBrowser.close();
+ delete ui;
+ delete m_GameInfo;
+ delete m_DirectoryStructure;
+}
+
+
+void MainWindow::resizeLists(bool modListCustom, bool pluginListCustom)
+{
+ if (!modListCustom) {
+ // resize mod list to fit content
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+ 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);
+#else
+ for (int i = 0; i < ui->modList->header()->count(); ++i) {
+ ui->modList->header()->setResizeMode(i, QHeaderView::ResizeToContents);
+ }
+ ui->modList->header()->setResizeMode(ModList::COL_NAME, QHeaderView::Stretch);
+#endif
+ }
+
+ if (!pluginListCustom) {
+ // resize plugin list to fit content
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+ for (int i = 0; i < ui->espList->header()->count(); ++i) {
+ ui->espList->header()->setSectionResizeMode(i, QHeaderView::ResizeToContents);
+ }
+ ui->espList->header()->setSectionResizeMode(0, QHeaderView::Stretch);
+#else
+ for (int i = 0; i < ui->espList->header()->count(); ++i) {
+ ui->espList->header()->setResizeMode(i, QHeaderView::ResizeToContents);
+ }
+ ui->espList->header()->setResizeMode(0, QHeaderView::Stretch);
+#endif
+ }
+}
+
+
+void MainWindow::allowListResize()
+{
+ // allow resize on mod list
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+ 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);
+#else
+ for (int i = 0; i < ui->modList->header()->count(); ++i) {
+ ui->modList->header()->setResizeMode(i, QHeaderView::Interactive);
+ }
+ ui->modList->header()->setResizeMode(ui->modList->header()->count() - 1, QHeaderView::Stretch);
+#endif
+
+
+ // allow resize on plugin list
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+ 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);
+#else
+ for (int i = 0; i < ui->espList->header()->count(); ++i) {
+ ui->espList->header()->setResizeMode(i, QHeaderView::Interactive);
+ }
+ ui->espList->header()->setResizeMode(ui->espList->header()->count() - 1, QHeaderView::Stretch);
+#endif
+
+}
+
+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 == NULL) {
+ return QModelIndex();
+ }
+ const QAbstractProxyModel *proxyModel = qobject_cast<const QAbstractProxyModel*>(model);
+ if (proxyModel == NULL) {
+ 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()
+{
+ foreach (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<QToolButton*>(widget);
+
+ if (toolBtn->menu() == NULL) {
+ actionToToolButton(ui->actionTool);
+ }
+
+ actionToToolButton(ui->actionHelp);
+ createHelpWidget();
+
+ foreach (QAction *action, ui->toolBar->actions()) {
+ if (action->isSeparator()) {
+ // insert spacers
+ ui->toolBar->insertWidget(action, spacer);
+
+ std::vector<Executable>::iterator begin, end;
+ m_ExecutablesList.getExecutables(begin, end);
+ for (auto iter = begin; iter != end; ++iter) {
+ if (iter->m_Toolbar) {
+ QAction *exeAction = new QAction(iconForExecutable(iter->m_BinaryInfo.filePath()),
+ iter->m_Title,
+ ui->toolBar);
+ QVariant temp;
+ temp.setValue(*iter);
+ exeAction->setData(temp);
+ 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);
+ }
+ }
+ }
+ }
+}
+
+
+void MainWindow::scheduleUpdateButton()
+{
+ if (!m_UpdateProblemsTimer.isActive()) {
+ m_UpdateProblemsTimer.start(1000);
+ }
+}
+
+
+void MainWindow::updateProblemsButton()
+{
+ int 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"));
+
+ 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<long long>(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"));
+ }
+}
+
+
+bool MainWindow::errorReported(QString &logFile)
+{
+ QDir dir(ToQString(GameInfo::instance().getLogDir()));
+ 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;
+ }
+ }
+ }
+ }
+
+ return false;
+}
+
+
+int MainWindow::checkForProblems()
+{
+ int numProblems = 0;
+ foreach (IPluginDiagnose *diagnose, m_DiagnosisPlugins) {
+ numProblems += diagnose->activeProblems().size();
+ }
+ return numProblems;
+}
+
+void MainWindow::about()
+{
+ AboutDialog dialog(m_Updater.getVersion().displayString(), this);
+ dialog.exec();
+}
+
+
+void MainWindow::createHelpWidget()
+{
+ QToolButton *toolBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionHelp));
+ QMenu *buttonMenu = toolBtn->menu();
+ if (buttonMenu == NULL) {
+ return;
+ }
+ buttonMenu->clear();
+
+ 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);
+
+ QAction *issueAction = new QAction(tr("Report Issue"), buttonMenu);
+ connect(issueAction, SIGNAL(triggered()), this, SLOT(issueTriggered()));
+ buttonMenu->addAction(issueAction);
+
+ QMenu *tutorialMenu = new QMenu(tr("Tutorials"), buttonMenu);
+
+ typedef std::vector<std::pair<int, QAction*> > ActionList;
+
+ ActionList tutorials;
+
+ 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("//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));
+ }
+ }
+
+ std::sort(tutorials.begin(), tutorials.end(),
+ [](const ActionList::value_type &LHS, const ActionList::value_type &RHS) {
+ return LHS.first < RHS.first; } );
+
+ for (auto iter = tutorials.begin(); iter != tutorials.end(); ++iter) {
+ connect(iter->second, SIGNAL(triggered()), this, SLOT(tutorialTriggered()));
+ tutorialMenu->addAction(iter->second);
+ }
+
+ buttonMenu->addMenu(tutorialMenu);
+ buttonMenu->addAction(tr("About"), this, SLOT(about()));
+ buttonMenu->addAction(tr("About Qt"), qApp, SLOT(aboutQt()));
+}
+
+
+bool MainWindow::saveArchiveList()
+{
+ if (m_ArchivesInit) {
+ SafeWriteFile archiveFile(m_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) {
+ // in managed mode, "register" all enabled archives, otherwise register only the files registered in the ini
+ if (ui->manageArchivesBox->isChecked()
+ || item->data(0, Qt::UserRole).toBool()) {
+ archiveFile->write(item->text(0).toUtf8().append("\r\n"));
+ }
+ }
+ }
+ }
+ if (archiveFile.commitIfDifferent(m_ArchiveListHash)) {
+ qDebug("%s saved", qPrintable(QDir::toNativeSeparators(m_CurrentProfile->getArchivesFileName())));
+ return true;
+ }
+ } else {
+ qWarning("archive list not initialised");
+ }
+ return false;
+}
+
+void MainWindow::savePluginList()
+{
+ m_PluginList.saveTo(m_CurrentProfile->getPluginsFileName(),
+ m_CurrentProfile->getLoadOrderFileName(),
+ m_CurrentProfile->getLockedOrderFileName(),
+ m_CurrentProfile->getDeleterFileName(),
+ m_Settings.hideUncheckedPlugins());
+ m_PluginList.saveLoadOrder(*m_DirectoryStructure);
+}
+
+void MainWindow::modFilterActive(bool filterActive)
+{
+ if (filterActive) {
+ 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::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;
+ }
+ }
+}
+
+bool MainWindow::saveCurrentLists()
+{
+ if (m_DirectoryUpdate) {
+ qWarning("not saving lists during directory update");
+ return false;
+ }
+
+ try {
+ savePluginList();
+ saveArchiveList();
+ } catch (const std::exception &e) {
+ reportError(tr("failed to save load order: %1").arg(e.what()));
+ }
+
+ return true;
+}
+
+
+bool MainWindow::addProfile()
+{
+ QComboBox *profileBox = findChild<QComboBox*>("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_Settings.directInterface().value("CompletedWindowTutorials/" + windowName, false).toBool()) {
+ TutorialManager::instance().activateTutorial(windowName, fileName);
+ }
+ }
+ }
+}
+
+
+void MainWindow::showEvent(QShowEvent *event)
+{
+ refreshFilters();
+
+ QMainWindow::showEvent(event);
+ m_Tutorial.registerControl();
+
+ hookUpWindowTutorials();
+
+ if (m_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_Settings.directInterface().setValue("first_start", false);
+ }
+
+ // this has no visible impact when called before the ui is visible
+ int grouping = m_Settings.directInterface().value("group_state").toInt();
+ ui->groupCombo->setCurrentIndex(grouping);
+
+ allowListResize();
+
+ m_Settings.registerAsNXMHandler(false);
+}
+
+
+void MainWindow::closeEvent(QCloseEvent* event)
+{
+ if (m_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_DownloadManager.pauseAll();
+ }
+ }
+
+ setCursor(Qt::WaitCursor);
+
+ m_IntegratedBrowser.close();
+
+ storeSettings();
+
+ // profile has to be cleaned up before the modinfo-buffer is cleared
+ delete m_CurrentProfile;
+ m_CurrentProfile = NULL;
+
+ ModInfo::clear();
+ LogBuffer::cleanQuit();
+ m_ModList.setProfile(NULL);
+}
+
+
+void MainWindow::createFirstProfile()
+{
+ if (!refreshProfiles(false)) {
+ qDebug("creating default profile");
+ Profile newProf("Default", false);
+ refreshProfiles(false);
+ }
+}
+
+
+void MainWindow::setBrowserGeometry(const QByteArray &geometry)
+{
+ m_IntegratedBrowser.restoreGeometry(geometry);
+}
+
+
+SaveGameGamebryo *MainWindow::getSaveGame(const QString &name)
+{
+ return new SaveGameGamebryo(this, name);
+}
+
+
+SaveGameGamebryo *MainWindow::getSaveGame(QListWidgetItem *item)
+{
+ try {
+ SaveGameGamebryo *saveGame = getSaveGame(item->data(Qt::UserRole).toString());
+ saveGame->setParent(item->listWidget());
+ return saveGame;
+ } catch (const std::exception &e) {
+ reportError(tr("failed to read savegame: %1").arg(e.what()));
+ return NULL;
+ }
+}
+
+
+void MainWindow::displaySaveGameInfo(const SaveGameGamebryo *save, QPoint pos)
+{
+ if (m_CurrentSaveView == NULL) {
+ m_CurrentSaveView = new SaveGameInfoWidgetGamebryo(save, &m_PluginList, this);
+ } else {
+ m_CurrentSaveView->setSave(save);
+ }
+
+ QRect screenRect = QApplication::desktop()->availableGeometry(m_CurrentSaveView);
+
+ 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);
+
+ m_CurrentSaveView->show();
+ ui->savegameList->activateWindow();
+ connect(m_CurrentSaveView, SIGNAL(closeSaveInfo()), this, SLOT(hideSaveGameInfo()));
+}
+
+
+void MainWindow::saveSelectionChanged(QListWidgetItem *newItem)
+{
+ if (newItem == NULL) {
+ hideSaveGameInfo();
+ } else if ((m_CurrentSaveView == NULL) || (newItem != m_CurrentSaveView->property("displayItem").value<void*>())) {
+ const SaveGameGamebryo *save = getSaveGame(newItem);
+ if (save != NULL) {
+ displaySaveGameInfo(save, QCursor::pos());
+ m_CurrentSaveView->setProperty("displayItem", qVariantFromValue((void*)newItem));
+ }
+ }
+}
+
+
+
+void MainWindow::hideSaveGameInfo()
+{
+ if (m_CurrentSaveView != NULL) {
+ disconnect(m_CurrentSaveView, SIGNAL(closeSaveInfo()), this, SLOT(hideSaveGameInfo()));
+ m_CurrentSaveView->deleteLater();
+ m_CurrentSaveView = NULL;
+ }
+}
+
+bool MainWindow::eventFilter(QObject *object, QEvent *event)
+{
+ if ((object == ui->savegameList) &&
+ ((event->type() == QEvent::Leave) || (event->type() == QEvent::WindowDeactivate))) {
+ hideSaveGameInfo();
+ }
+ return false;
+}
+
+
+bool MainWindow::testForSteam()
+{
+ DWORD processIDs[1024];
+ DWORD bytesReturned;
+ if (!::EnumProcesses(processIDs, sizeof(processIDs), &bytesReturned)) {
+ qWarning("failed to determine if steam is running");
+ return 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 != NULL) {
+ 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;
+}
+
+
+bool MainWindow::verifyPlugin(IPlugin *plugin)
+{
+ if (plugin == NULL) {
+ return false;
+ } else if (!plugin->init(new OrganizerProxy(this, plugin->name()))) {
+ qWarning("plugin failed to initialize");
+ return false;
+ }
+ return true;
+}
+
+
+void MainWindow::toolPluginInvoke()
+{
+ QAction *triggeredAction = qobject_cast<QAction*>(sender());
+ IPluginTool *plugin = (IPluginTool*)triggeredAction->data().value<void*>();
+ 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::requestDownload(const QUrl &url, QNetworkReply *reply)
+{
+ QToolButton *browserBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionNexus));
+ if (browserBtn->menu() != NULL) {
+ // go through modpage plugins, find one to handle the download.
+ QList<QAction*> browserActions = browserBtn->menu()->actions();
+ foreach (QAction *action, browserActions) {
+ // the nexus action doesn't have a plugin connected currently
+ if (action->data().isValid()) {
+ IPluginModPage *plugin = qobject_cast<IPluginModPage*>(qvariant_cast<QObject*>(action->data()));
+ if (plugin == NULL) {
+ qCritical("invalid mod page. This is a bug");
+ continue;
+ }
+ ModRepositoryFileInfo *fileInfo = new ModRepositoryFileInfo();
+ if (plugin->handlesDownload(url, reply->url(), *fileInfo)) {
+ fileInfo->repository = plugin->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(this, 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());
+ }
+ }
+}
+
+
+void MainWindow::modPagePluginInvoke()
+{
+ QAction *triggeredAction = qobject_cast<QAction*>(sender());
+ IPluginModPage *plugin = qobject_cast<IPluginModPage*>(triggeredAction->data().value<QObject*>());
+ if (plugin->useIntegratedBrowser()) {
+ m_IntegratedBrowser.setWindowTitle(plugin->displayName());
+ m_IntegratedBrowser.openUrl(plugin->pageURL());
+ } else {
+ ::ShellExecuteW(NULL, L"open", ToWString(plugin->pageURL().toString()).c_str(), NULL, NULL, 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((void*)tool));
+ connect(action, SIGNAL(triggered()), this, SLOT(toolPluginInvoke()), Qt::QueuedConnection);
+ QToolButton *toolBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionTool));
+ toolBtn->menu()->addAction(action);
+}
+
+
+void MainWindow::registerModPage(IPluginModPage *modPage)
+{
+ QToolButton *browserBtn = NULL;
+ // turn the browser action into a drop-down menu if necessary
+ if (ui->actionNexus->menu() == NULL) {
+ 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);
+
+ browserBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionNexus));
+ browserBtn->menu()->addAction(nexusAction);
+ } else {
+ browserBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionNexus));
+ }
+
+ QAction *action = new QAction(modPage->icon(), modPage->displayName(), ui->toolBar);
+ modPage->setParentWidget(this);
+ action->setData(qVariantFromValue(reinterpret_cast<QObject*>(modPage)));
+
+ connect(action, SIGNAL(triggered()), this, SLOT(modPagePluginInvoke()), Qt::QueuedConnection);
+ QToolButton *toolBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionNexus));
+ toolBtn->menu()->addAction(action);
+}
+
+
+bool MainWindow::registerPlugin(QObject *plugin, const QString &fileName)
+{
+ { // generic treatment for all plugins
+ IPlugin *pluginObj = qobject_cast<IPlugin*>(plugin);
+ if (pluginObj == NULL) {
+ qDebug("not an IPlugin");
+ return false;
+ }
+ plugin->setProperty("filename", fileName);
+ m_Settings.registerPlugin(pluginObj);
+ installTranslator(QFileInfo(fileName).baseName());
+ }
+
+ { // diagnosis plugins
+ IPluginDiagnose *diagnose = qobject_cast<IPluginDiagnose*>(plugin);
+ if (diagnose != NULL) {
+ m_DiagnosisPlugins.push_back(diagnose);
+ diagnose->onInvalidated([&] () { this->scheduleUpdateButton(); });
+ }
+ }
+ { // mod page plugin
+ IPluginModPage *modPage = qobject_cast<IPluginModPage*>(plugin);
+ if (verifyPlugin(modPage)) {
+ registerModPage(modPage);
+ return true;
+ }
+ }
+ { // tool plugins
+ IPluginTool *tool = qobject_cast<IPluginTool*>(plugin);
+ if (verifyPlugin(tool)) {
+ registerPluginTool(tool);
+ return true;
+ }
+ }
+ { // installer plugins
+ IPluginInstaller *installer = qobject_cast<IPluginInstaller*>(plugin);
+ if (verifyPlugin(installer)) {
+ installer->setParentWidget(this);
+ m_InstallationManager.registerInstaller(installer);
+ return true;
+ }
+ }
+ { // preview plugins
+ IPluginPreview *preview = qobject_cast<IPluginPreview*>(plugin);
+ if (verifyPlugin(preview)) {
+ m_PreviewGenerator.registerPlugin(preview);
+ }
+ }
+ { // proxy plugins
+ IPluginProxy *proxy = qobject_cast<IPluginProxy*>(plugin);
+ if (verifyPlugin(proxy)) {
+ proxy->setParentWidget(this);
+ QStringList pluginNames = proxy->pluginList(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::pluginPath()));
+ foreach (const QString &pluginName, pluginNames) {
+ try {
+ QObject *proxiedPlugin = proxy->instantiate(pluginName);
+ if (proxiedPlugin != NULL) {
+ if (registerPlugin(proxiedPlugin, pluginName)) {
+ qDebug("loaded plugin \"%s\"", qPrintable(pluginName));
+ } else {
+ qWarning("plugin \"%s\" failed to load", qPrintable(pluginName));
+ }
+ }
+ } catch (const std::exception &e) {
+ reportError(tr("failed to init plugin %1: %2").arg(pluginName).arg(e.what()));
+ }
+ }
+ return true;
+ }
+ }
+
+ { // dummy plugins
+ // only initialize these, no processing otherwise
+ IPlugin *dummy = qobject_cast<IPlugin*>(plugin);
+ if (verifyPlugin(dummy)) {
+ return true;
+ }
+ }
+
+ qDebug("no matching plugin interface");
+
+ return false;
+}
+
+
+void MainWindow::loadPlugins()
+{
+ m_DiagnosisPlugins.clear();
+
+ m_Settings.clearPlugins();
+
+ foreach (QObject *plugin, QPluginLoader::staticInstances()) {
+ registerPlugin(plugin, "");
+ }
+
+ QFile loadCheck(QCoreApplication::applicationDirPath() + "/plugin_loadcheck.tmp");
+ if (loadCheck.exists() && loadCheck.open(QIODevice::ReadOnly)) {
+ // oh, there was a failed plugin load last time. Find out which plugin was loaded last
+ QString fileName;
+ while (!loadCheck.atEnd()) {
+ fileName = QString::fromUtf8(loadCheck.readLine().constData()).trimmed();
+ }
+ if (QMessageBox::question(this, tr("Plugin error"),
+ tr("It appears the plugin \"%1\" failed to load last startup and caused MO to crash. Do you want to disable it?\n"
+ "(Please note: If this is the first time you see this message for this plugin you may want to give it another try. "
+ "The plugin may be able to recover from the problem)").arg(fileName),
+ QMessageBox::Yes | QMessageBox::No, QMessageBox::Yes) == QMessageBox::Yes) {
+ m_Settings.addBlacklistPlugin(fileName);
+ }
+ loadCheck.close();
+ }
+
+ loadCheck.open(QIODevice::WriteOnly);
+
+ QString pluginPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())) + "/" + ToQString(AppConfig::pluginPath());
+ qDebug("looking for plugins in %s", QDir::toNativeSeparators(pluginPath).toUtf8().constData());
+ QDirIterator iter(pluginPath, QDir::Files | QDir::NoDotAndDotDot);
+
+ while (iter.hasNext()) {
+ iter.next();
+ if (m_Settings.pluginBlacklisted(iter.fileName())) {
+ qDebug("plugin \"%s\" blacklisted", qPrintable(iter.fileName()));
+ continue;
+ }
+ loadCheck.write(iter.fileName().toUtf8());
+ loadCheck.write("\n");
+ loadCheck.flush();
+ QString pluginName = iter.filePath();
+ if (QLibrary::isLibrary(pluginName)) {
+ QPluginLoader pluginLoader(pluginName);
+ if (pluginLoader.instance() == NULL) {
+ m_UnloadedPlugins.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(pluginName));
+ } else {
+ m_UnloadedPlugins.push_back(pluginName);
+ qWarning("plugin \"%s\" failed to load", qPrintable(pluginName));
+ }
+ }
+ }
+ }
+
+ // remove the load check file on success
+ loadCheck.remove();
+
+ m_DownloadManager.setSupportedExtensions(m_InstallationManager.getSupportedExtensions());
+
+ m_DiagnosisPlugins.push_back(this);
+}
+
+
+void MainWindow::startSteam()
+{
+ QSettings steamSettings("HKEY_CURRENT_USER\\Software\\Valve\\Steam",
+ QSettings::NativeFormat);
+ QString exe = steamSettings.value("SteamExe", "").toString();
+ if (!exe.isEmpty()) {
+ QString temp = QString("\"%1\"").arg(exe);
+ if (!QProcess::startDetached(temp)) {
+ reportError(tr("Failed to start \"%1\"").arg(temp));
+ } else {
+ QMessageBox::information(this, tr("Waiting"), tr("Please press OK once you're logged into steam."));
+ }
+ }
+}
+
+
+HANDLE MainWindow::spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName,
+ const QDir ¤tDirectory, const QString &steamAppID)
+{
+ storeSettings();
+
+ if (!binary.exists()) {
+ reportError(tr("Executable \"%1\" not found").arg(binary.fileName()));
+ return INVALID_HANDLE_VALUE;
+ }
+
+ if (!steamAppID.isEmpty()) {
+ ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(steamAppID).c_str());
+ } else {
+ ::SetEnvironmentVariableW(L"SteamAPPId", ToWString(m_Settings.getSteamAppID()).c_str());
+ }
+
+ if ((GameInfo::instance().requiresSteam()) &&
+ (m_Settings.getLoadMechanism() == LoadMechanism::LOAD_MODORGANIZER)) {
+ if (!testForSteam()) {
+ if (QuestionBoxMemory::query(this->isVisible() ? this : NULL,
+ "steamQuery", 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();
+ }
+ }
+ }
+
+ 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())) {
+ return startBinary(binary, arguments, profileName, m_Settings.logLevel(), currentDirectory, true);
+ } else {
+ qDebug("start of \"%s\" canceled by plugin", qPrintable(binary.absoluteFilePath()));
+ return INVALID_HANDLE_VALUE;
+ }
+}
+
+
+void MainWindow::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir ¤tDirectory, bool closeAfterStart, const QString &steamAppID)
+{
+ LockedDialog *dialog = new LockedDialog(this);
+ dialog->show();
+ ON_BLOCK_EXIT([&] () { dialog->hide(); dialog->deleteLater(); });
+
+ HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->getName(), currentDirectory, steamAppID);
+ if (processHandle != INVALID_HANDLE_VALUE) {
+ if (closeAfterStart) {
+ close();
+ } else {
+ this->setEnabled(false);
+ // re-enable the locked dialog because what'd be the point otherwise?
+ dialog->setEnabled(true);
+
+ QCoreApplication::processEvents();
+
+ DWORD retLen;
+ JOBOBJECT_BASIC_PROCESS_ID_LIST info;
+
+ bool isJobHandle = true;
+
+ DWORD res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE);
+ while ((res != WAIT_FAILED) && (res != WAIT_OBJECT_0) && !dialog->unlockClicked()) {
+ if (isJobHandle) {
+ if (::QueryInformationJobObject(processHandle, JobObjectBasicProcessIdList, &info, sizeof(info), &retLen) > 0) {
+ if (info.NumberOfProcessIdsInList == 0) {
+ break;
+ }
+ } 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;
+ }
+ }
+ }
+
+ // keep processing events so the app doesn't appear dead
+ QCoreApplication::processEvents();
+
+ res = ::MsgWaitForMultipleObjects(1, &processHandle, false, 1000, QS_KEY | QS_MOUSE);
+ }
+ ::CloseHandle(processHandle);
+
+ this->setEnabled(true);
+ refreshDirectoryStructure();
+ if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) {
+ QFile::remove(m_CurrentProfile->getLoadOrderFileName());
+ }
+ }
+ }
+}
+
+
+void MainWindow::startExeAction()
+{
+ QAction *action = qobject_cast<QAction*>(sender());
+ if (action != NULL) {
+ Executable selectedExecutable = action->data().value<Executable>();
+ spawnBinary(selectedExecutable.m_BinaryInfo,
+ selectedExecutable.m_Arguments,
+ selectedExecutable.m_WorkingDirectory.length() != 0 ? selectedExecutable.m_WorkingDirectory
+ : selectedExecutable.m_BinaryInfo.absolutePath(),
+ selectedExecutable.m_CloseMO == DEFAULT_CLOSE,
+ selectedExecutable.m_SteamAppID);
+ } else {
+ qCritical("not an action?");
+ }
+}
+
+
+void MainWindow::setExecutablesList(const ExecutablesList &executablesList)
+{
+ m_ExecutablesList = executablesList;
+ refreshExecutablesList();
+ updateToolBar();
+}
+
+void MainWindow::setExecutableIndex(int index)
+{
+ QComboBox *executableBox = findChild<QComboBox*>("executablesListBox");
+
+ if ((index != 0) && (executableBox->count() > index)) {
+ executableBox->setCurrentIndex(index);
+ } else {
+ executableBox->setCurrentIndex(1);
+ }
+}
+
+void MainWindow::activateSelectedProfile()
+{
+ QString profileName = ui->profileBox->currentText();
+ qDebug("activate profile \"%s\"", qPrintable(profileName));
+ QString profileDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))
+ .append("/").append(profileName);
+ delete m_CurrentProfile;
+ m_CurrentProfile = new Profile(QDir(profileDir));
+ m_ModList.setProfile(m_CurrentProfile);
+
+ m_ModListSortProxy->setProfile(m_CurrentProfile);
+
+ connect(m_CurrentProfile, SIGNAL(modStatusChanged(uint)), this, SLOT(modStatusChanged(uint)));
+
+ refreshSaveList();
+ refreshModList();
+}
+
+void MainWindow::on_profileBox_currentIndexChanged(int index)
+{
+ if (ui->profileBox->isEnabled()) {
+ int previousIndex = m_OldProfileIndex;
+ m_OldProfileIndex = index;
+
+ if ((previousIndex != -1) &&
+ (m_CurrentProfile != NULL) &&
+ m_CurrentProfile->exists()) {
+ saveCurrentLists();
+ }
+
+ // 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) {
+ ProfilesDialog(m_GamePath).exec();
+ while (!refreshProfiles()) {
+ ProfilesDialog(m_GamePath).exec();
+ }
+ ui->profileBox->setCurrentIndex(previousIndex);
+ } else {
+ activateSelectedProfile();
+ }
+ }
+}
+
+
+void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const DirectoryEntry &directoryEntry, bool conflictsOnly)
+{
+ {
+ std::vector<FileEntry::Ptr> files = directoryEntry.getFiles();
+ for (auto iter = files.begin(); iter != files.end(); ++iter) {
+ FileEntry::Ptr current = *iter;
+ if (conflictsOnly && (current->getAlternatives().size() == 0)) {
+ continue;
+ }
+
+ QString fileName = ToQString(current->getName());
+ QStringList columns(fileName);
+ bool isArchive = false;
+ int originID = current->getOrigin(isArchive);
+ QString source = ToQString(m_DirectoryStructure->getOriginByID(originID).getName());
+ 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<int> alternatives = current->getAlternatives();
+
+ if (!alternatives.empty()) {
+ std::wostringstream altString;
+ altString << ToWString(tr("Also in: <br>"));
+ for (std::vector<int>::iterator altIter = alternatives.begin();
+ altIter != alternatives.end(); ++altIter) {
+ if (altIter != alternatives.begin()) {
+ altString << " , ";
+ }
+ altString << "<span style=\"white-space: nowrap;\"><i>" << m_DirectoryStructure->getOriginByID(*altIter).getName() << "</font></span>";
+ }
+ 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<DirectoryEntry*>::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);
+}
+
+void MainWindow::delayedRemove()
+{
+ foreach (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();
+
+ std::wstring virtualPath = (path + L"\\").substr(6) + ToWString(item->text(0));
+ DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(virtualPath);
+ if (dir != NULL) {
+ 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<QComboBox*>("profileBox");
+
+ QString currentProfileName = profileBox->currentText();
+
+ profileBox->blockSignals(true);
+ profileBox->clear();
+ profileBox->addItem(QObject::tr("<Manage...>"));
+
+ QDir profilesDir(ToQString(GameInfo::instance().getProfilesDir()));
+ profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
+
+ QDirIterator profileIter(profilesDir);
+
+ int newIndex = profileIter.hasNext() ? 1 : 0;
+ int currentIndex = 0;
+ while (profileIter.hasNext()) {
+ profileIter.next();
+ ++currentIndex;
+ try {
+ profileBox->addItem(profileIter.fileName());
+ if (currentProfileName == profileIter.fileName()) {
+ newIndex = currentIndex;
+ }
+ } 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) {
+ if (currentProfileName.length() != 0) {
+ if ((newIndex != 0) && (profileBox->count() > newIndex)) {
+ profileBox->setCurrentIndex(newIndex);
+ } else {
+ profileBox->setCurrentIndex(1);
+ }
+ }
+ return true;
+ } else {
+ return false;
+ }
+ } else {
+ return profileBox->count() > 1;
+ }
+}
+
+std::set<QString> MainWindow::enabledArchives()
+{
+ std::set<QString> result;
+ QFile archiveFile(m_CurrentProfile->getArchivesFileName());
+ if (archiveFile.open(QIODevice::ReadOnly)) {
+ while (!archiveFile.atEnd()) {
+ result.insert(QString::fromUtf8(archiveFile.readLine()).trimmed());
+ }
+ archiveFile.close();
+ }
+ return result;
+}
+
+void MainWindow::refreshDirectoryStructure()
+{
+ m_DirectoryUpdate = true;
+ std::vector<std::tuple<QString, QString, int> > activeModList = m_CurrentProfile->getActiveMods();
+
+ m_DirectoryRefresher.setMods(activeModList, enabledArchives());
+
+ statusBar()->show();
+ m_RefreshProgress->setRange(0, 100);
+
+ QTimer::singleShot(0, &m_DirectoryRefresher, SLOT(refresh()));
+}
+
+#if QT_VERSION >= 0x050000
+extern QPixmap qt_pixmapFromWinHICON(HICON icon);
+#else
+#define qt_pixmapFromWinHICON(icon) QPixmap::fromWinHICON(icon)
+#endif
+
+QIcon MainWindow::iconForExecutable(const QString &filePath)
+{
+ HICON winIcon;
+ UINT res = ::ExtractIconExW(ToWString(filePath).c_str(), 0, &winIcon, NULL, 1);
+ if (res == 1) {
+ QIcon result = QIcon(qt_pixmapFromWinHICON(winIcon));
+ ::DestroyIcon(winIcon);
+ return result;
+ } else {
+ return QIcon(":/MO/gui/executable");
+ }
+}
+
+void MainWindow::refreshExecutablesList()
+{
+ QComboBox* executablesList = findChild<QComboBox*>("executablesListBox");
+ executablesList->setEnabled(false);
+ executablesList->clear();
+ executablesList->addItem(tr("<Edit...>"));
+
+ QAbstractItemModel *model = executablesList->model();
+
+ std::vector<Executable>::const_iterator current, end;
+ m_ExecutablesList.getExecutables(current, end);
+ for(int i = 0; current != end; ++current, ++i) {
+ QVariant temp;
+ temp.setValue(*current);
+ QIcon icon = iconForExecutable(current->m_BinaryInfo.filePath());
+ executablesList->addItem(icon, current->m_Title, temp);
+ 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<QCheckBox*>("conflictsCheckBox");
+ QTreeWidget *tree = findChild<QTreeWidget*>("dataTree");
+ tree->clear();
+ QStringList columns("data");
+ columns.append("");
+ QTreeWidgetItem *subTree = new QTreeWidgetItem(columns);
+ updateTo(subTree, L"", *m_DirectoryStructure, conflictsBox->isChecked());
+ tree->insertTopLevelItem(0, subTree);
+ subTree->setExpanded(true);
+ tree->header()->resizeSection(0, 200);
+}
+
+
+void MainWindow::refreshSavesIfOpen()
+{
+ if (ui->tabWidget->currentIndex() == 3) {
+ refreshSaveList();
+ }
+}
+
+
+void MainWindow::refreshSaveList()
+{
+ ui->savegameList->clear();
+
+ QDir savesDir;
+ if (m_CurrentProfile->localSavesEnabled()) {
+ savesDir.setPath(m_CurrentProfile->getPath() + "/saves");
+ } else {
+ wchar_t path[MAX_PATH];
+ ::GetPrivateProfileStringW(L"General", L"SLocalSavePath", L"Saves",
+ path, MAX_PATH,
+ (ToWString(m_CurrentProfile->getPath()) + L"\\" + GameInfo::instance().getIniFileNames().at(0)).c_str());
+ savesDir.setPath(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getDocumentsDir() + L"\\" + path)));
+ }
+
+ if (m_SavesWatcher.directories().length() > 0) {
+ m_SavesWatcher.removePaths(m_SavesWatcher.directories());
+ }
+ m_SavesWatcher.addPath(savesDir.absolutePath());
+
+ QStringList filters;
+ filters << ToQString(GameInfo::instance().getSaveGameExtension());
+ savesDir.setNameFilters(filters);
+
+ QFileInfoList files = savesDir.entryInfoList(QDir::Files, QDir::Time);
+
+ foreach (const QFileInfo &file, files) {
+ QListWidgetItem *item = new QListWidgetItem(file.fileName());
+ item->setData(Qt::UserRole, file.absoluteFilePath());
+ ui->savegameList->addItem(item);
+ }
+}
+
+
+void MainWindow::refreshLists()
+{
+ if ((m_CurrentProfile != NULL) && m_DirectoryStructure->isPopulated()) {
+ refreshESPList();
+ refreshBSAList();
+ } // no point in refreshing lists if no files have been added to the directory tree
+}
+
+
+void MainWindow::refreshESPList()
+{
+ m_CurrentProfile->writeModlist();
+
+ // clear list
+ try {
+ m_PluginList.refresh(m_CurrentProfile->getName(),
+ *m_DirectoryStructure,
+ m_CurrentProfile->getPluginsFileName(),
+ m_CurrentProfile->getLoadOrderFileName(),
+ m_CurrentProfile->getLockedOrderFileName());
+ } catch (const std::exception &e) {
+ reportError(tr("Failed to refresh list of esps: %1").arg(e.what()));
+ }
+}
+
+void MainWindow::refreshModList(bool saveChanges)
+{
+ // don't lose changes!
+ if (saveChanges) {
+ m_CurrentProfile->writeModlistNow(true);
+ }
+ ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure, m_Settings.displayForeign());
+
+ m_CurrentProfile->refreshModStatus();
+
+ m_ModList.notifyChange(-1);
+
+ refreshDirectoryStructure();
+}
+
+
+static bool BySortValue(const std::pair<UINT32, QTreeWidgetItem*> &LHS, const std::pair<UINT32, QTreeWidgetItem*> &RHS)
+{
+ return LHS.first < RHS.first;
+}
+
+
+template <typename InputIterator>
+QStringList toStringList(InputIterator current, InputIterator end)
+{
+ QStringList result;
+ for (; current != end; ++current) {
+ result.append(*current);
+ }
+ return result;
+}
+
+
+void MainWindow::refreshBSAList()
+{
+ m_ArchivesInit = false;
+ ui->bsaList->clear();
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+ ui->bsaList->header()->setSectionResizeMode(QHeaderView::ResizeToContents);
+#else
+ ui->bsaList->header()->setResizeMode(QHeaderView::ResizeToContents);
+#endif
+
+ m_DefaultArchives.clear();
+
+ wchar_t buffer[256];
+ std::wstring iniFileName = ToWString(QDir::toNativeSeparators(m_CurrentProfile->getIniFileName()));
+ if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(),
+ L"", buffer, 256, iniFileName.c_str()) != 0) {
+ m_DefaultArchives = ToQString(buffer).split(',');
+ } else {
+ std::vector<std::wstring> vanillaBSAs = GameInfo::instance().getVanillaBSAs();
+ for (auto iter = vanillaBSAs.begin(); iter != vanillaBSAs.end(); ++iter) {
+ m_DefaultArchives.append(ToQString(*iter));
+ }
+ }
+
+ if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().append(L"2").c_str(),
+ L"", buffer, 256, iniFileName.c_str()) != 0) {
+ m_DefaultArchives.append(ToQString(buffer).split(','));
+ }
+
+ for (int i = 0; i < m_DefaultArchives.count(); ++i) {
+ m_DefaultArchives[i] = m_DefaultArchives[i].trimmed();
+ }
+
+ m_ActiveArchives.clear();
+
+ auto iter = enabledArchives();
+ m_ActiveArchives = toStringList(iter.begin(), iter.end());
+ if (m_ActiveArchives.isEmpty()) {
+ m_ActiveArchives = m_DefaultArchives;
+ }
+
+ std::vector<std::pair<UINT32, QTreeWidgetItem*> > items;
+
+ std::vector<FileEntry::Ptr> files = m_DirectoryStructure->getFiles();
+ for (auto iter = files.begin(); iter != files.end(); ++iter) {
+ FileEntry::Ptr current = *iter;
+
+ QString filename = ToQString(current->getName().c_str());
+ QString extension = filename.right(3).toLower();
+
+ if (extension == "bsa") {
+ int index = m_ActiveArchives.indexOf(filename);
+ if (index == -1) {
+ index = 0xFFFF;
+ }
+ QString basename = filename.left(filename.indexOf("."));
+ QStringList strings(filename);
+ bool isArchive = false;
+ int origin = current->getOrigin(isArchive);
+ strings.append(ToQString(m_DirectoryStructure->getOriginByID(origin).getName()));
+ QTreeWidgetItem *newItem = new QTreeWidgetItem(strings);
+ newItem->setData(0, Qt::UserRole, index);
+ newItem->setData(1, Qt::UserRole, origin);
+ newItem->setFlags(newItem->flags() & ~Qt::ItemIsDropEnabled | Qt::ItemIsUserCheckable);
+ newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked);
+ newItem->setData(0, Qt::UserRole, false);
+ if (m_Settings.forceEnableCoreFiles()
+ && m_DefaultArchives.contains(filename)) {
+ newItem->setCheckState(0, Qt::Checked);
+ newItem->setDisabled(true);
+ newItem->setData(0, Qt::UserRole, true);
+ } else if ((m_PluginList.state(basename + ".esp") == IPluginList::STATE_ACTIVE)
+ || (m_PluginList.state(basename + ".esm") == IPluginList::STATE_ACTIVE)) {
+ newItem->setCheckState(0, Qt::Checked);
+ newItem->setDisabled(true);
+ } else {
+ if (ui->manageArchivesBox->isChecked()) {
+ newItem->setCheckState(0, (index != 0xFFFF) ? Qt::Checked : Qt::Unchecked);
+ } else {
+ newItem->setCheckState(0, Qt::Unchecked);
+ newItem->setDisabled(true);
+ }
+ }
+
+ if (index < 0) index = 0;
+
+ UINT32 sortValue = ((m_DirectoryStructure->getOriginByID(origin).getPriority() & 0xFFFF) << 16) | (index & 0xFFFF);
+ items.push_back(std::make_pair(sortValue, newItem));
+ }
+ }
+
+ std::sort(items.begin(), items.end(), BySortValue);
+
+ for (std::vector<std::pair<UINT32, QTreeWidgetItem*> >::iterator iter = items.begin(); iter != items.end(); ++iter) {
+ int originID = iter->second->data(1, Qt::UserRole).toInt();
+ FilesOrigin origin = m_DirectoryStructure->getOriginByID(originID);
+ QList<QTreeWidgetItem*> items = ui->bsaList->findItems(ToQString(origin.getName()), Qt::MatchFixedString);
+ QTreeWidgetItem *subItem = NULL;
+ if (items.length() > 0) {
+ subItem = items.at(0);
+ } else {
+ subItem = new QTreeWidgetItem(QStringList(ToQString(origin.getName())));
+ subItem->setFlags(subItem->flags() & ~Qt::ItemIsDragEnabled);
+ ui->bsaList->addTopLevelItem(subItem);
+ }
+ subItem->addChild(iter->second);
+ subItem->setExpanded(true);
+ }
+
+ checkBSAList();
+ m_ArchivesInit = true;
+}
+
+
+void MainWindow::checkBSAList()
+{
+ ui->bsaList->blockSignals(true);
+
+ 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 (m_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());
+ }
+
+ ui->bsaList->blockSignals(false);
+}
+
+
+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<int> categories = modInfo->getCategories();
+ for (std::set<int>::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<QNetworkProxy> 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<void> future = QtConcurrent::run(MainWindow::setupNetworkProxy, activate);
+ while (!future.isFinished()) {
+ QCoreApplication::processEvents();
+ ::Sleep(100);
+ }
+ busyDialog.hide();
+}
+
+void MainWindow::readSettings()
+{
+ QSettings settings(ToQString(GameInfo::instance().getIniFilename()), QSettings::IniFormat);
+
+ 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("log_split")) {
+ ui->topLevelSplitter->restoreState(settings.value("log_split").toByteArray());
+ }
+
+ bool filtersVisible = settings.value("filters_visible", false).toBool();
+ setCategoryListVisible(filtersVisible);
+ ui->displayCategoriesBtn->setChecked(filtersVisible);
+
+ int selectedExecutable = settings.value("selected_executable").toInt();
+ setExecutableIndex(selectedExecutable);
+
+ if (settings.value("Settings/use_proxy", false).toBool()) {
+ activateProxy(true);
+ }
+
+ ui->manageArchivesBox->blockSignals(true);
+ ui->manageArchivesBox->setChecked(settings.value("manage_bsas", true).toBool());
+ ui->manageArchivesBox->blockSignals(false);
+}
+
+
+void MainWindow::storeSettings()
+{
+ if (m_CurrentProfile == NULL) {
+ return;
+ }
+ m_CurrentProfile->writeModlist();
+ m_CurrentProfile->createTweakedIniFile();
+ saveCurrentLists();
+ m_Settings.setupLoadMechanism();
+
+ QString iniFile = ToQString(GameInfo::instance().getIniFilename());
+ shellCopy(iniFile, iniFile + ".new", true, this);
+
+ QSettings::Status result = QSettings::NoError;
+ {
+ QSettings settings(iniFile + ".new", QSettings::IniFormat);
+ if (m_CurrentProfile != NULL) {
+ settings.setValue("selected_profile", m_CurrentProfile->getName().toUtf8().constData());
+ } else {
+ settings.remove("selected_profile");
+ }
+
+ settings.setValue("mod_list_state", ui->modList->header()->saveState());
+ settings.setValue("plugin_list_state", ui->espList->header()->saveState());
+
+ settings.setValue("group_state", ui->groupCombo->currentIndex());
+
+ settings.setValue("ask_for_nexuspw", m_AskForNexusPW);
+
+ 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("filters_visible", ui->displayCategoriesBtn->isChecked());
+ settings.setValue("manage_bsas", ui->manageArchivesBox->isChecked());
+
+ settings.remove("customExecutables");
+ settings.beginWriteArray("customExecutables");
+ std::vector<Executable>::const_iterator current, end;
+ m_ExecutablesList.getExecutables(current, end);
+ int count = 0;
+ for (; current != end; ++current) {
+ const Executable &item = *current;
+ if (item.m_Custom || item.m_Toolbar) {
+ settings.setArrayIndex(count++);
+ settings.setValue("binary", item.m_BinaryInfo.absoluteFilePath());
+ settings.setValue("title", item.m_Title);
+ settings.setValue("arguments", item.m_Arguments);
+ settings.setValue("workingDirectory", item.m_WorkingDirectory);
+ settings.setValue("closeOnStart", item.m_CloseMO == DEFAULT_CLOSE);
+ settings.setValue("steamAppID", item.m_SteamAppID);
+ settings.setValue("custom", item.m_Custom);
+ settings.setValue("toolbar", item.m_Toolbar);
+ }
+ }
+ settings.endArray();
+
+ QComboBox *executableBox = findChild<QComboBox*>("executablesListBox");
+ settings.setValue("selected_executable", executableBox->currentIndex());
+
+ FileDialogMemory::save(settings);
+
+ settings.sync();
+ result = settings.status();
+ }
+ if (result == QSettings::NoError) {
+ if (!shellRename(iniFile + ".new", iniFile, true, this)) {
+ QMessageBox::critical(this, tr("Failed to write settings"),
+ tr("An error occured trying to write back MO settings: %1").arg(windowsErrorString(::GetLastError())));
+ }
+ } else {
+ 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(this, tr("Failed to write settings"),
+ tr("An error occured trying to write back MO settings: %1").arg(reason));
+ }
+}
+
+
+void MainWindow::on_btnRefreshData_clicked()
+{
+ if (!m_DirectoryUpdate) {
+ refreshDirectoryStructure();
+ } else {
+ qDebug("directory update");
+ }
+}
+
+void MainWindow::on_tabWidget_currentChanged(int index)
+{
+ if (index == 0) {
+ refreshESPList();
+ } else if (index == 1) {
+ refreshBSAList();
+ } else if (index == 2) {
+ refreshDataTree();
+ } else if (index == 3) {
+ refreshSaveList();
+ } else if (index == 4) {
+ ui->downloadView->scrollToBottom();
+ }
+}
+
+std::vector<unsigned int> MainWindow::activeProblems() const
+{
+ std::vector<unsigned int> problems;
+ if (m_UnloadedPlugins.size() != 0) {
+ problems.push_back(PROBLEM_PLUGINSNOTLOADED);
+ }
+ if (m_PluginList.enabledCount() > 255) {
+ problems.push_back(PROBLEM_TOOMANYPLUGINS);
+ }
+ return problems;
+}
+
+QString MainWindow::shortDescription(unsigned int key) const
+{
+ switch (key) {
+ case PROBLEM_PLUGINSNOTLOADED: {
+ return tr("Some plugins could not be loaded");
+ } break;
+ case PROBLEM_TOOMANYPLUGINS: {
+ return tr("Too many esps and esms enabled");
+ } break;
+ default: {
+ return tr("Description missing");
+ } break;
+ }
+}
+
+QString MainWindow::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:") + "<ul>";
+ foreach (const QString &plugin, m_UnloadedPlugins) {
+ result += "<li>" + plugin + "</li>";
+ }
+ result += "<ul>";
+ return result;
+ } break;
+ case PROBLEM_TOOMANYPLUGINS: {
+ return tr("The game doesn't allow more than 255 active plugins (including the official ones) to be loaded. You have to disable some unused plugins or "
+ "merge some plugins into one. You can find a guide here: <a href=\"http://wiki.step-project.com/Guide:Merging_Plugins\">http://wiki.step-project.com/Guide:Merging_Plugins</a>");
+ } break;
+ default: {
+ return tr("Description missing");
+ } break;
+ }
+}
+
+bool MainWindow::hasGuidedFix(unsigned int) const
+{
+ return false;
+}
+
+void MainWindow::startGuidedFix(unsigned int) const
+{
+}
+
+void MainWindow::installMod()
+{
+ try {
+ QStringList extensions = m_InstallationManager.getSupportedExtensions();
+ for (auto iter = extensions.begin(); iter != extensions.end(); ++iter) {
+ *iter = "*." + *iter;
+ }
+
+ QString fileName = FileDialogMemory::getOpenFileName("installMod", this, tr("Choose Mod"), QString(),
+ tr("Mod Archive").append(QString(" (%1)").arg(extensions.join(" "))));
+
+ if (fileName.length() == 0) {
+ return;
+ } else {
+ installMod(fileName);
+ }
+ } catch (const std::exception &e) {
+ reportError(e.what());
+ }
+}
+
+IModInterface *MainWindow::installMod(const QString &fileName)
+{
+ if (m_CurrentProfile == NULL) {
+ return NULL;
+ }
+
+ bool hasIniTweaks = false;
+ GuessedValue<QString> modName;
+ m_CurrentProfile->writeModlistNow();
+ m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
+ if (m_InstallationManager.install(fileName, modName, hasIniTweaks)) {
+ MessageDialog::showMessage(tr("Installation successful"), this);
+ refreshModList();
+
+ QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, static_cast<const QString&>(modName));
+ if (posList.count() == 1) {
+ ui->modList->scrollTo(posList.at(0));
+ }
+ int modIndex = ModInfo::getIndex(modName);
+ if (modIndex != UINT_MAX) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
+ if (hasIniTweaks &&
+ (QMessageBox::question(this, tr("Configure Mod"),
+ tr("This mod contains ini tweaks. Do you want to configure them now?"),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) {
+ 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(this, tr("Installation cancelled"), tr("The mod was not installed completely."), QMessageBox::Ok);
+ }
+ return NULL;
+}
+
+IModInterface *MainWindow::getMod(const QString &name)
+{
+ unsigned int index = ModInfo::getIndex(name);
+ if (index == UINT_MAX) {
+ return NULL;
+ } else {
+ return ModInfo::getByIndex(index).data();
+ }
+}
+
+IModInterface *MainWindow::createMod(GuessedValue<QString> &name)
+{
+ if (!m_InstallationManager.testOverwrite(name)) {
+ return NULL;
+ }
+
+ m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
+
+ QString targetDirectory = QDir::fromNativeSeparators(m_Settings.getModDirectory()).append("/").append(name);
+
+ QSettings settingsFile(targetDirectory.mid(0).append("/meta.ini"), QSettings::IniFormat);
+
+ 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();
+}
+
+bool MainWindow::removeMod(IModInterface *mod)
+{
+ unsigned int index = ModInfo::getIndex(mod->name());
+ if (index == UINT_MAX) {
+ return mod->remove();
+ } else {
+ return ModInfo::removeMod(index);
+ }
+}
+
+QList<IOrganizer::FileInfo> MainWindow::findFileInfos(const QString &path, const std::function<bool (const IOrganizer::FileInfo &)> &filter) const
+{
+ QList<IOrganizer::FileInfo> result;
+ DirectoryEntry *dir = m_DirectoryStructure->findSubDirectoryRecursive(ToWString(path));
+ if (dir != NULL) {
+ std::vector<FileEntry::Ptr> 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 (int idx, file->getAlternatives()) {
+ info.origins.append(ToQString(m_DirectoryStructure->getOriginByID(idx).getName()));
+ }
+
+ if (filter(info)) {
+ result.append(info);
+ }
+ }
+ }
+ return result;
+}
+
+void MainWindow::on_startButton_clicked()
+{
+ QComboBox* executablesList = findChild<QComboBox*>("executablesListBox");
+
+ Executable selectedExecutable = executablesList->itemData(executablesList->currentIndex()).value<Executable>();
+
+ spawnBinary(selectedExecutable.m_BinaryInfo,
+ selectedExecutable.m_Arguments,
+ selectedExecutable.m_WorkingDirectory.length() != 0 ? selectedExecutable.m_WorkingDirectory
+ : selectedExecutable.m_BinaryInfo.absolutePath(),
+ selectedExecutable.m_CloseMO == DEFAULT_CLOSE,
+ selectedExecutable.m_SteamAppID);
+}
+
+
+static HRESULT CreateShortcut(LPCWSTR targetFileName, LPCWSTR arguments,
+ LPCSTR linkFileName, LPCWSTR description,
+ LPCWSTR currentDirectory)
+{
+ HRESULT result = E_INVALIDARG;
+ if ((targetFileName != NULL) && (wcslen(targetFileName) > 0) &&
+ (arguments != NULL) &&
+ (linkFileName != NULL) && (strlen(linkFileName) > 0) &&
+ (description != NULL) &&
+ (currentDirectory != NULL)) {
+
+ IShellLink* shellLink;
+ result = CoCreateInstance(CLSID_ShellLink, NULL, CLSCTX_INPROC_SERVER,
+ IID_IShellLink, (LPVOID*)&shellLink);
+
+ if (!SUCCEEDED(result)) {
+ qCritical("failed to create IShellLink instance");
+ return result;
+ }
+ if (!SUCCEEDED(result)) 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;
+ }
+ }
+
+ IPersistFile* persistFile;
+ result = shellLink->QueryInterface(IID_IPersistFile, (LPVOID*)&persistFile);
+ if (SUCCEEDED(result)) {
+ wchar_t linkFileNameW[MAX_PATH];
+ MultiByteToWideChar(CP_ACP, 0, linkFileName, -1, linkFileNameW, MAX_PATH);
+ result = persistFile->Save(linkFileNameW, TRUE);
+ persistFile->Release();
+ } else {
+ qCritical("failed to create IPersistFile instance");
+ }
+
+ shellLink->Release();
+ }
+ return result;
+}
+
+
+bool MainWindow::modifyExecutablesDialog()
+{
+ bool result = false;
+ try {
+ EditExecutablesDialog dialog(m_ExecutablesList);
+ if (dialog.exec() == QDialog::Accepted) {
+ m_ExecutablesList = 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<QComboBox*>("executablesListBox");
+
+ int previousIndex = m_OldExecutableIndex;
+ m_OldExecutableIndex = index;
+
+ if (executablesList->isEnabled()) {
+
+ if (executablesList->itemData(index).isNull()) {
+ if (modifyExecutablesDialog()) {
+ setExecutableIndex(previousIndex);
+// executablesList->setCurrentIndex(previousIndex);
+ }
+ } else {
+ setExecutableIndex(index);
+ }
+ }
+}
+
+void MainWindow::helpTriggered()
+{
+ QWhatsThis::enterWhatsThisMode();
+}
+
+void MainWindow::wikiTriggered()
+{
+// ::ShellExecuteW(NULL, L"open", L"http://issue.tannin.eu/tbg/wiki/Modorganizer%3AMainPage", NULL, NULL, SW_SHOWNORMAL);
+ ::ShellExecuteW(NULL, L"open", L"http://wiki.step-project.com/Guide:Mod_Organizer", NULL, NULL, SW_SHOWNORMAL);
+}
+
+void MainWindow::issueTriggered()
+{
+ ::ShellExecuteW(NULL, L"open", L"http://issue.tannin.eu/tbg", NULL, NULL, SW_SHOWNORMAL);
+}
+
+void MainWindow::tutorialTriggered()
+{
+ QAction *tutorialAction = qobject_cast<QAction*>(sender());
+ if (tutorialAction != NULL) {
+ 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()
+{
+ bool repeat = true;
+ while (repeat) {
+ ProfilesDialog profilesDialog(m_GamePath);
+ profilesDialog.exec();
+ if (refreshProfiles() && !profilesDialog.failed()) {
+ repeat = false;
+ }
+ }
+// 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;
+ }
+}
+
+
+bool MainWindow::queryLogin(QString &username, QString &password)
+{
+ CredentialsDialog dialog(this);
+ 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 MainWindow::setCurrentProfile(int index)
+{
+ QComboBox *profilesBox = findChild<QComboBox*>("profileBox");
+ if (index >= profilesBox->count()) {
+ return false;
+ } else {
+ profilesBox->setCurrentIndex(index);
+ return true;
+ }
+}
+
+bool MainWindow::setCurrentProfile(const QString &name)
+{
+ QComboBox *profilesBox = findChild<QComboBox*>("profileBox");
+ for (int i = 0; i < profilesBox->count(); ++i) {
+ if (QString::compare(profilesBox->itemText(i), name, Qt::CaseInsensitive) == 0) {
+ profilesBox->setCurrentIndex(i);
+ return true;
+ }
+ }
+ // profile not valid
+ profilesBox->setCurrentIndex(1);
+ return false;
+}
+
+
+void MainWindow::refresher_progress(int percent)
+{
+ m_RefreshProgress->setValue(percent);
+}
+
+
+void MainWindow::directory_refreshed()
+{
+ DirectoryEntry *newStructure = m_DirectoryRefresher.getDirectoryStructure();
+ Q_ASSERT(newStructure != m_DirectoryStructure);
+ if (newStructure != NULL) {
+ std::swap(m_DirectoryStructure, newStructure);
+ delete newStructure;
+ refreshDataTree();
+ } else {
+ // TODO: don't know why this happens, this slot seems to get called twice with only one emit
+ return;
+ }
+ m_DirectoryUpdate = false;
+ if (m_CurrentProfile != NULL) {
+ refreshLists();
+ }
+
+ // some problem-reports may rely on the virtual directory tree so they need to be updated
+ // now
+ updateProblemsButton();
+
+ for (int i = 0; i < m_ModList.rowCount(); ++i) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(i);
+ modInfo->clearCaches();
+ }
+ statusBar()->hide();
+}
+
+void MainWindow::externalMessage(const QString &message)
+{
+ if (message.left(6).toLower() == "nxm://") {
+ MessageDialog::showMessage(tr("Download started"), this);
+ downloadRequestedNXM(message);
+ }
+}
+
+void MainWindow::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();
+ saveArchiveList();
+ m_DirectoryRefresher.setMods(m_CurrentProfile->getActiveMods(), enabledArchives());
+
+ // 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 MainWindow::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);
+ }
+ }
+ 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 MainWindow::removeOrigin(const QString &name)
+{
+ FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(name));
+ origin.enable(false);
+ refreshLists();
+}
+
+
+void MainWindow::modorder_changed()
+{
+ for (unsigned int i = 0; i < m_CurrentProfile->numMods(); ++i) {
+ int priority = m_CurrentProfile->getModPriority(i);
+ if (m_CurrentProfile->modEnabled(i)) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(i);
+ // priorities in the directory structure are one higher because data is 0
+ m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())).setPriority(priority + 1);
+ }
+ }
+ refreshBSAList();
+ m_CurrentProfile->writeModlist();
+ saveArchiveList();
+ m_DirectoryStructure->getFileRegister()->sortOrigins();
+}
+
+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::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());
+ m_CurrentProfile->refreshModStatus();
+
+ refreshModList();
+}
+
+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::renameModInList(QFile &modList, const QString &oldName, const QString &newName)
+{
+ //TODO this code needs to be merged with ModList::readFrom
+ if (!modList.open(QIODevice::ReadWrite)) {
+ reportError(tr("failed to open %1").arg(modList.fileName()));
+ return;
+ }
+
+ QBuffer outBuffer;
+ outBuffer.open(QIODevice::WriteOnly);
+
+ 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;
+ }
+
+ QString modName = QString::fromUtf8(line).mid(1).trimmed();
+
+ if (modName.isEmpty()) {
+ // file broken?
+ qWarning("mod list contained invalid data: missing mod name");
+ continue;
+ }
+
+ outBuffer.write(QByteArray(1, spec));
+ if (modName == oldName) {
+ modName = newName;
+ }
+ outBuffer.write(modName.toUtf8().constData());
+ outBuffer.write("\r\n");
+ }
+
+ modList.resize(0);
+ modList.write(outBuffer.buffer());
+ modList.close();
+}
+
+
+void MainWindow::modRenamed(const QString &oldName, const QString &newName)
+{
+ // fix the profiles directly on disc
+ for (int i = 0; i < ui->profileBox->count(); ++i) {
+ QString profileName = ui->profileBox->itemText(i);
+
+ //TODO this functionality should be in the Profile class
+ QString modlistName = QString("%1/%2/modlist.txt")
+ .arg(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())))
+ .arg(profileName);
+
+ QFile modList(modlistName);
+ if (modList.exists()) {
+ qDebug("rewrite modlist %s", QDir::toNativeSeparators(modlistName).toUtf8().constData());
+ renameModInList(modList, oldName, newName);
+ }
+ }
+
+ // immediately refresh the active profile because the data in memory is invalid
+ m_CurrentProfile->refreshModStatus();
+
+ // also fix the directory structure
+ try {
+ if (m_DirectoryStructure->originExists(ToWString(oldName))) {
+ FilesOrigin &origin = m_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::modlistChanged(int)
+{
+ m_ModListSortProxy->invalidate();
+}
+
+
+void MainWindow::fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName)
+{
+ const FileEntry::Ptr filePtr = m_DirectoryStructure->findFile(ToWString(filePath));
+ if (filePtr.get() != NULL) {
+ try {
+ if (m_DirectoryStructure->originExists(ToWString(newOriginName))) {
+ FilesOrigin &newOrigin = m_DirectoryStructure->getOriginByName(ToWString(newOriginName));
+
+ QString fullNewPath = ToQString(newOrigin.getPath()) + "\\" + filePath;
+ WIN32_FIND_DATAW findData;
+ ::FindFirstFileW(ToWString(fullNewPath).c_str(), &findData);
+
+ filePtr->addOrigin(newOrigin.getID(), findData.ftCreationTime, L"");
+ }
+ if (m_DirectoryStructure->originExists(ToWString(oldOriginName))) {
+ FilesOrigin &oldOrigin = m_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)
+{
+ QTreeWidgetItem *item = new QTreeWidgetItem(QStringList(name));
+ item->setData(0, Qt::UserRole, categoryID);
+ if (root != NULL) {
+ root->addChild(item);
+ } else {
+ ui->categoriesList->addTopLevelItem(item);
+ }
+ return item;
+}
+
+
+void MainWindow::addCategoryFilters(QTreeWidgetItem *root, const std::set<int> &categoriesUsed, int targetID)
+{
+ for (unsigned i = 1; i < 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);
+ if (m_CategoryFactory.hasChildren(i)) {
+ addCategoryFilters(item, categoriesUsed, categoryID);
+ }
+ }
+ }
+ }
+}
+
+
+void MainWindow::refreshFilters()
+{
+ QItemSelection currentSelection = ui->modList->selectionModel()->selection();
+
+ ui->modList->setCurrentIndex(QModelIndex());
+
+ QStringList selectedItems;
+ foreach (QTreeWidgetItem *item, ui->categoriesList->selectedItems()) {
+ selectedItems.append(item->text(0));
+ }
+
+ ui->categoriesList->clear();
+ addFilterItem(NULL, tr("<Checked>"), CategoryFactory::CATEGORY_SPECIAL_CHECKED);
+ addFilterItem(NULL, tr("<Unchecked>"), CategoryFactory::CATEGORY_SPECIAL_UNCHECKED);
+ addFilterItem(NULL, tr("<Update>"), CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE);
+ addFilterItem(NULL, tr("<Managed by MO>"), CategoryFactory::CATEGORY_SPECIAL_MANAGED);
+ addFilterItem(NULL, tr("<Managed outside MO>"), CategoryFactory::CATEGORY_SPECIAL_UNMANAGED);
+ addFilterItem(NULL, tr("<No category>"), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY);
+ addFilterItem(NULL, tr("<Conflicted>"), CategoryFactory::CATEGORY_SPECIAL_CONFLICT);
+ addFilterItem(NULL, tr("<Not Endorsed>"), CategoryFactory::CATEGORY_SPECIAL_NOTENDORSED);
+
+ std::set<int> categoriesUsed;
+ for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modIdx);
+ BOOST_FOREACH (int categoryID, modInfo->getCategories()) {
+ int currentID = categoryID;
+ // also add parents so they show up in the tree
+ while (currentID != 0) {
+ categoriesUsed.insert(currentID);
+ currentID = m_CategoryFactory.getParentID(m_CategoryFactory.getCategoryIndex(currentID));
+ }
+ }
+ }
+
+ addCategoryFilters(NULL, categoriesUsed, 0);
+
+ foreach (const QString &item, selectedItems) {
+ QList<QTreeWidgetItem*> 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);
+}
+
+
+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_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_Settings.getModDirectory()) + "/" + regName;
+ if (!modDir.rename(modInfo->absolutePath(), destinationPath)) {
+ reportError(tr("failed to rename \"%1\" to \"%2\"").arg(modInfo->absolutePath()).arg(destinationPath));
+ }
+ refreshModList();
+ }
+ }
+ }
+}
+
+void MainWindow::updateModActiveState(int index, bool active)
+{
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
+
+ QDir dir(modInfo->absolutePath());
+ foreach (const QString &esm, dir.entryList(QStringList("*.esm"), QDir::Files)) {
+ m_PluginList.enableESP(esm, active);
+ }
+ int enabled = 0;
+ QStringList esps = dir.entryList(QStringList("*.esp"), QDir::Files);
+ foreach (const QString &esp, esps) {
+ if (active != m_PluginList.isEnabled(esp)) {
+ m_PluginList.enableESP(esp, active);
+ ++enabled;
+ }
+ }
+ if (active && (enabled > 1)) {
+ MessageDialog::showMessage(tr("Multiple esps activated, please check that they don't conflict."), this);
+ }
+ m_PluginList.refreshLoadOrder();
+ // immediately save affected lists
+ savePluginList();
+// refreshBSAList();
+}
+
+void MainWindow::modlistChanged(const QModelIndex&, int)
+{
+ m_CurrentProfile->writeModlist();
+}
+
+void MainWindow::modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex&)
+{
+ ModInfo::Ptr selectedMod = ModInfo::getByIndex(current.data(Qt::UserRole + 1).toInt());
+ m_ModList.setOverwriteMarkers(selectedMod->getModOverwrite(), selectedMod->getModOverwritten());
+ if (m_ModListSortProxy != NULL) {
+ m_ModListSortProxy->invalidate();
+ }
+ 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;
+ foreach (QModelIndex idx, selection->selectedRows()) {
+// QString name = ModInfo::getByIndex(m_ModListGroupProxy->mapToSource(idx).row())->name();
+ QString name = idx.data().toString();
+ if (!ModInfo::getByIndex(idx.data(Qt::UserRole + 1).toInt())->isRegular()) {
+ continue;
+ }
+ mods += "<li>" + name + "</li>";
+ modNames.append(name);
+ }
+ if (QMessageBox::question(this, tr("Confirm"),
+ tr("Remove the following mods?<br><ul>%1</ul>").arg(mods),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ // use mod names instead of indexes because those become invalid during the removal
+ foreach (QString name, modNames) {
+ m_ModList.removeRowForce(ModInfo::getIndex(name));
+ }
+ }
+ } else {
+ m_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_DownloadManager.indexByName(fileName);
+ if (index >= 0) {
+ m_DownloadManager.markUninstalled(index);
+ }
+ }
+}
+
+
+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_DownloadManager.getOutputDirectory().append("/").append(fileInfo.fileName());
+ }
+ } else {
+ fullInstallationFile = m_DownloadManager.getOutputDirectory().append("/").append(installationFile);
+ }
+ if (QFile::exists(fullInstallationFile)) {
+ installMod(fullInstallationFile);
+ } 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::resumeDownload(int downloadIndex)
+{
+ if (NexusInterface::instance()->getAccessManager()->loggedIn()) {
+ m_DownloadManager.resumeDownload(downloadIndex);
+ } else {
+ QString username, password;
+ if (m_Settings.getNexusLogin(username, password)) {
+ m_PostLoginTasks.push_back(boost::bind(&MainWindow::resumeDownload, _1, downloadIndex));
+ NexusInterface::instance()->getAccessManager()->login(username, password);
+ } else {
+ MessageDialog::showMessage(tr("You need to be logged in with Nexus to resume a download"), this);
+ }
+ }
+}
+
+
+void MainWindow::endorseMod(ModInfo::Ptr mod)
+{
+ if (NexusInterface::instance()->getAccessManager()->loggedIn()) {
+ mod->endorse(true);
+ } else {
+ QString username, password;
+ if (m_Settings.getNexusLogin(username, password)) {
+ m_PostLoginTasks.push_back(boost::bind(&MainWindow::endorseMod, _1, mod));
+ NexusInterface::instance()->getAccessManager()->login(username, password);
+ } else {
+ MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this);
+ }
+ }
+}
+
+
+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);
+ } else {
+ if (m_Settings.getNexusLogin(username, password)) {
+ m_PostLoginTasks.push_back(boost::mem_fn(&MainWindow::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::overwriteClosed(int)
+{
+ OverwriteInfoDialog *dialog = this->findChild<OverwriteInfoDialog*>("__overwriteDialog");
+ if (dialog != NULL) {
+ m_ModList.modInfoChanged(dialog->modInfo());
+ dialog->deleteLater();
+ }
+}
+
+
+void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab)
+{
+ m_ModList.modInfoAboutToChange(modInfo);
+ std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
+ if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) {
+ QDialog *dialog = this->findChild<QDialog*>("__overwriteDialog");
+ try {
+ if (dialog == NULL) {
+ dialog = new OverwriteInfoDialog(modInfo, this);
+ dialog->setObjectName("__overwriteDialog");
+ } else {
+ qobject_cast<OverwriteInfoDialog*>(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_DirectoryStructure, modInfo->hasFlag(ModInfo::FLAG_FOREIGN), this);
+ connect(&dialog, SIGNAL(nexusLinkActivated(QString)), this, SLOT(nexusLinkActivated(QString)));
+ connect(&dialog, SIGNAL(downloadRequest(QString)), this, 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_Settings.directInterface().value("mod_info_tabs").toByteArray());
+ dialog.exec();
+ m_Settings.directInterface().setValue("mod_info_tabs", dialog.saveTabState());
+
+ modInfo->saveMeta();
+ emit modInfoDisplayed();
+ m_ModList.modInfoChanged(modInfo);
+ }
+
+ if (m_CurrentProfile->modEnabled(index)) {
+ FilesOrigin& origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name()));
+ origin.enable(false);
+
+ if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) {
+ FilesOrigin& origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name()));
+ origin.enable(false);
+
+ m_DirectoryRefresher.addModToStructure(m_DirectoryStructure
+ , modInfo->name()
+ , m_CurrentProfile->getModPriority(index)
+ , modInfo->absolutePath()
+ , modInfo->stealFiles()
+ , modInfo->archives());
+ DirectoryRefresher::cleanStructure(m_DirectoryStructure);
+ refreshLists();
+ }
+ }
+}
+
+
+void MainWindow::modOpenNext()
+{
+ QModelIndex index = m_ModListSortProxy->mapFromSource(m_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<ModInfo::EFlag> 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::modOpenPrev()
+{
+ QModelIndex index = m_ModListSortProxy->mapFromSource(m_ModList.index(m_ContextRow, 0));
+ int row = index.row() - 1;
+ if (row == -1) {
+ row = m_ModListSortProxy->rowCount() - 1;
+ }
+
+ m_ContextRow = m_ModListSortProxy->mapToSource(m_ModListSortProxy->index(row, 0)).row();
+ ModInfo::Ptr mod = ModInfo::getByIndex(m_ContextRow);
+ std::vector<ModInfo::EFlag> 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);
+ }
+}
+
+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::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_ModList, SIGNAL(dataChanged(QModelIndex,QModelIndex)));
+
+ emit modListDataChanged(m_ModList.index(m_ContextRow, 0), m_ModList.index(m_ContextRow, m_ModList.columnCount() - 1));
+}
+
+
+void MainWindow::visitOnNexus_clicked()
+{
+ int modID = m_ModList.data(m_ModList.index(m_ContextRow, 0), Qt::UserRole).toInt();
+ if (modID > 0) {
+ nexusLinkActivated(QString("%1/mods/%2").arg(ToQString(GameInfo::instance().getNexusPage(false))).arg(modID));
+ } else {
+ MessageDialog::showMessage(tr("Nexus ID for this Mod is unknown"), this);
+ }
+}
+
+void MainWindow::openExplorer_clicked()
+{
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
+
+ ::ShellExecuteW(NULL, L"explore", ToWString(modInfo->absolutePath()).c_str(), NULL, NULL, SW_SHOWNORMAL);
+}
+
+void MainWindow::information_clicked()
+{
+ try {
+ displayModInformation(m_ContextRow);
+ } catch (const std::exception &e) {
+ reportError(e.what());
+ }
+}
+
+void MainWindow::syncOverwrite()
+{
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
+ SyncOverwriteDialog syncDialog(modInfo->absolutePath(), m_DirectoryStructure, this);
+ if (syncDialog.exec() == QDialog::Accepted) {
+ syncDialog.apply(QDir::fromNativeSeparators(m_Settings.getModDirectory()));
+ modInfo->testValid();
+ refreshDirectoryStructure();
+ }
+
+}
+
+void MainWindow::createModFromOverwrite()
+{
+ GuessedValue<QString> 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 (getMod(name) != NULL) {
+ reportError(tr("A mod with this name already exists"));
+ return;
+ }
+
+ IModInterface *newMod = createMod(name);
+ if (newMod == NULL) {
+ return;
+ }
+
+ ModInfo::Ptr overwriteInfo = ModInfo::getByIndex(m_ContextRow);
+ shellMove(QStringList(QDir::toNativeSeparators(overwriteInfo->absolutePath()) + "\\*"),
+ QStringList(QDir::toNativeSeparators(newMod->absolutePath())), this);
+
+ refreshModList();
+}
+
+void MainWindow::cancelModListEditor()
+{
+ ui->modList->setEnabled(false);
+ ui->modList->setEnabled(true);
+}
+
+void MainWindow::on_modList_doubleClicked(const QModelIndex &index)
+{
+ if (!index.isValid()) {
+ return;
+ }
+ QModelIndex sourceIdx = mapToModel(&m_ModList, index);
+ if (!sourceIdx.isValid()) {
+ return;
+ }
+
+ try {
+ m_ContextRow = m_ModListSortProxy->mapToSource(index).row();
+// displayModInformation(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<int> &categories = modInfo->getCategories();
+
+ bool childEnabled = false;
+
+ for (unsigned 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<QCheckBox> 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<QWidgetAction> 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);
+ foreach (QAction* action, menu->actions()) {
+ if (action->menu() != NULL) {
+ replaceCategoriesFromMenu(action->menu(), modRow);
+ } else {
+ QWidgetAction *widgetAction = qobject_cast<QWidgetAction*>(action);
+ if (widgetAction != NULL) {
+ QCheckBox *checkbox = qobject_cast<QCheckBox*>(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);
+ foreach (QAction* action, menu->actions()) {
+ if (action->menu() != NULL) {
+ addRemoveCategoriesFromMenu(action->menu(), modRow, referenceRow);
+ } else {
+ QWidgetAction *widgetAction = qobject_cast<QWidgetAction*>(action);
+ if (widgetAction != NULL) {
+ QCheckBox *checkbox = qobject_cast<QCheckBox*>(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::addRemoveCategories_MenuHandler() {
+ QMenu *menu = qobject_cast<QMenu*>(sender());
+ if (menu == NULL) {
+ qCritical("not a menu?");
+ return;
+ }
+
+ QModelIndexList selectedTemp = ui->modList->selectionModel()->selectedRows();
+ QList<QPersistentModelIndex> selected;
+ foreach (const QModelIndex &idx, selectedTemp) {
+ selected.append(QPersistentModelIndex(idx));
+ }
+
+ if (selected.size() > 0) {
+ foreach (const QPersistentModelIndex &idx, selected) {
+ qDebug("change categories on: %s (ref: %s)", qPrintable(idx.data().toString()), qPrintable(m_ContextIdx.data().toString()));
+ QModelIndex modIdx = mapToModel(&m_ModList, idx);
+ if (modIdx.row() != m_ContextIdx.row()) {
+ addRemoveCategoriesFromMenu(menu, modIdx.row(), m_ContextIdx.row());
+ }
+ }
+ replaceCategoriesFromMenu(menu, m_ContextIdx.row());
+
+ m_ModList.notifyChange(-1);
+
+ foreach (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_ModList.notifyChange(m_ContextRow);
+ }
+
+ refreshFilters();
+}
+
+void MainWindow::replaceCategories_MenuHandler() {
+ QMenu *menu = qobject_cast<QMenu*>(sender());
+ if (menu == NULL) {
+ qCritical("not a menu?");
+ return;
+ }
+
+ QModelIndexList selected = ui->modList->selectionModel()->selectedRows();
+
+ if (selected.size() > 0) {
+ QStringList selectedMods;
+ for (int i = 0; i < selected.size(); ++i) {
+ QModelIndex temp = mapToModel(&m_ModList, selected.at(i));
+ selectedMods.append(temp.data().toString());
+ replaceCategoriesFromMenu(menu, mapToModel(&m_ModList, selected.at(i)).row());
+ }
+
+ m_ModList.notifyChange(-1);
+
+ // find mods by their name because indices are invalidated
+ QAbstractItemModel *model = ui->modList->model();
+ Q_FOREACH(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_ModList.notifyChange(m_ContextRow);
+ }
+
+ refreshFilters();
+}
+
+void MainWindow::savePrimaryCategory()
+{
+ QMenu *menu = qobject_cast<QMenu*>(sender());
+ if (menu == NULL) {
+ qCritical("not a menu?");
+ return;
+ }
+
+ foreach (QAction* action, menu->actions()) {
+ QWidgetAction *widgetAction = qobject_cast<QWidgetAction*>(action);
+ if (widgetAction != NULL) {
+ QRadioButton *btn = qobject_cast<QRadioButton*>(widgetAction->defaultWidget());
+ if (btn->isChecked()) {
+ QModelIndexList selected = ui->modList->selectionModel()->selectedRows();
+ for (int i = 0; i < selected.size(); ++i) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ModListSortProxy->mapToSource(selected.at(i)).row());
+ modInfo->setPrimaryCategory(widgetAction->data().toInt());
+ }
+ break;
+ }
+ }
+ }
+}
+
+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_Settings.getNexusLogin(username, password)) {
+ m_PostLoginTasks.push_back(boost::mem_fn(&MainWindow::checkModsForUpdates));
+ NexusInterface::instance()->getAccessManager()->login(username, password);
+ } else { // otherwise there will be no endorsement info
+ m_ModsToUpdate = ModInfo::checkAllForUpdate(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::ignoreUpdate() {
+ ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow);
+ info->ignoreUpdate(true);
+}
+
+void MainWindow::unignoreUpdate()
+{
+ ModInfo::Ptr info = ModInfo::getByIndex(m_ContextRow);
+ info->ignoreUpdate(false);
+}
+
+void MainWindow::addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info)
+{
+ const std::set<int> &categories = info->getCategories();
+ foreach (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);
+ 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::addPrimaryCategoryCandidates()
+{
+ QMenu *menu = qobject_cast<QMenu*>(sender());
+ if (menu == NULL) {
+ qCritical("not a menu?");
+ return;
+ }
+ menu->clear();
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
+
+ addPrimaryCategoryCandidates(menu, modInfo);
+}
+
+void MainWindow::enableVisibleMods()
+{
+ if (QMessageBox::question(NULL, tr("Confirm"), tr("Really enable all visible mods?"),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ m_ModListSortProxy->enableAllVisible();
+ }
+}
+
+void MainWindow::disableVisibleMods()
+{
+ if (QMessageBox::question(NULL, tr("Confirm"), tr("Really disable all visible mods?"),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ m_ModListSortProxy->disableAllVisible();
+ }
+}
+
+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);
+
+ if (selection.exec() == QDialog::Accepted) {
+ unsigned int numMods = ModInfo::getNumMods();
+
+ try {
+ QBuffer buffer;
+ buffer.open(QIODevice::ReadWrite);
+ CSVBuilder builder(&buffer);
+ builder.setEscapeMode(CSVBuilder::TYPE_STRING, CSVBuilder::QUOTE_ALWAYS);
+ std::vector<std::pair<QString, CSVBuilder::EFieldType> > fields;
+ fields.push_back(std::make_pair(QString("mod_id"), CSVBuilder::TYPE_INTEGER));
+ fields.push_back(std::make_pair(QString("mod_installed_name"), CSVBuilder::TYPE_STRING));
+ fields.push_back(std::make_pair(QString("mod_version"), CSVBuilder::TYPE_STRING));
+ fields.push_back(std::make_pair(QString("file_installed_name"), CSVBuilder::TYPE_STRING));
+// fields.push_back(std::make_pair(QString("file_category"), CSVBuilder::TYPE_INTEGER));
+ builder.setFields(fields);
+
+ builder.writeHeader();
+
+ for (unsigned int i = 0; i < numMods; ++i) {
+ ModInfo::Ptr info = ModInfo::getByIndex(i);
+ bool enabled = m_CurrentProfile->modEnabled(i);
+ if ((selection.getChoiceData().toInt() == 1) && !enabled) {
+ continue;
+ } else if ((selection.getChoiceData().toInt() == 2) && !m_ModListSortProxy->filterMatchesMod(info, enabled)) {
+ continue;
+ }
+ std::vector<ModInfo::EFlag> 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())) {
+ builder.setRowField("mod_id", info->getNexusID());
+ builder.setRowField("mod_installed_name", info->name());
+ builder.setRowField("mod_version", info->getVersion().canonicalString());
+ builder.setRowField("file_installed_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()));
+ }
+ }
+}
+
+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);
+}
+
+QMenu *MainWindow::modListContextMenu()
+{
+ QMenu *menu = new QMenu(this);
+ menu->addAction(tr("Install Mod..."), this, SLOT(installMod_clicked()));
+
+ 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"), this, SLOT(profileRefresh()));
+
+ menu->addAction(tr("Export to csv..."), this, SLOT(exportModListCSV()));
+ return menu;
+}
+
+void MainWindow::on_modList_customContextMenuRequested(const QPoint &pos)
+{
+ try {
+ QTreeView *modList = findChild<QTreeView*>("modList");
+
+ m_ContextIdx = mapToModel(&m_ModList, modList->indexAt(pos));
+ m_ContextRow = m_ContextIdx.row();
+
+ QMenu *menu = NULL;
+ 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<ModInfo::EFlag> 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..."), this, SLOT(syncOverwrite()));
+ menu->addAction(tr("Create Mod..."), this, SLOT(createModFromOverwrite()));
+ }
+ } 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()));
+ connect(primaryCategoryMenu, SIGNAL(aboutToHide()), this, SLOT(savePrimaryCategory()));
+ addMenuAsPushButton(menu, primaryCategoryMenu);
+
+ menu->addSeparator();
+ if (info->downgradeAvailable()) {
+ menu->addAction(tr("Change versioning scheme"), this, SLOT(changeVersioningScheme()));
+ }
+ if (info->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()));
+ 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;
+ }
+ std::vector<ModInfo::EFlag> 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()));
+ }
+
+ menu->addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked()));
+ menu->addAction(tr("Open in explorer"), this, SLOT(openExplorer_clicked()));
+ }
+
+ 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);
+ }
+ }
+
+ menu->exec(modList->mapToGlobal(pos));
+ } catch (const std::exception &e) {
+ reportError(tr("Exception: ").arg(e.what()));
+ } catch (...) {
+ reportError(tr("Unknown exception"));
+ }
+}
+
+
+void MainWindow::on_categoriesList_itemSelectionChanged()
+{
+ QModelIndexList indices = ui->categoriesList->selectionModel()->selectedRows();
+ std::vector<int> categories;
+ foreach (const QModelIndex &index, indices) {
+ int categoryId = index.data(Qt::UserRole).toInt();
+ if (categoryId != CategoryFactory::CATEGORY_NONE) {
+ categories.push_back(categoryId);
+ }
+ }
+
+ m_ModListSortProxy->setCategoryFilter(categories);
+ ui->clickBlankLabel->setEnabled(categories.size() > 0);
+ if (indices.count() == 0) {
+ ui->currentCategoryLabel->setText(QString("(%1)").arg(tr("<All>")));
+ } else if (indices.count() > 1) {
+ ui->currentCategoryLabel->setText(QString("(%1)").arg(tr("<Multiple>")));
+ } else {
+ ui->currentCategoryLabel->setText(QString("(%1)").arg(indices.first().data().toString()));
+ }
+ ui->modList->reset();
+}
+
+
+void MainWindow::deleteSavegame_clicked()
+{
+ QModelIndexList selectedIndexes = ui->savegameList->selectionModel()->selectedIndexes();
+
+ QString savesMsgLabel;
+ QStringList deleteFiles;
+
+ foreach (const QModelIndex &idx, selectedIndexes) {
+ QString name = idx.data().toString();
+ SaveGame *save = new SaveGame(this, idx.data(Qt::UserRole).toString());
+
+ savesMsgLabel += "<li>" + QFileInfo(name).completeBaseName() + "</li>";
+
+ deleteFiles << save->saveFiles();
+ }
+
+ if (QMessageBox::question(this, tr("Confirm"), tr("Are you sure you want to remove the following %n save(s)?<br><ul>%1</ul><br>Removed saves will be sent to the Recycle Bin.", "", selectedIndexes.count())
+ .arg(savesMsgLabel),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ shellDelete(deleteFiles, true); // recycle bin delete.
+ }
+}
+
+
+void MainWindow::fixMods_clicked()
+{
+ QListWidgetItem *selectedItem = ui->savegameList->currentItem();
+
+ if (selectedItem == NULL)
+ return;
+
+ // if required, parse the save game
+ if (selectedItem->data(Qt::UserRole).isNull()) {
+ QVariant temp;
+ SaveGameGamebryo *save = getSaveGame(selectedItem->data(Qt::UserRole).toString());
+ save->setParent(selectedItem->listWidget());
+ temp.setValue(save);
+ selectedItem->setData(Qt::UserRole, temp);
+ }
+
+ const SaveGameGamebryo *save = getSaveGame(selectedItem);
+
+ // collect the list of missing plugins
+ std::map<QString, std::vector<QString> > missingPlugins;
+
+ for (int i = 0; i < save->numPlugins(); ++i) {
+ const QString &pluginName = save->plugin(i);
+ if (!m_PluginList.isEnabled(pluginName)) {
+ missingPlugins[pluginName] = std::vector<QString>();
+ }
+ }
+
+ // figure out, for each esp/esm, which mod, if any, contains it
+ QStringList espFilter("*.esp");
+ espFilter.append("*.esm");
+
+ // search in data
+ {
+ QDir dataDir(m_GamePath + "/data");
+ QStringList esps = dataDir.entryList(espFilter);
+ foreach (const QString &esp, esps) {
+ std::map<QString, std::vector<QString> >::iterator iter = missingPlugins.find(esp);
+ if (iter != missingPlugins.end()) {
+ iter->second.push_back("<data>");
+ }
+ }
+ }
+
+ // search in mods
+ for (unsigned int i = 0; i < m_CurrentProfile->numRegularMods(); ++i) {
+ int modIndex = m_CurrentProfile->modIndexByPriority(i);
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
+
+ QStringList esps = QDir(modInfo->absolutePath()).entryList(espFilter);
+ foreach (const QString &esp, esps) {
+ std::map<QString, std::vector<QString> >::iterator iter = missingPlugins.find(esp);
+ if (iter != missingPlugins.end()) {
+ iter->second.push_back(modInfo->name());
+ }
+ }
+ }
+
+ // search in overwrite
+ {
+ QDir overwriteDir(ToQString(GameInfo::instance().getOverwriteDir()));
+ QStringList esps = overwriteDir.entryList(espFilter);
+ foreach (const QString &esp, esps) {
+ std::map<QString, std::vector<QString> >::iterator iter = missingPlugins.find(esp);
+ if (iter != missingPlugins.end()) {
+ iter->second.push_back("<overwrite>");
+ }
+ }
+ }
+
+
+ ActivateModsDialog dialog(missingPlugins, this);
+ if (dialog.exec() == QDialog::Accepted) {
+ // activate the required mods, then enable all esps
+ std::set<QString> modsToActivate = dialog.getModsToActivate();
+ for (std::set<QString>::iterator iter = modsToActivate.begin(); iter != modsToActivate.end(); ++iter) {
+ if ((*iter != "<data>") && (*iter != "<overwrite>")) {
+ unsigned int modIndex = ModInfo::getIndex(*iter);
+ m_CurrentProfile->setModEnabled(modIndex, true);
+ }
+ }
+
+ m_CurrentProfile->writeModlist();
+ refreshLists();
+
+ std::set<QString> espsToActivate = dialog.getESPsToActivate();
+ for (std::set<QString>::iterator iter = espsToActivate.begin(); iter != espsToActivate.end(); ++iter) {
+ m_PluginList.enableESP(*iter);
+ }
+ saveCurrentLists();
+ }
+}
+
+
+void MainWindow::on_savegameList_customContextMenuRequested(const QPoint &pos)
+{
+ QItemSelectionModel *selection = ui->savegameList->selectionModel();
+
+ if (!selection->hasSelection())
+ return;
+
+ QMenu menu;
+
+ if (!(selection->selectedIndexes().count() > 1))
+ menu.addAction(tr("Fix Mods..."), this, SLOT(fixMods_clicked()));
+
+ 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::linkToolbar()
+{
+ const Executable &selectedExecutable = ui->executablesListBox->itemData(ui->executablesListBox->currentIndex()).value<Executable>();
+ Executable &exe = m_ExecutablesList.find(selectedExecutable.m_Title);
+ exe.m_Toolbar = !exe.m_Toolbar;
+ ui->linkButton->menu()->actions().at(2)->setIcon(exe.m_Toolbar ? QIcon(":/MO/gui/remove") : QIcon(":/MO/gui/link"));
+ updateToolBar();
+}
+
+void MainWindow::linkDesktop()
+{
+ QComboBox* executablesList = findChild<QComboBox*>("executablesListBox");
+
+ const Executable &selectedExecutable = executablesList->itemData(executablesList->currentIndex()).value<Executable>();
+ QString linkName = getDesktopDirectory() + "\\" + selectedExecutable.m_Title + ".lnk";
+
+ if (QFile::exists(linkName)) {
+ if (QFile::remove(linkName)) {
+ ui->linkButton->menu()->actions().at(0)->setIcon(QIcon(":/MO/gui/link"));
+ } else {
+ reportError(tr("failed to remove %1").arg(linkName));
+ }
+ } else {
+ QFileInfo exeInfo(qApp->arguments().at(0));
+ // create link
+ std::wstring targetFile = ToWString(exeInfo.absoluteFilePath());
+ std::wstring parameter = ToWString(QString("\"%1\" %2").arg(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath()))
+ .arg(selectedExecutable.m_Arguments));
+ std::wstring description = ToWString(selectedExecutable.m_BinaryInfo.fileName());
+ std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(exeInfo.absolutePath()));
+ if (CreateShortcut(targetFile.c_str()
+ , parameter.c_str()
+ , linkName.toUtf8().constData()
+ , description.c_str()
+ , currentDirectory.c_str()) != E_INVALIDARG) {
+ ui->linkButton->menu()->actions().at(0)->setIcon(QIcon(":/MO/gui/remove"));
+ } else {
+ reportError(tr("failed to create %1").arg(linkName));
+ }
+ }
+}
+
+void MainWindow::linkMenu()
+{
+ QComboBox* executablesList = findChild<QComboBox*>("executablesListBox");
+
+ const Executable &selectedExecutable = executablesList->itemData(executablesList->currentIndex()).value<Executable>();
+ QString linkName = getStartMenuDirectory() + "\\" + selectedExecutable.m_Title + ".lnk";
+
+ if (QFile::exists(linkName)) {
+ if (QFile::remove(linkName)) {
+ ui->linkButton->menu()->actions().at(1)->setIcon(QIcon(":/MO/gui/link"));
+ } else {
+ reportError(tr("failed to remove %1").arg(linkName));
+ }
+ } else {
+ QFileInfo exeInfo(qApp->arguments().at(0));
+ // create link
+ std::wstring targetFile = ToWString(exeInfo.absoluteFilePath());
+ std::wstring parameter = ToWString(QString("\"%1\" %2").arg(QDir::toNativeSeparators(selectedExecutable.m_BinaryInfo.absoluteFilePath()))
+ .arg(selectedExecutable.m_Arguments));
+ std::wstring description = ToWString(selectedExecutable.m_BinaryInfo.fileName());
+ std::wstring currentDirectory = ToWString(QDir::toNativeSeparators(exeInfo.absolutePath()));
+
+ if (CreateShortcut(targetFile.c_str(), parameter.c_str(),
+ linkName.toUtf8().constData(),
+ description.c_str(), currentDirectory.c_str()) != E_INVALIDARG) {
+ ui->linkButton->menu()->actions().at(1)->setIcon(QIcon(":/MO/gui/remove"));
+ } else {
+ reportError(tr("failed to create %1").arg(linkName));
+ }
+ }
+}
+
+void MainWindow::downloadSpeed(const QString &serverName, int bytesPerSecond)
+{
+ m_Settings.setDownloadSpeed(serverName, bytesPerSecond);
+}
+
+
+void MainWindow::on_actionSettings_triggered()
+{
+ QString oldModDirectory(m_Settings.getModDirectory());
+ QString oldCacheDirectory(m_Settings.getCacheDirectory());
+ bool oldDisplayForeign(m_Settings.displayForeign());
+ bool proxy = m_Settings.useProxy();
+ m_Settings.query(this);
+ m_InstallationManager.setModsDirectory(m_Settings.getModDirectory());
+ m_InstallationManager.setDownloadDirectory(m_Settings.getDownloadDirectory());
+ fixCategories();
+ refreshFilters();
+ if (QDir::fromNativeSeparators(m_DownloadManager.getOutputDirectory()) != QDir::fromNativeSeparators(m_Settings.getDownloadDirectory())) {
+ if (m_DownloadManager.downloadsInProgress()) {
+ MessageDialog::showMessage(tr("Can't change download directory while downloads are in progress!"), this);
+ } else {
+ m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory());
+ }
+ }
+ m_DownloadManager.setPreferredServers(m_Settings.getPreferredServers());
+
+ if ((m_Settings.getModDirectory() != oldModDirectory)
+ || (m_Settings.displayForeign() != oldDisplayForeign)) {
+ profileRefresh();
+ }
+
+ if (m_Settings.getCacheDirectory() != oldCacheDirectory) {
+ NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory());
+ }
+
+ if (proxy != m_Settings.useProxy()) {
+ activateProxy(m_Settings.useProxy());
+ }
+
+ NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion());
+
+ updateDownloadListDelegate();
+}
+
+
+void MainWindow::on_actionNexus_triggered()
+{
+ ::ShellExecuteW(NULL, L"open", GameInfo::instance().getNexusPage(false).c_str(), NULL, NULL, SW_SHOWNORMAL);
+}
+
+
+void MainWindow::nexusLinkActivated(const QString &link)
+{
+ ::ShellExecuteW(NULL, L"open", ToWString(link).c_str(), NULL, NULL, SW_SHOWNORMAL);
+ ui->tabWidget->setCurrentIndex(4);
+}
+
+
+void MainWindow::linkClicked(const QString &url)
+{
+ ::ShellExecuteW(NULL, L"open", ToWString(url).c_str(), NULL, NULL, SW_SHOWNORMAL);
+}
+
+
+bool MainWindow::nexusLogin()
+{
+ QString username, password;
+
+ NXMAccessManager *accessManager = NexusInterface::instance()->getAccessManager();
+
+ if (!accessManager->loginAttempted()
+ && !accessManager->loggedIn()
+ && (m_Settings.getNexusLogin(username, password)
+ || (m_AskForNexusPW
+ && queryLogin(username, password)))) {
+ accessManager->login(username, password);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+void MainWindow::downloadRequestedNXM(const QString &url)
+{
+ qDebug("download requested: %s", qPrintable(url));
+ if (nexusLogin()) {
+ m_PendingDownloads.append(url);
+ } else {
+ m_DownloadManager.addNXMDownload(url);
+ }
+}
+
+
+void MainWindow::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"), this);
+ }
+ } catch (const std::exception &e) {
+ MessageDialog::showMessage(tr("Download failed"), this);
+ qCritical("exception starting download: %s", e.what());
+ }
+}
+
+
+void MainWindow::installTranslator(const QString &name)
+{
+/* if (m_CurrentLanguage == "en_US") {
+ return;
+ }*/
+ QTranslator *translator = new QTranslator(this);
+ QString fileName = name + "_" + m_CurrentLanguage;
+ if (!translator->load(fileName, qApp->applicationDirPath() + "/translations")) {
+ if (m_CurrentLanguage != "en_US") {
+ qWarning("localization file %s not found", qPrintable(fileName));
+ } // we don't actually expect localization files for english
+ }
+ qApp->installTranslator(translator);
+ m_Translators.push_back(translator);
+}
+
+
+void MainWindow::languageChange(const QString &newLanguage)
+{
+ foreach (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("<Manage...>"));
+
+ createHelpWidget();
+
+ updateDownloadListDelegate();
+ updateProblemsButton();
+
+ ui->listOptionsBtn->setMenu(modListContextMenu());
+}
+
+
+void MainWindow::installDownload(int index)
+{
+ try {
+ QString fileName = m_DownloadManager.getFilePath(index);
+ int modID = m_DownloadManager.getModID(index);
+ GuessedValue<QString> modName;
+
+ // see if there already are mods with the specified mod id
+ if (modID != 0) {
+ std::vector<ModInfo::Ptr> modInfo = ModInfo::getByModID(modID);
+ for (auto iter = modInfo.begin(); iter != modInfo.end(); ++iter) {
+ std::vector<ModInfo::EFlag> 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"), this);
+ refreshModList();
+
+ QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, static_cast<const QString&>(modName));
+ if (posList.count() == 1) {
+ ui->modList->scrollTo(posList.at(0));
+ }
+ int modIndex = ModInfo::getIndex(modName);
+ if (modIndex != UINT_MAX) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
+
+ if (hasIniTweaks &&
+ (QMessageBox::question(this, tr("Configure Mod"),
+ tr("This mod contains ini tweaks. Do you want to configure them now?"),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes)) {
+ displayModInformation(modInfo, modIndex, ModInfoDialog::TAB_INIFILES);
+ }
+
+ m_ModInstalled(modName);
+ } else {
+ reportError(tr("mod \"%1\" not found").arg(modName));
+ }
+ m_DownloadManager.markInstalled(index);
+
+ emit modInstalled();
+ } else if (m_InstallationManager.wasCancelled()) {
+ QMessageBox::information(this, tr("Installation cancelled"), tr("The mod was not installed completely."), QMessageBox::Ok);
+ }
+ } catch (const std::exception &e) {
+ reportError(e.what());
+ }
+}
+
+
+void MainWindow::writeDataToFile(QFile &file, const QString &directory, const DirectoryEntry &directoryEntry)
+{
+ { // list files
+// std::set<FileEntry>::const_iterator current, end;
+// directoryEntry.getFiles(current, end);
+// for (; current != end; ++current) {
+
+ std::vector<FileEntry::Ptr> files = directoryEntry.getFiles();
+ for (auto iter = files.begin(); iter != files.end(); ++iter) {
+ FileEntry::Ptr current = *iter;
+ 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;
+ fullName.append("\\").append(ToQString(current->getName()));
+ file.write(fullName.toUtf8());
+
+ file.write("\t(");
+ file.write(ToQString(m_DirectoryStructure->getOriginByID(origin).getName()).toUtf8());
+ file.write(")\r\n");
+ }
+ }
+
+ { // recurse into subdirectories
+ std::vector<DirectoryEntry*>::const_iterator current, end;
+ directoryEntry.getSubDirectories(current, end);
+ for (; current != end; ++current) {
+ writeDataToFile(file, directory.mid(0).append("\\").append(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_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.completeSuffix();
+ if ((extension == "exe") ||
+ (extension == "cmd") ||
+ (extension == "com") ||
+ (extension == "bat")) {
+ binaryInfo = QFileInfo("C:\\Windows\\System32\\cmd.exe");
+ arguments = QString("/C \"%1\"").arg(QDir::toNativeSeparators(targetInfo.absoluteFilePath()));
+ return 1;
+ } else if (extension == "jar") {
+ // 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(), NULL, buffer) > (HINSTANCE)32) {
+ DWORD binaryType = 0UL;
+ if (!::GetBinaryTypeW(targetPathW.c_str(), &binaryType)) {
+ qDebug("failed to determine binary type: %lu", ::GetLastError());
+ } else if (binaryType == SCS_32BIT_BINARY) {
+ binaryPath = ToQString(buffer);
+ }
+ }
+ }
+ if (binaryPath.isEmpty() && (extension == "jar")) {
+ 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 != NULL) {
+ 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()) {
+ m_ExecutablesList.addExecutable(name, binaryInfo.absoluteFilePath(),
+ arguments, targetInfo.absolutePath(),
+ DEFAULT_STAY, QString(),
+ true, false);
+ 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_DirectoryStructure->getOriginByID(originID);
+ origin.enable(false);
+ m_DirectoryStructure->addFromOrigin(origin.getName(), origin.getPath(), origin.getPriority());
+ DirectoryRefresher::cleanStructure(m_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 {
+ return;
+ }
+ }
+
+ if (QFile::rename(oldName, newName)) {
+ originModified(m_ContextItem->data(1, Qt::UserRole + 1).toInt());
+ refreshDataTree();
+ } else {
+ reportError(tr("failed to rename \"%1\" to \"%2\"").arg(oldName).arg(QDir::toNativeSeparators(newName)));
+ }
+}
+
+
+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());
+ refreshDataTree();
+ } 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_Settings.getModDirectory().size() + 1;
+ offset = fileName.indexOf("/", offset);
+ fileName = fileName.mid(offset + 1);
+
+ const FileEntry::Ptr file = m_DirectoryStructure->searchFile(ToWString(fileName), NULL);
+
+ if (file.get() == NULL) {
+ reportError(tr("file not found: %1").arg(fileName));
+ return;
+ }
+
+ // set up preview dialog
+ PreviewDialog preview(fileName);
+ auto addFunc = [&] (int originId) {
+ FilesOrigin &origin = m_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_PreviewGenerator.genPreview(filePath);
+ if (wid == NULL) {
+ reportError(tr("failed to generate preview for %1").arg(filePath));
+ } else {
+ preview.addVariant(ToQString(origin.getName()), wid);
+ }
+ }
+ };
+
+ addFunc(file->getOrigin());
+ foreach (int i, file->getAlternatives()) {
+ addFunc(i);
+ }
+ 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::openDataFile()
+{
+ if (m_ContextItem != NULL) {
+ QFileInfo targetInfo(m_ContextItem->data(0, Qt::UserRole).toString());
+ QFileInfo binaryInfo;
+ QString arguments;
+ switch (getBinaryExecuteInfo(targetInfo, binaryInfo, arguments)) {
+ case 1: {
+ spawnBinaryDirect(binaryInfo, arguments, m_CurrentProfile->getName(), targetInfo.absolutePath(), "");
+ } break;
+ case 2: {
+ ::ShellExecuteW(NULL, L"open", ToWString(targetInfo.absoluteFilePath()).c_str(), NULL, NULL, SW_SHOWNORMAL);
+ } break;
+ default: {
+ // nop
+ } break;
+ }
+ }
+}
+
+
+void MainWindow::updateAvailable()
+{
+ QToolBar *toolBar = findChild<QToolBar*>("toolBar");
+ foreach (QAction *action, toolBar->actions()) {
+ if (action->text() == tr("Update")) {
+ action->setEnabled(true);
+ action->setToolTip(tr("Update available"));
+ break;
+ }
+ }
+}
+
+
+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_Settings.getMotDHash()) {
+ MotDDialog dialog(motd);
+ dialog.exec();
+ m_Settings.setMotDHash(hash);
+ }
+ }
+
+ ui->actionEndorseMO->setVisible(false);
+}
+
+
+void MainWindow::notEndorsedYet()
+{
+ ui->actionEndorseMO->setVisible(true);
+}
+
+
+void MainWindow::on_dataTree_customContextMenuRequested(const QPoint &pos)
+{
+ QTreeWidget *dataTree = findChild<QTreeWidget*>("dataTree");
+ m_ContextItem = dataTree->itemAt(pos.x(), pos.y());
+
+ QMenu menu;
+ if ((m_ContextItem != NULL) && (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_PreviewGenerator.previewSupported(QFileInfo(fileName).completeSuffix())) {
+ menu.addAction(tr("Preview"), this, SLOT(previewDataFile()));
+ }
+
+ // 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.addSeparator();
+ }
+ menu.addAction(tr("Write To File..."), this, SLOT(writeDataToFile()));
+ menu.addAction(tr("Refresh"), this, SLOT(on_btnRefreshData_clicked()));
+
+ menu.exec(dataTree->mapToGlobal(pos));
+}
+
+void MainWindow::on_conflictsCheckBox_toggled(bool)
+{
+ refreshDataTree();
+}
+
+
+void MainWindow::on_actionUpdate_triggered()
+{
+ if (nexusLogin()) {
+ m_PostLoginTasks.push_back([&](MainWindow*) { m_Updater.startUpdate(); });
+ } else {
+ m_Updater.startUpdate();
+ }
+}
+
+
+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(ToQString(GameInfo::instance().getNexusPage())),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ NexusInterface::instance()->requestToggleEndorsement(GameInfo::instance().getNexusModID(), true, this, QVariant(), QString());
+ }
+}
+
+
+void MainWindow::updateDownloadListDelegate()
+{
+ if (m_Settings.compactDownloads()) {
+ ui->downloadView->setItemDelegate(new DownloadListWidgetCompactDelegate(&m_DownloadManager, m_Settings.metaDownloads(),
+ ui->downloadView, ui->downloadView));
+ } else {
+ ui->downloadView->setItemDelegate(new DownloadListWidgetDelegate(&m_DownloadManager, m_Settings.metaDownloads(),
+ ui->downloadView, ui->downloadView));
+ }
+
+ DownloadListSortProxy *sortProxy = new DownloadListSortProxy(&m_DownloadManager, ui->downloadView);
+ sortProxy->setSourceModel(new DownloadList(&m_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::AscendingOrder);
+ ui->downloadView->header()->resizeSections(QHeaderView::Fixed);
+
+ connect(ui->downloadView->itemDelegate(), SIGNAL(installDownload(int)), this, SLOT(installDownload(int)));
+ connect(ui->downloadView->itemDelegate(), SIGNAL(queryInfo(int)), &m_DownloadManager, SLOT(queryInfo(int)));
+ connect(ui->downloadView->itemDelegate(), SIGNAL(removeDownload(int, bool)), &m_DownloadManager, SLOT(removeDownload(int, bool)));
+ connect(ui->downloadView->itemDelegate(), SIGNAL(restoreDownload(int)), &m_DownloadManager, SLOT(restoreDownload(int)));
+ connect(ui->downloadView->itemDelegate(), SIGNAL(cancelDownload(int)), &m_DownloadManager, SLOT(cancelDownload(int)));
+ connect(ui->downloadView->itemDelegate(), SIGNAL(pauseDownload(int)), &m_DownloadManager, SLOT(pauseDownload(int)));
+ connect(ui->downloadView->itemDelegate(), SIGNAL(resumeDownload(int)), this, SLOT(resumeDownload(int)));
+}
+
+
+void MainWindow::modDetailsUpdated(bool)
+{
+ --m_ModsToUpdate;
+ 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<int> &modIDs, QVariant userData, QVariant resultData, int)
+{
+ m_ModsToUpdate -= modIDs.size();
+ QVariantList resultList = resultData.toList();
+ for (auto iter = resultList.begin(); iter != resultList.end(); ++iter) {
+ QVariantMap result = iter->toMap();
+ if (result["id"].toInt() == GameInfo::instance().getNexusModID()) {
+ if (!result["voted_by_user"].toBool()) {
+ ui->actionEndorseMO->setVisible(true);
+ }
+ } else {
+ std::vector<ModInfo::Ptr> 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::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<ServerInfo> servers;
+ foreach (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_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::loginSuccessful(bool necessary)
+{
+ if (necessary) {
+ MessageDialog::showMessage(tr("login successful"), this);
+ }
+ foreach (QString url, m_PendingDownloads) {
+ downloadRequestedNXM(url);
+ }
+ m_PendingDownloads.clear();
+ foreach (auto task, m_PostLoginTasks) {
+ task(this);
+ }
+
+ m_PostLoginTasks.clear();
+ NexusInterface::instance()->loginCompleted();
+}
+
+
+void MainWindow::loginSuccessfulUpdate(bool necessary)
+{
+ if (necessary) {
+ MessageDialog::showMessage(tr("login successful"), this);
+ }
+ m_Updater.startUpdate();
+}
+
+
+void MainWindow::loginFailed(const QString &message)
+{
+ if (!m_PendingDownloads.isEmpty()) {
+ MessageDialog::showMessage(tr("login failed: %1. Trying to download anyway").arg(message), this);
+ foreach (QString url, m_PendingDownloads) {
+ downloadRequestedNXM(url);
+ }
+ m_PendingDownloads.clear();
+ } else {
+ MessageDialog::showMessage(tr("login failed: %1").arg(message), this);
+ m_PostLoginTasks.clear();
+ statusBar()->hide();
+ }
+ NexusInterface::instance()->loginCompleted();
+}
+
+
+void MainWindow::loginFailedUpdate(const QString &message)
+{
+ MessageDialog::showMessage(tr("login failed: %1. You need to log-in with Nexus to update MO.").arg(message), this);
+}
+
+
+void MainWindow::windowTutorialFinished(const QString &windowName)
+{
+ m_Settings.directInterface().setValue(QString("CompletedWindowTutorials/") + windowName, true);
+}
+
+
+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;
+ }
+ }
+
+ 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;
+}
+
+
+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::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_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;
+ }
+
+ 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::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;
+ foreach (const QAction *action, menu.actions()) {
+ const QWidgetAction *widgetAction = qobject_cast<const QWidgetAction*>(action);
+ if (widgetAction != NULL) {
+ const QCheckBox *checkBox = qobject_cast<const QCheckBox*>(widgetAction->defaultWidget());
+ if (checkBox != NULL) {
+ ui->modList->header()->setSectionHidden(i, !checkBox->isChecked());
+ }
+ }
+ ++i;
+ }
+}
+
+
+void MainWindow::on_bsaList_customContextMenuRequested(const QPoint &pos)
+{
+ m_ContextItem = ui->bsaList->itemAt(pos);
+
+// m_ContextRow = ui->bsaList->indexOfTopLevelItem(ui->bsaList->itemAt(pos));
+
+ QMenu menu;
+ menu.addAction(tr("Extract..."), this, SLOT(extractBSATriggered()));
+
+ menu.exec(ui->bsaList->mapToGlobal(pos));
+}
+
+void MainWindow::bsaList_itemMoved()
+{
+ saveArchiveList();
+ m_CheckBSATimer.start(500);
+}
+
+
+void MainWindow::on_bsaList_itemChanged(QTreeWidgetItem*, int)
+{
+ saveArchiveList();
+ m_CheckBSATimer.start(500);
+}
+
+void MainWindow::on_actionProblems_triggered()
+{
+// QString problemDescription;
+// checkForProblems(problemDescription);
+// QMessageBox::information(this, tr("Problems"), problemDescription);
+ ProblemsDialog problems(m_DiagnosisPlugins, this);
+ if (problems.hasProblems()) {
+ problems.exec();
+ updateProblemsButton();
+ }
+}
+
+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_displayCategoriesBtn_toggled(bool checked)
+{
+ setCategoryListVisible(checked);
+}
+
+void MainWindow::editCategories()
+{
+ CategoriesDialog dialog(this);
+ if (dialog.exec() == QDialog::Accepted) {
+ dialog.commitChanges();
+ }
+}
+
+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()));
+
+ menu.exec(ui->categoriesList->mapToGlobal(pos));
+}
+
+
+void MainWindow::updateESPLock(bool locked)
+{
+ QItemSelection currentSelection = ui->espList->selectionModel()->selection();
+ if (currentSelection.count() == 0) {
+ // this path is probably useless
+ m_PluginList.lockESPIndex(m_ContextRow, locked);
+ } else {
+ Q_FOREACH (const QModelIndex &idx, currentSelection.indexes()) {
+ m_PluginList.lockESPIndex(mapToModel(&m_PluginList, idx).row(), locked);
+ }
+ }
+}
+
+
+void MainWindow::lockESPIndex()
+{
+ updateESPLock(true);
+}
+
+void MainWindow::unlockESPIndex()
+{
+ updateESPLock(false);
+}
+
+
+void MainWindow::removeFromToolbar()
+{
+ try {
+ Executable &exe = m_ExecutablesList.find(m_ContextAction->text());
+ exe.m_Toolbar = false;
+ } catch (const std::runtime_error&) {
+ qDebug("executable doesn't exist any more");
+ }
+
+ updateToolBar();
+}
+
+
+void MainWindow::toolBar_customContextMenuRequested(const QPoint &point)
+{
+ QAction *action = ui->toolBar->actionAt(point);
+ if (action != NULL) {
+ if (action->objectName().startsWith("custom_")) {
+ m_ContextAction = action;
+ QMenu menu;
+ menu.addAction(tr("Remove"), this, SLOT(removeFromToolbar()));
+ menu.exec(ui->toolBar->mapToGlobal(point));
+ }
+ }
+}
+
+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_PluginList, SLOT(enableAll()));
+ menu.addAction(tr("Disable all"), &m_PluginList, SLOT(disableAll()));
+
+ QItemSelection currentSelection = ui->espList->selectionModel()->selection();
+ bool hasLocked = false;
+ bool hasUnlocked = false;
+ Q_FOREACH (const QModelIndex &idx, currentSelection.indexes()) {
+ int row = m_PluginListSortProxy->mapToSource(idx).row();
+ if (m_PluginList.isEnabled(row)) {
+ if (m_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 == NULL) {
+ return;
+ }
+ QAbstractProxyModel *newModel = NULL;
+ switch (index) {
+ case 1: {
+ newModel = new QtGroupingProxy(&m_ModList, QModelIndex(), ModList::COL_CATEGORY, Qt::UserRole,
+ 0, Qt::UserRole + 2);
+ } break;
+ case 2: {
+ newModel = new QtGroupingProxy(&m_ModList, QModelIndex(), ModList::COL_MODID, Qt::DisplayRole,
+ QtGroupingProxy::FLAG_NOGROUPNAME | QtGroupingProxy::FLAG_NOSINGLE,
+ Qt::UserRole + 2);
+ } break;
+ default: {
+ newModel = NULL;
+ } break;
+ }
+
+ if (newModel != NULL) {
+#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_ModList);
+ }
+ modFilterActive(m_ModListSortProxy->isFilterActive());
+}
+
+void MainWindow::on_linkButton_pressed()
+{
+ const Executable &selectedExecutable = ui->executablesListBox->itemData(ui->executablesListBox->currentIndex()).value<Executable>();
+
+ QIcon addIcon(":/MO/gui/link");
+ QIcon removeIcon(":/MO/gui/remove");
+
+ QFileInfo linkDesktopFile(QDir::fromNativeSeparators(getDesktopDirectory()) + "/" + selectedExecutable.m_Title + ".lnk");
+ QFileInfo linkMenuFile(QDir::fromNativeSeparators(getStartMenuDirectory()) + "/" + selectedExecutable.m_Title + ".lnk");
+
+ ui->linkButton->menu()->actions().at(0)->setIcon(selectedExecutable.m_Toolbar ? removeIcon : addIcon);
+ ui->linkButton->menu()->actions().at(1)->setIcon(linkDesktopFile.exists() ? removeIcon : addIcon);
+ ui->linkButton->menu()->actions().at(2)->setIcon(linkMenuFile.exists() ? removeIcon : addIcon);
+}
+
+void MainWindow::on_showHiddenBox_toggled(bool checked)
+{
+ m_DownloadManager.setShowHidden(checked);
+}
+
+
+void MainWindow::createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite)
+{
+ SECURITY_ATTRIBUTES secAttributes;
+ secAttributes.nLength = sizeof(SECURITY_ATTRIBUTES);
+ secAttributes.bInheritHandle = TRUE;
+ secAttributes.lpSecurityDescriptor = NULL;
+
+ 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;
+ }
+}
+
+std::string MainWindow::readFromPipe(HANDLE stdOutRead)
+{
+ static const int chunkSize = 128;
+ std::string result;
+
+ char buffer[chunkSize + 1];
+ buffer[chunkSize] = '\0';
+
+ DWORD read = 1;
+ while (read > 0) {
+ if (!::ReadFile(stdOutRead, buffer, chunkSize, &read, NULL)) {
+ break;
+ }
+ if (read > 0) {
+ result.append(buffer, read);
+ if (read < chunkSize) {
+ break;
+ }
+ }
+ }
+ return result;
+}
+
+void MainWindow::processLOOTOut(const std::string &lootOut, std::string &reportURL, std::string &errorMessages, QProgressDialog &dialog)
+{
+ std::vector<std::string> lines;
+ boost::split(lines, lootOut, boost::is_any_of("\r\n"));
+
+ std::tr1::regex exRequires("\"([^\"]*)\" requires \"([^\"]*)\", but it is missing\\.");
+
+ foreach (const std::string &line, lines) {
+ if (line.length() > 0) {
+ size_t progidx = line.find("[progress]");
+ size_t reportidx = line.find("[report]");
+ size_t erroridx = line.find("[error]");
+ if (progidx != std::string::npos) {
+ dialog.setLabelText(line.substr(progidx + 11).c_str());
+ } else if (reportidx != std::string::npos) {
+ reportURL = line.substr(reportidx + 9);
+ } 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_PluginList.addInformation(modName.c_str(), tr("depends on missing \"%1\"").arg(dependency.c_str()));
+ } else {
+ qDebug("%s", line.c_str());
+ }
+ }
+ }
+ }
+}
+
+
+HANDLE MainWindow::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 != NULL) {
+ profileName = m_CurrentProfile->getName();
+ } else {
+ throw MyException(tr("No profile set"));
+ }
+ }
+ QString steamAppID;
+ if (executable.contains('\\') || executable.contains('/')) {
+ // file path
+ binary = QFileInfo(executable);
+ if (binary.isRelative()) {
+ // relative path, should be relative to game directory
+ binary = QFileInfo(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/" + executable);
+ }
+ if (cwd.length() == 0) {
+ currentDirectory = binary.absolutePath();
+ }
+ } else {
+ // only a file name, search executables list
+ try {
+ const Executable &exe = m_ExecutablesList.find(executable);
+ steamAppID = exe.m_SteamAppID;
+ 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);
+}
+
+void MainWindow::on_bossButton_clicked()
+{
+ std::string reportURL;
+ std::string errorMessages;
+
+ m_CurrentProfile->writeModlistNow();
+
+ bool success = false;
+
+ try {
+ this->setEnabled(false);
+ ON_BLOCK_EXIT([&] () { this->setEnabled(true); });
+ QProgressDialog dialog(this);
+ dialog.setLabelText(tr("LOOT working"));
+ dialog.setMaximum(0);
+ dialog.show();
+
+ QStringList parameters;
+ parameters << "--game" << ToQString(GameInfo::instance().getGameShortName())
+ << "--gamePath" << QString("\"%1\"").arg(ToQString(GameInfo::instance().getGameDirectory()));
+
+ if (!m_DidUpdateMasterList) {
+ parameters << "--updateMasterlist";
+ m_DidUpdateMasterList = true;
+ }
+
+ HANDLE stdOutWrite = INVALID_HANDLE_VALUE;
+ HANDLE stdOutRead = INVALID_HANDLE_VALUE;
+ createStdoutPipe(&stdOutRead, &stdOutWrite);
+ HANDLE loot = startBinary(QFileInfo(qApp->applicationDirPath() + "/loot/lootcli.exe"),
+ parameters.join(" "),
+ m_CurrentProfile->getName(),
+ m_Settings.logLevel(),
+ qApp->applicationDirPath() + "/loot",
+ true,
+ stdOutWrite);
+
+ // we don't use the write end
+ ::CloseHandle(stdOutWrite);
+
+ m_PluginList.clearAdditionalInformation();
+
+ DWORD retLen;
+ JOBOBJECT_BASIC_PROCESS_ID_LIST info;
+ bool isJobHandle = true;
+
+ if (loot != INVALID_HANDLE_VALUE) {
+ DWORD res = ::MsgWaitForMultipleObjects(1, &loot, false, 1000, 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 {
+ // 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);
+ }
+ }
+
+ // keep processing events so the app doesn't appear dead
+ QCoreApplication::processEvents();
+ std::string lootOut = readFromPipe(stdOutRead);
+ processLOOTOut(lootOut, reportURL, errorMessages, dialog);
+
+ res = ::MsgWaitForMultipleObjects(1, &loot, false, 1000, QS_KEY | QS_MOUSE);
+ }
+
+ std::string remainder = readFromPipe(stdOutRead).c_str();
+ if (remainder.length() > 0) {
+ processLOOTOut(remainder, reportURL, errorMessages, dialog);
+ }
+ DWORD exitCode = 0UL;
+ ::GetExitCodeProcess(loot, &exitCode);
+ if (exitCode != 0UL) {
+ reportError(tr("loot failed. Exit code was: %1").arg(exitCode));
+ return;
+ } else {
+ success = true;
+ }
+ } else {
+ 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) {
+ if (reportURL.length() > 0) {
+ m_IntegratedBrowser.setWindowTitle("LOOT Report");
+ QString report(reportURL.c_str());
+ if (QFile::exists(report)) {
+ m_IntegratedBrowser.openUrl(QUrl::fromLocalFile(report));
+ } else {
+ qWarning("report file missing");
+ }
+ }
+ refreshESPList();
+
+ if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) {
+ QFile::remove(m_CurrentProfile->getLoadOrderFileName());
+ }
+ }
+}
+
+
+const char *MainWindow::PATTERN_BACKUP_GLOB = ".????_??_??_??_??_??";
+const char *MainWindow::PATTERN_BACKUP_REGEX = "\\.(\\d\\d\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d_\\d\\d)";
+const char *MainWindow::PATTERN_BACKUP_DATE = "yyyy_MM_dd_hh_mm_ss";
+
+
+bool MainWindow::createBackup(const QString &filePath, const QDateTime &time)
+{
+ 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()
+{
+ savePluginList();
+ QDateTime now = QDateTime::currentDateTime();
+ if (createBackup(m_CurrentProfile->getPluginsFileName(), now)
+ && createBackup(m_CurrentProfile->getLoadOrderFileName(), now)
+ && createBackup(m_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() + "\\.(.*)");
+ foreach(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_CurrentProfile->getPluginsFileName();
+ QString choice = queryRestore(pluginName);
+ if (!choice.isEmpty()) {
+ QString loadOrderName = m_CurrentProfile->getLoadOrderFileName();
+ QString lockedName = m_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())));
+ }
+ refreshESPList();
+ }
+}
+
+void MainWindow::on_saveModsButton_clicked()
+{
+ m_CurrentProfile->writeModlistNow(true);
+ QDateTime now = QDateTime::currentDateTime();
+ if (createBackup(m_CurrentProfile->getModlistFileName(), now)) {
+ MessageDialog::showMessage(tr("Backup of modlist created"), this);
+ }
+}
+void MainWindow::on_restoreModsButton_clicked()
+{
+ QString modlistName = m_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())));
+ }
+ 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::on_manageArchivesBox_toggled(bool)
+{
+ refreshBSAList();
+}
diff --git a/src/mainwindow.h b/src/mainwindow.h index 89e883f9..62503062 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -1,615 +1,615 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef MAINWINDOW_H -#define MAINWINDOW_H - -#include <QMainWindow> -#include <QFileInfo> -#include <QDir> -#include <QTreeWidget> -#include <QListWidgetItem> -#include <QProcess> -#include <QThread> -#include <QProgressBar> -#include <QTranslator> -#include "executableslist.h" -#include "modlist.h" -#include "pluginlist.h" -#define WIN32_LEAN_AND_MEAN -#include <windows.h> -#include <archive.h> -#include "directoryrefresher.h" -#include <imoinfo.h> -#include <iplugintool.h> -#include <iplugindiagnose.h> -#include <ipluginmodpage.h> -#include "settings.h" -#include "downloadmanager.h" -#include "installationmanager.h" -#include "selfupdater.h" -#include "savegamegamebyro.h" -#include "modlistsortproxy.h" -#include "pluginlistsortproxy.h" -#include "tutorialcontrol.h" -#include "savegameinfowidgetgamebryo.h" -#include "previewgenerator.h" -#include "browserdialog.h" -#include <guessedvalue.h> -#include <directoryentry.h> -#include <boost/signals2.hpp> - -namespace Ui { - class MainWindow; -} - -class QToolButton; -class ModListSortProxy; -class ModListGroupCategoriesProxy; - - -class MainWindow : public QMainWindow, public MOBase::IPluginDiagnose -{ - Q_OBJECT - Q_INTERFACES(MOBase::IPluginDiagnose) - - friend class OrganizerProxy; - -private: - - struct SignalCombinerAnd - { - typedef bool result_type; - template<typename InputIterator> - bool operator()(InputIterator first, InputIterator last) const - { - while (first != last) { - if (!(*first)) { - return false; - } - ++first; - } - return true; - } - }; - - typedef boost::signals2::signal<bool (const QString&), SignalCombinerAnd> SignalAboutToRunApplication; - typedef boost::signals2::signal<void (const QString&)> SignalModInstalled; - -public: - explicit MainWindow(const QString &exeName, QSettings &initSettings, QWidget *parent = 0); - ~MainWindow(); - - void readSettings(); - - bool addProfile(); - void refreshBSAList(); - void refreshDataTree(); - void refreshSaveList(); - - void setExecutablesList(const ExecutablesList &executablesList); - - void setModListSorting(int index); - void setESPListSorting(int index); - - bool setCurrentProfile(int index); - bool setCurrentProfile(const QString &name); - - void createFirstProfile(); - -/* void spawnProgram(const QString &fileName, const QString &argumentsArg, - const QString &profileName, const QDir ¤tDirectory);*/ - - void loadPlugins(); - - virtual std::vector<unsigned int> 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; - - void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info); - - bool saveArchiveList(); - - void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite); - std::string readFromPipe(HANDLE stdOutRead); - void processLOOTOut(const std::string &lootOut, std::string &reportURL, std::string &errorMessages, QProgressDialog &dialog); - - HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", const QString &profile = ""); - - void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo); - - void waitForProcessOrJob(HANDLE processHandle); -public slots: - - void refreshLists(); - - void displayColumnSelection(const QPoint &pos); - - void externalMessage(const QString &message); - void modorder_changed(); - void refresher_progress(int percent); - void directory_refreshed(); - - void toolPluginInvoke(); - void modPagePluginInvoke(); - -signals: - - /** - * @brief emitted after a mod has been installed - * @node this is currently only used for tutorials - */ - void modInstalled(); - - /** - * @brief emitted after the information dialog has been closed - */ - void modInfoDisplayed(); - - /** - * @brief emitted when the selected style changes - */ - void styleChanged(const QString &styleFile); - - - 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); - -private: - - void refreshESPList(); - void refreshModList(bool saveChanges = true); - - void actionToToolButton(QAction *&sourceAction); - bool verifyPlugin(MOBase::IPlugin *plugin); - void registerPluginTool(MOBase::IPluginTool *tool); - void registerModPage(MOBase::IPluginModPage *modPage); - bool registerPlugin(QObject *pluginObj, const QString &fileName); - - void updateToolBar(); - void activateSelectedProfile(); - - void setExecutableIndex(int index); - - bool testForSteam(); - void startSteam(); - - HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID); - void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir ¤tDirectory = QDir(), bool closeAfterStart = true, const QString &steamAppID = ""); - - void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly); - void refreshDirectoryStructure(); - bool refreshProfiles(bool selectProfile = true); - void refreshExecutablesList(); - void installMod(); - MOBase::IModInterface *installMod(const QString &fileName); - MOBase::IModInterface *getMod(const QString &name); - MOBase::IModInterface *createMod(MOBase::GuessedValue<QString> &name); - bool removeMod(MOBase::IModInterface *mod); - - QList<MOBase::IOrganizer::FileInfo> findFileInfos(const QString &path, const std::function<bool (const MOBase::IOrganizer::FileInfo &)> &filter) const; - - bool modifyExecutablesDialog(); - void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab); - void displayModInformation(int row, int tab = 0); - void testExtractBSA(int modIndex); - - void writeDataToFile(QFile &file, const QString &directory, const MOShared::DirectoryEntry &directoryEntry); - - void renameModInList(QFile &modList, const QString &oldName, const QString &newName); - - 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 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); - - void updateDownloadListDelegate(); - - // remove invalid category-references from mods - void fixCategories(); - - void storeSettings(); - - bool queryLogin(QString &username, QString &password); - - void createHelpWidget(); - - bool extractProgress(QProgressDialog &extractProgress, int percentage, std::string fileName); - - int checkForProblems(); - - int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments); - QTreeWidgetItem *addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID); - void addCategoryFilters(QTreeWidgetItem *root, const std::set<int> &categoriesUsed, int targetID); - - void setCategoryListVisible(bool visible); - - SaveGameGamebryo *getSaveGame(const QString &name); - SaveGameGamebryo *getSaveGame(QListWidgetItem *item); - - void displaySaveGameInfo(const SaveGameGamebryo *save, QPoint pos); - - HANDLE nextChildProcess(); - - bool errorReported(QString &logFile); - - QIcon iconForExecutable(const QString &filePath); - - void updateESPLock(bool locked); - - static void setupNetworkProxy(bool activate); - void activateProxy(bool activate); - void installTranslator(const QString &name); - void setBrowserGeometry(const QByteArray &geometry); - - bool createBackup(const QString &filePath, const QDateTime &time); - QString queryRestore(const QString &filePath); - - QMenu *modListContextMenu(); - - std::set<QString> enabledArchives(); - - void scheduleUpdateButton(); - - void updateModActiveState(int index, bool active); - -private: - - static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1; - static const unsigned int PROBLEM_TOOMANYPLUGINS = 2; - - static const char *PATTERN_BACKUP_GLOB; - static const char *PATTERN_BACKUP_REGEX; - static const char *PATTERN_BACKUP_DATE; - -private: - - Ui::MainWindow *ui; - - MOBase::TutorialControl m_Tutorial; - - QString m_ExeName; - - int m_OldProfileIndex; - - QThread m_RefresherThread; - DirectoryRefresher m_DirectoryRefresher; - MOShared::DirectoryEntry *m_DirectoryStructure; - std::vector<QString> m_ModNameList; // the mod-list to go with the directory structure - QProgressBar *m_RefreshProgress; - bool m_Refreshing; - - ModList m_ModList; - QAbstractItemModel *m_ModListGroupingProxy; - ModListSortProxy *m_ModListSortProxy; - - PluginList m_PluginList; - PluginListSortProxy *m_PluginListSortProxy; - - ExecutablesList m_ExecutablesList; - int m_OldExecutableIndex; - - QString m_GamePath; - - int m_ContextRow; - QPersistentModelIndex m_ContextIdx; - QTreeWidgetItem *m_ContextItem; - QAction *m_ContextAction; - - //int m_SelectedSaveGame; - - Settings m_Settings; - - DownloadManager m_DownloadManager; - InstallationManager m_InstallationManager; - - SelfUpdater m_Updater; - - CategoryFactory &m_CategoryFactory; - - Profile *m_CurrentProfile; - - int m_ModsToUpdate; - - QStringList m_PendingDownloads; - QList<boost::function<void (MainWindow*)> > m_PostLoginTasks; - bool m_AskForNexusPW; - bool m_LoginAttempted; - - QStringList m_DefaultArchives; - QStringList m_ActiveArchives; - bool m_DirectoryUpdate; - bool m_ArchivesInit; - QTimer m_CheckBSATimer; - QTimer m_SaveMetaTimer; - QTimer m_UpdateProblemsTimer; - - QTime m_StartTime; - SaveGameInfoWidget *m_CurrentSaveView; - - MOBase::IGameInfo *m_GameInfo; - - std::vector<MOBase::IPluginDiagnose*> m_DiagnosisPlugins; - std::vector<MOBase::IPluginModPage*> m_ModPages; - std::vector<QString> m_UnloadedPlugins; - - QFile m_PluginsCheck; - - SignalAboutToRunApplication m_AboutToRun; - SignalModInstalled m_ModInstalled; - - QString m_CurrentLanguage; - std::vector<QTranslator*> m_Translators; - - PreviewGenerator m_PreviewGenerator; - BrowserDialog m_IntegratedBrowser; - - QFileSystemWatcher m_SavesWatcher; - - std::vector<QTreeWidgetItem*> m_RemoveWidget; - - QByteArray m_ArchiveListHash; - - bool m_DidUpdateMasterList; - -private slots: - - void showMessage(const QString &message); - void showError(const QString &message); - - // main window actions - void helpTriggered(); - void issueTriggered(); - void wikiTriggered(); - void tutorialTriggered(); - void extractBSATriggered(); - - // modlist context menu - void installMod_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 openExplorer_clicked(); - void information_clicked(); - // savegame context menu - void deleteSavegame_clicked(); - void fixMods_clicked(); - // data-tree context menu - void writeDataToFile(); - void openDataFile(); - void addAsExecutable(); - void previewDataFile(); - void hideFile(); - void unhideFile(); - - void linkToolbar(); - void linkDesktop(); - void linkMenu(); - - void languageChange(const QString &newLanguage); - void modStatusChanged(unsigned int index); - void saveSelectionChanged(QListWidgetItem *newItem); - - bool saveCurrentLists(); - - void windowTutorialFinished(const QString &windowName); - - BSA::EErrorCode extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, QProgressDialog &extractProgress); - - void syncOverwrite(); - - void createModFromOverwrite(); - - void removeOrigin(const QString &name); - - void procError(QProcess::ProcessError error); - void procFinished(int exitCode, QProcess::ExitStatus exitStatus); - - // nexus related - void checkModsForUpdates(); - void nexusLinkActivated(const QString &link); - - void linkClicked(const QString &url); - - bool nexusLogin(); - - void loginSuccessful(bool necessary); - void loginSuccessfulUpdate(bool necessary); - void loginFailed(const QString &message); - void loginFailedUpdate(const QString &message); - - void downloadRequestedNXM(const QString &url); - void downloadRequested(QNetworkReply *reply, int modID, const QString &fileName); - - void installDownload(int index); - void updateAvailable(); - - void motdReceived(const QString &motd); - void notEndorsedYet(); - - void originModified(int originID); - - void addRemoveCategories_MenuHandler(); - void replaceCategories_MenuHandler(); - - void savePrimaryCategory(); - void addPrimaryCategoryCandidates(); - - void modDetailsUpdated(bool success); - void modlistChanged(int row); - - void nxmUpdatesAvailable(const std::vector<int> &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 displayModInformation(const QString &modName, int tab); - void modOpenNext(); - void modOpenPrev(); - - void modRenamed(const QString &oldName, const QString &newName); - void modRemoved(const QString &fileName); - - void hideSaveGameInfo(); - - void hookUpWindowTutorials(); - - void resumeDownload(int downloadIndex); - void endorseMod(ModInfo::Ptr mod); - void cancelModListEditor(); - - void lockESPIndex(); - void unlockESPIndex(); - - void enableVisibleMods(); - void disableVisibleMods(); - void exportModListCSV(); - - void startExeAction(); - - void checkBSAList(); - - void updateProblemsButton(); - - void saveModMetas(); - - void updateStyle(const QString &style); - - void modlistChanged(const QModelIndex &index, int role); - void fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName); - - void savePluginList(); - - void modFilterActive(bool active); - void espFilterChanged(const QString &filter); - void downloadFilterChanged(const QString &filter); - - void expandModList(const QModelIndex &index); - - /** - * @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(); - - void downloadSpeed(const QString &serverName, int bytesPerSecond); - - void toolBar_customContextMenuRequested(const QPoint &point); - void removeFromToolbar(); - void overwriteClosed(int); - - void changeVersioningScheme(); - void ignoreUpdate(); - void unignoreUpdate(); - - void refreshSavesIfOpen(); - void expandDataTreeItem(QTreeWidgetItem *item); - void about(); - void delayedRemove(); - - void requestDownload(const QUrl &url, QNetworkReply *reply); - void modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous); - void modListSortIndicatorChanged(int column, Qt::SortOrder order); - -private slots: // ui slots - void profileRefresh(); - // 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_bsaList_customContextMenuRequested(const QPoint &pos); - void bsaList_itemMoved(); - 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); - void on_manageArchivesBox_toggled(bool checked); -}; - - - -#endif // MAINWINDOW_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef MAINWINDOW_H
+#define MAINWINDOW_H
+
+#include <QMainWindow>
+#include <QFileInfo>
+#include <QDir>
+#include <QTreeWidget>
+#include <QListWidgetItem>
+#include <QProcess>
+#include <QThread>
+#include <QProgressBar>
+#include <QTranslator>
+#include "executableslist.h"
+#include "modlist.h"
+#include "pluginlist.h"
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <archive.h>
+#include "directoryrefresher.h"
+#include <imoinfo.h>
+#include <iplugintool.h>
+#include <iplugindiagnose.h>
+#include <ipluginmodpage.h>
+#include "settings.h"
+#include "downloadmanager.h"
+#include "installationmanager.h"
+#include "selfupdater.h"
+#include "savegamegamebyro.h"
+#include "modlistsortproxy.h"
+#include "pluginlistsortproxy.h"
+#include "tutorialcontrol.h"
+#include "savegameinfowidgetgamebryo.h"
+#include "previewgenerator.h"
+#include "browserdialog.h"
+#include <guessedvalue.h>
+#include <directoryentry.h>
+#include <boost/signals2.hpp>
+
+namespace Ui {
+ class MainWindow;
+}
+
+class QToolButton;
+class ModListSortProxy;
+class ModListGroupCategoriesProxy;
+
+
+class MainWindow : public QMainWindow, public MOBase::IPluginDiagnose
+{
+ Q_OBJECT
+ Q_INTERFACES(MOBase::IPluginDiagnose)
+
+ friend class OrganizerProxy;
+
+private:
+
+ struct SignalCombinerAnd
+ {
+ typedef bool result_type;
+ template<typename InputIterator>
+ bool operator()(InputIterator first, InputIterator last) const
+ {
+ while (first != last) {
+ if (!(*first)) {
+ return false;
+ }
+ ++first;
+ }
+ return true;
+ }
+ };
+
+ typedef boost::signals2::signal<bool (const QString&), SignalCombinerAnd> SignalAboutToRunApplication;
+ typedef boost::signals2::signal<void (const QString&)> SignalModInstalled;
+
+public:
+ explicit MainWindow(const QString &exeName, QSettings &initSettings, QWidget *parent = 0);
+ ~MainWindow();
+
+ void readSettings();
+
+ bool addProfile();
+ void refreshBSAList();
+ void refreshDataTree();
+ void refreshSaveList();
+
+ void setExecutablesList(const ExecutablesList &executablesList);
+
+ void setModListSorting(int index);
+ void setESPListSorting(int index);
+
+ bool setCurrentProfile(int index);
+ bool setCurrentProfile(const QString &name);
+
+ void createFirstProfile();
+
+/* void spawnProgram(const QString &fileName, const QString &argumentsArg,
+ const QString &profileName, const QDir ¤tDirectory);*/
+
+ void loadPlugins();
+
+ virtual std::vector<unsigned int> 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;
+
+ void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info);
+
+ bool saveArchiveList();
+
+ void createStdoutPipe(HANDLE *stdOutRead, HANDLE *stdOutWrite);
+ std::string readFromPipe(HANDLE stdOutRead);
+ void processLOOTOut(const std::string &lootOut, std::string &reportURL, std::string &errorMessages, QProgressDialog &dialog);
+
+ HANDLE startApplication(const QString &executable, const QStringList &args = QStringList(), const QString &cwd = "", const QString &profile = "");
+
+ void updateModInDirectoryStructure(unsigned int index, ModInfo::Ptr modInfo);
+
+ void waitForProcessOrJob(HANDLE processHandle);
+public slots:
+
+ void refreshLists();
+
+ void displayColumnSelection(const QPoint &pos);
+
+ void externalMessage(const QString &message);
+ void modorder_changed();
+ void refresher_progress(int percent);
+ void directory_refreshed();
+
+ void toolPluginInvoke();
+ void modPagePluginInvoke();
+
+signals:
+
+ /**
+ * @brief emitted after a mod has been installed
+ * @node this is currently only used for tutorials
+ */
+ void modInstalled();
+
+ /**
+ * @brief emitted after the information dialog has been closed
+ */
+ void modInfoDisplayed();
+
+ /**
+ * @brief emitted when the selected style changes
+ */
+ void styleChanged(const QString &styleFile);
+
+
+ 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);
+
+private:
+
+ void refreshESPList();
+ void refreshModList(bool saveChanges = true);
+
+ void actionToToolButton(QAction *&sourceAction);
+ bool verifyPlugin(MOBase::IPlugin *plugin);
+ void registerPluginTool(MOBase::IPluginTool *tool);
+ void registerModPage(MOBase::IPluginModPage *modPage);
+ bool registerPlugin(QObject *pluginObj, const QString &fileName);
+
+ void updateToolBar();
+ void activateSelectedProfile();
+
+ void setExecutableIndex(int index);
+
+ bool testForSteam();
+ void startSteam();
+
+ HANDLE spawnBinaryDirect(const QFileInfo &binary, const QString &arguments, const QString &profileName, const QDir ¤tDirectory, const QString &steamAppID);
+ void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir ¤tDirectory = QDir(), bool closeAfterStart = true, const QString &steamAppID = "");
+
+ void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const MOShared::DirectoryEntry &directoryEntry, bool conflictsOnly);
+ void refreshDirectoryStructure();
+ bool refreshProfiles(bool selectProfile = true);
+ void refreshExecutablesList();
+ void installMod();
+ MOBase::IModInterface *installMod(const QString &fileName);
+ MOBase::IModInterface *getMod(const QString &name);
+ MOBase::IModInterface *createMod(MOBase::GuessedValue<QString> &name);
+ bool removeMod(MOBase::IModInterface *mod);
+
+ QList<MOBase::IOrganizer::FileInfo> findFileInfos(const QString &path, const std::function<bool (const MOBase::IOrganizer::FileInfo &)> &filter) const;
+
+ bool modifyExecutablesDialog();
+ void displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab);
+ void displayModInformation(int row, int tab = 0);
+ void testExtractBSA(int modIndex);
+
+ void writeDataToFile(QFile &file, const QString &directory, const MOShared::DirectoryEntry &directoryEntry);
+
+ void renameModInList(QFile &modList, const QString &oldName, const QString &newName);
+
+ 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 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);
+
+ void updateDownloadListDelegate();
+
+ // remove invalid category-references from mods
+ void fixCategories();
+
+ void storeSettings();
+
+ bool queryLogin(QString &username, QString &password);
+
+ void createHelpWidget();
+
+ bool extractProgress(QProgressDialog &extractProgress, int percentage, std::string fileName);
+
+ int checkForProblems();
+
+ int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments);
+ QTreeWidgetItem *addFilterItem(QTreeWidgetItem *root, const QString &name, int categoryID);
+ void addCategoryFilters(QTreeWidgetItem *root, const std::set<int> &categoriesUsed, int targetID);
+
+ void setCategoryListVisible(bool visible);
+
+ SaveGameGamebryo *getSaveGame(const QString &name);
+ SaveGameGamebryo *getSaveGame(QListWidgetItem *item);
+
+ void displaySaveGameInfo(const SaveGameGamebryo *save, QPoint pos);
+
+ HANDLE nextChildProcess();
+
+ bool errorReported(QString &logFile);
+
+ QIcon iconForExecutable(const QString &filePath);
+
+ void updateESPLock(bool locked);
+
+ static void setupNetworkProxy(bool activate);
+ void activateProxy(bool activate);
+ void installTranslator(const QString &name);
+ void setBrowserGeometry(const QByteArray &geometry);
+
+ bool createBackup(const QString &filePath, const QDateTime &time);
+ QString queryRestore(const QString &filePath);
+
+ QMenu *modListContextMenu();
+
+ std::set<QString> enabledArchives();
+
+ void scheduleUpdateButton();
+
+ void updateModActiveState(int index, bool active);
+
+private:
+
+ static const unsigned int PROBLEM_PLUGINSNOTLOADED = 1;
+ static const unsigned int PROBLEM_TOOMANYPLUGINS = 2;
+
+ static const char *PATTERN_BACKUP_GLOB;
+ static const char *PATTERN_BACKUP_REGEX;
+ static const char *PATTERN_BACKUP_DATE;
+
+private:
+
+ Ui::MainWindow *ui;
+
+ MOBase::TutorialControl m_Tutorial;
+
+ QString m_ExeName;
+
+ int m_OldProfileIndex;
+
+ QThread m_RefresherThread;
+ DirectoryRefresher m_DirectoryRefresher;
+ MOShared::DirectoryEntry *m_DirectoryStructure;
+ std::vector<QString> m_ModNameList; // the mod-list to go with the directory structure
+ QProgressBar *m_RefreshProgress;
+ bool m_Refreshing;
+
+ ModList m_ModList;
+ QAbstractItemModel *m_ModListGroupingProxy;
+ ModListSortProxy *m_ModListSortProxy;
+
+ PluginList m_PluginList;
+ PluginListSortProxy *m_PluginListSortProxy;
+
+ ExecutablesList m_ExecutablesList;
+ int m_OldExecutableIndex;
+
+ QString m_GamePath;
+
+ int m_ContextRow;
+ QPersistentModelIndex m_ContextIdx;
+ QTreeWidgetItem *m_ContextItem;
+ QAction *m_ContextAction;
+
+ //int m_SelectedSaveGame;
+
+ Settings m_Settings;
+
+ DownloadManager m_DownloadManager;
+ InstallationManager m_InstallationManager;
+
+ SelfUpdater m_Updater;
+
+ CategoryFactory &m_CategoryFactory;
+
+ Profile *m_CurrentProfile;
+
+ int m_ModsToUpdate;
+
+ QStringList m_PendingDownloads;
+ QList<boost::function<void (MainWindow*)> > m_PostLoginTasks;
+ bool m_AskForNexusPW;
+ bool m_LoginAttempted;
+
+ QStringList m_DefaultArchives;
+ QStringList m_ActiveArchives;
+ bool m_DirectoryUpdate;
+ bool m_ArchivesInit;
+ QTimer m_CheckBSATimer;
+ QTimer m_SaveMetaTimer;
+ QTimer m_UpdateProblemsTimer;
+
+ QTime m_StartTime;
+ SaveGameInfoWidget *m_CurrentSaveView;
+
+ MOBase::IGameInfo *m_GameInfo;
+
+ std::vector<MOBase::IPluginDiagnose*> m_DiagnosisPlugins;
+ std::vector<MOBase::IPluginModPage*> m_ModPages;
+ std::vector<QString> m_UnloadedPlugins;
+
+ QFile m_PluginsCheck;
+
+ SignalAboutToRunApplication m_AboutToRun;
+ SignalModInstalled m_ModInstalled;
+
+ QString m_CurrentLanguage;
+ std::vector<QTranslator*> m_Translators;
+
+ PreviewGenerator m_PreviewGenerator;
+ BrowserDialog m_IntegratedBrowser;
+
+ QFileSystemWatcher m_SavesWatcher;
+
+ std::vector<QTreeWidgetItem*> m_RemoveWidget;
+
+ QByteArray m_ArchiveListHash;
+
+ bool m_DidUpdateMasterList;
+
+private slots:
+
+ void showMessage(const QString &message);
+ void showError(const QString &message);
+
+ // main window actions
+ void helpTriggered();
+ void issueTriggered();
+ void wikiTriggered();
+ void tutorialTriggered();
+ void extractBSATriggered();
+
+ // modlist context menu
+ void installMod_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 openExplorer_clicked();
+ void information_clicked();
+ // savegame context menu
+ void deleteSavegame_clicked();
+ void fixMods_clicked();
+ // data-tree context menu
+ void writeDataToFile();
+ void openDataFile();
+ void addAsExecutable();
+ void previewDataFile();
+ void hideFile();
+ void unhideFile();
+
+ void linkToolbar();
+ void linkDesktop();
+ void linkMenu();
+
+ void languageChange(const QString &newLanguage);
+ void modStatusChanged(unsigned int index);
+ void saveSelectionChanged(QListWidgetItem *newItem);
+
+ bool saveCurrentLists();
+
+ void windowTutorialFinished(const QString &windowName);
+
+ BSA::EErrorCode extractBSA(BSA::Archive &archive, BSA::Folder::Ptr folder, const QString &destination, QProgressDialog &extractProgress);
+
+ void syncOverwrite();
+
+ void createModFromOverwrite();
+
+ void removeOrigin(const QString &name);
+
+ void procError(QProcess::ProcessError error);
+ void procFinished(int exitCode, QProcess::ExitStatus exitStatus);
+
+ // nexus related
+ void checkModsForUpdates();
+ void nexusLinkActivated(const QString &link);
+
+ void linkClicked(const QString &url);
+
+ bool nexusLogin();
+
+ void loginSuccessful(bool necessary);
+ void loginSuccessfulUpdate(bool necessary);
+ void loginFailed(const QString &message);
+ void loginFailedUpdate(const QString &message);
+
+ void downloadRequestedNXM(const QString &url);
+ void downloadRequested(QNetworkReply *reply, int modID, const QString &fileName);
+
+ void installDownload(int index);
+ void updateAvailable();
+
+ void motdReceived(const QString &motd);
+ void notEndorsedYet();
+
+ void originModified(int originID);
+
+ void addRemoveCategories_MenuHandler();
+ void replaceCategories_MenuHandler();
+
+ void savePrimaryCategory();
+ void addPrimaryCategoryCandidates();
+
+ void modDetailsUpdated(bool success);
+ void modlistChanged(int row);
+
+ void nxmUpdatesAvailable(const std::vector<int> &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 displayModInformation(const QString &modName, int tab);
+ void modOpenNext();
+ void modOpenPrev();
+
+ void modRenamed(const QString &oldName, const QString &newName);
+ void modRemoved(const QString &fileName);
+
+ void hideSaveGameInfo();
+
+ void hookUpWindowTutorials();
+
+ void resumeDownload(int downloadIndex);
+ void endorseMod(ModInfo::Ptr mod);
+ void cancelModListEditor();
+
+ void lockESPIndex();
+ void unlockESPIndex();
+
+ void enableVisibleMods();
+ void disableVisibleMods();
+ void exportModListCSV();
+
+ void startExeAction();
+
+ void checkBSAList();
+
+ void updateProblemsButton();
+
+ void saveModMetas();
+
+ void updateStyle(const QString &style);
+
+ void modlistChanged(const QModelIndex &index, int role);
+ void fileMoved(const QString &filePath, const QString &oldOriginName, const QString &newOriginName);
+
+ void savePluginList();
+
+ void modFilterActive(bool active);
+ void espFilterChanged(const QString &filter);
+ void downloadFilterChanged(const QString &filter);
+
+ void expandModList(const QModelIndex &index);
+
+ /**
+ * @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();
+
+ void downloadSpeed(const QString &serverName, int bytesPerSecond);
+
+ void toolBar_customContextMenuRequested(const QPoint &point);
+ void removeFromToolbar();
+ void overwriteClosed(int);
+
+ void changeVersioningScheme();
+ void ignoreUpdate();
+ void unignoreUpdate();
+
+ void refreshSavesIfOpen();
+ void expandDataTreeItem(QTreeWidgetItem *item);
+ void about();
+ void delayedRemove();
+
+ void requestDownload(const QUrl &url, QNetworkReply *reply);
+ void modlistSelectionChanged(const QModelIndex ¤t, const QModelIndex &previous);
+ void modListSortIndicatorChanged(int column, Qt::SortOrder order);
+
+private slots: // ui slots
+ void profileRefresh();
+ // 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_bsaList_customContextMenuRequested(const QPoint &pos);
+ void bsaList_itemMoved();
+ 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);
+ void on_manageArchivesBox_toggled(bool checked);
+};
+
+
+
+#endif // MAINWINDOW_H
diff --git a/src/messagedialog.cpp b/src/messagedialog.cpp index 2aa07ccf..863d7628 100644 --- a/src/messagedialog.cpp +++ b/src/messagedialog.cpp @@ -1,92 +1,92 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "messagedialog.h" -#include "ui_messagedialog.h" -#include <QTimer> -#include <QResizeEvent> -#include <Windows.h> - -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 += "<span style=\"nobreak\">" + metrics.elidedText(word, Qt::ElideMiddle, ui->message->maximumWidth()) + "</span>"; - } 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 != NULL) { - 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 != NULL) { - 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 != NULL) { - if (bringToFront || (qApp->activeWindow() != NULL)) { - MessageDialog *dialog = new MessageDialog(text, reference); - dialog->show(); - reference->activateWindow(); - } - } -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "messagedialog.h"
+#include "ui_messagedialog.h"
+#include <QTimer>
+#include <QResizeEvent>
+#include <Windows.h>
+
+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 += "<span style=\"nobreak\">" + metrics.elidedText(word, Qt::ElideMiddle, ui->message->maximumWidth()) + "</span>";
+ } 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 != NULL) {
+ 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 != NULL) {
+ 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 != NULL) {
+ if (bringToFront || (qApp->activeWindow() != NULL)) {
+ MessageDialog *dialog = new MessageDialog(text, reference);
+ dialog->show();
+ reference->activateWindow();
+ }
+ }
+}
diff --git a/src/messagedialog.h b/src/messagedialog.h index 0dbc0e6e..7e9b9c3b 100644 --- a/src/messagedialog.h +++ b/src/messagedialog.h @@ -1,67 +1,67 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef MESSAGEDIALOG_H -#define MESSAGEDIALOG_H - -#include <QDialog> - -namespace Ui { - class MessageDialog; -} - -/** - * borderless dialog used to display short messages that will automatically - * vanish after a moment - **/ -class MessageDialog : public QDialog -{ - Q_OBJECT - -public: - /** - * @brief constructor - * - * @param text the message to display - * @param reference parent widget. This will also be used to position the message at the bottom center of the dialog - **/ - - explicit MessageDialog(const QString &text, QWidget *reference); - - ~MessageDialog(); - - /** - * factory function for message dialogs. This can be used as a fire-and-forget. The message - * will automatically positioned to the reference dialog and get a reasonable view time - * - * @param text the text to display. The length of this text is used to determine how long the dialog is to be shown - * @param reference the reference widget on top of which the message should be displayed - * @param true if the message should bring MO to front to ensure this message is visible - **/ - static void showMessage(const QString &text, QWidget *reference, bool bringToFront = true); - -protected: - - virtual void resizeEvent(QResizeEvent *event); - -private: - Ui::MessageDialog *ui; -}; - -#endif // MESSAGEDIALOG_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef MESSAGEDIALOG_H
+#define MESSAGEDIALOG_H
+
+#include <QDialog>
+
+namespace Ui {
+ class MessageDialog;
+}
+
+/**
+ * borderless dialog used to display short messages that will automatically
+ * vanish after a moment
+ **/
+class MessageDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ /**
+ * @brief constructor
+ *
+ * @param text the message to display
+ * @param reference parent widget. This will also be used to position the message at the bottom center of the dialog
+ **/
+
+ explicit MessageDialog(const QString &text, QWidget *reference);
+
+ ~MessageDialog();
+
+ /**
+ * factory function for message dialogs. This can be used as a fire-and-forget. The message
+ * will automatically positioned to the reference dialog and get a reasonable view time
+ *
+ * @param text the text to display. The length of this text is used to determine how long the dialog is to be shown
+ * @param reference the reference widget on top of which the message should be displayed
+ * @param true if the message should bring MO to front to ensure this message is visible
+ **/
+ static void showMessage(const QString &text, QWidget *reference, bool bringToFront = true);
+
+protected:
+
+ virtual void resizeEvent(QResizeEvent *event);
+
+private:
+ Ui::MessageDialog *ui;
+};
+
+#endif // MESSAGEDIALOG_H
diff --git a/src/moapplication.cpp b/src/moapplication.cpp index 12ff713b..df0fdd09 100644 --- a/src/moapplication.cpp +++ b/src/moapplication.cpp @@ -1,148 +1,148 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "moapplication.h" -#include "report.h" -#include <utility.h> -#include <appconfig.h> -#include <QFile> -#include <QStringList> -#if QT_VERSION < QT_VERSION_CHECK(5,0,0) -#include <QPlastiqueStyle> -#include <QCleanlooksStyle> -#endif -#include <QProxyStyle> -#include <QStyleFactory> -#include <QPainter> -#include <QStyleOption> - - -#include <QDebug> - - -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); - } - } -}; - - -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); - } - } else { - 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; - } -} - - -void MOApplication::updateStyle(const QString &fileName) -{ -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - if (fileName == "Fusion") { - setStyle(QStyleFactory::create("fusion")); - setStyleSheet(""); -#else - if (fileName == "Plastique") { - setStyle(new ProxyStyle(new QPlastiqueStyle)); - setStyleSheet(""); - } else if (fileName == "Cleanlooks") { - setStyle(new ProxyStyle(new QCleanlooksStyle)); - setStyleSheet(""); -#endif - } 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)); - } - } -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "moapplication.h"
+#include "report.h"
+#include <utility.h>
+#include <appconfig.h>
+#include <QFile>
+#include <QStringList>
+#if QT_VERSION < QT_VERSION_CHECK(5,0,0)
+#include <QPlastiqueStyle>
+#include <QCleanlooksStyle>
+#endif
+#include <QProxyStyle>
+#include <QStyleFactory>
+#include <QPainter>
+#include <QStyleOption>
+
+
+#include <QDebug>
+
+
+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);
+ }
+ }
+};
+
+
+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);
+ }
+ } else {
+ 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;
+ }
+}
+
+
+void MOApplication::updateStyle(const QString &fileName)
+{
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+ if (fileName == "Fusion") {
+ setStyle(QStyleFactory::create("fusion"));
+ setStyleSheet("");
+#else
+ if (fileName == "Plastique") {
+ setStyle(new ProxyStyle(new QPlastiqueStyle));
+ setStyleSheet("");
+ } else if (fileName == "Cleanlooks") {
+ setStyle(new ProxyStyle(new QCleanlooksStyle));
+ setStyleSheet("");
+#endif
+ } 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));
+ }
+ }
+}
diff --git a/src/moapplication.h b/src/moapplication.h index 645b9144..cf6bc625 100644 --- a/src/moapplication.h +++ b/src/moapplication.h @@ -1,51 +1,51 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef MOAPPLICATION_H -#define MOAPPLICATION_H - -#include <QApplication> -#include <QFileSystemWatcher> - - -class MOApplication : public QApplication { -Q_OBJECT -public: - - MOApplication(int argc, char **argv); - - virtual bool notify (QObject *receiver, QEvent *event); - -public slots: - - bool setStyleFile(const QString &style); - -private slots: - - void updateStyle(const QString &fileName); - -private: - - QFileSystemWatcher m_StyleWatcher; - QString m_DefaultStyle; - -}; - - -#endif // MOAPPLICATION_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef MOAPPLICATION_H
+#define MOAPPLICATION_H
+
+#include <QApplication>
+#include <QFileSystemWatcher>
+
+
+class MOApplication : public QApplication {
+Q_OBJECT
+public:
+
+ MOApplication(int argc, char **argv);
+
+ virtual bool notify (QObject *receiver, QEvent *event);
+
+public slots:
+
+ bool setStyleFile(const QString &style);
+
+private slots:
+
+ void updateStyle(const QString &fileName);
+
+private:
+
+ QFileSystemWatcher m_StyleWatcher;
+ QString m_DefaultStyle;
+
+};
+
+
+#endif // MOAPPLICATION_H
diff --git a/src/modeltest.cpp b/src/modeltest.cpp index b2e495b6..90061261 100644 --- a/src/modeltest.cpp +++ b/src/modeltest.cpp @@ -1,583 +1,583 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#include <QtGui/QtGui> - -#include "modeltest.h" - -#include <QtTest/QtTest> - -#undef QVERIFY -#define QVERIFY Q_ASSERT -#undef QCOMPARE -#define QCOMPARE(x, y) Q_ASSERT(x == y) - -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 ) -{ - 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() ) ); - - // 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 ) ) ); - - runAllTests(); -} - -void ModelTest::runAllTests() -{ - if ( fetchingMore ) - return; - nonDestructiveBasicTest(); - rowCount(); - columnCount(); - hasIndex(); - index(); - parent(); - data(); -} - -/*! - 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() ); - fetchingMore = true; - 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() ); - QVariant cache; - model->match ( QModelIndex(), -1, cache ); - model->mimeTypes(); - 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() ); - QMap<int, QVariant> roles; - model->sibling ( 0, 0, QModelIndex() ); - model->span ( QModelIndex() ); - model->supportedDropActions(); -} - -/*! - Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren() - - Models that are dynamically populated are not as fully tested here. - */ -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; - } - - 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 - // check a row count where parent is valid - rows = model->rowCount ( secondLevelIndex ); - QVERIFY( rows >= 0 ); - if ( rows > 0 ) - QVERIFY( model->hasChildren ( secondLevelIndex ) ); - } - - // The models rowCount() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren() - */ -void ModelTest::columnCount() -{ - // check top row - 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 ); - - // columnCount() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::hasIndex() - */ -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 ) ); - - int rows = model->rowCount(); - int columns = model->columnCount(); - - // check out of bounds - QVERIFY( !model->hasIndex ( rows, columns ) ); - QVERIFY( !model->hasIndex ( rows + 1, columns + 1 ) ); - - if ( rows > 0 ) - QVERIFY( model->hasIndex ( 0, 0 ) ); - - // hasIndex() is tested more extensively in checkChildren(), - // but this catches the big mistakes -} - -/*! - Tests model's implementation of QAbstractItemModel::index() - */ -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() ); - - int rows = model->rowCount(); - int columns = model->columnCount(); - - 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() ); - - // Make sure that the same index is *always* returned - 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 -} - -/*! - Tests model's implementation of QAbstractItemModel::parent() - */ -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() ); - - if ( model->rowCount() == 0 ) - return; - - // Column 0 | Column 1 | - // QModelIndex() | | - // \- topIndex | topIndex1 | - // \- childIndex | childIndex1 | - - // 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() ); - - // 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 ); - } - - // 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 ); - } - - // Full test, walk n levels deep through the model making sure that all - // parent's children correctly specify their parent. - checkChildren ( QModelIndex() ); -} - -/*! - Called from the parent() test. - - A model that returns an index of parent X should also return X when asking - for the parent of the index. - - This recursive function does pretty extensive testing on the whole model in an - effort to catch edge cases. - - This function assumes that rowCount(), columnCount() and index() already work. - If they have a bug it will point it out, but the above tests should have already - 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 ) -{ - // First just try walking back up the tree. - QModelIndex p = parent; - while ( p.isValid() ) - p = p.parent(); - - // For models that are dynamically populated - if ( model->canFetchMore ( parent ) ) { - fetchingMore = true; - model->fetchMore ( parent ); - fetchingMore = false; - } - - int rows = model->rowCount ( parent ); - int columns = model->columnCount ( 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 ) ); - - //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 ) ) { - fetchingMore = true; - 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 ); - // rowCount() and columnCount() said that it existed... - 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 ); - - // 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 ); - - // Some basic checking on the index that is returned - 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() ); - - // 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() << index << parent << model->parent(index); -// 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 ); - - // 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"; };*/ - - // make sure that after testing the children that the index doesn't change. - QModelIndex newerIndex = model->index ( r, c, parent ); - QVERIFY( index == newerIndex ); - } - } -} - -/*! - Tests model's implementation of QAbstractItemModel::data() - */ -void ModelTest::data() -{ - // Invalid index should return an invalid qvariant - QVERIFY( !model->data ( QModelIndex() ).isValid() ); - - if ( model->rowCount() == 0 ) - return; - - // A valid index should have a valid QVariant data - 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 ) ); - - // 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) ); - } - 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) ); - } - - // 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) ); - } - - // 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) ); - } - - // Check that the alignment is one we know about - 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 ) ) ); - } - - // 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) ); - } - - 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() ) { - int state = checkStateVariant.toInt(); - QVERIFY( state == Qt::Unchecked || - state == Qt::PartiallyChecked || - state == Qt::Checked ); - } -} - -/*! - Store what is about to be inserted to make sure it actually happens - - \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) ); - 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 ); -} - -/*! - Confirm that what was said was going to happen actually did - - \sa rowsAboutToBeInserted() - */ -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 ); - -// 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 ) ) ); - - if (c.next != model->data(model->index(end + 1, 0, c.parent))) { - qDebug() << start << end; - 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 ) ) ); -} - -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 ) { - QPersistentModelIndex p = changing[i]; - QVERIFY( p == model->index ( p.row(), p.column(), p.parent() ) ); - } - changing.clear(); -} - -/*! - Store what is about to be inserted to make sure it actually happens - - \sa rowsRemoved() - */ -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 ); -} - -/*! - Confirm that what was said was going to happen actually did - - \sa rowsAboutToBeRemoved() - */ -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 ) ) ); -} - - +/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#include <QtGui/QtGui>
+
+#include "modeltest.h"
+
+#include <QtTest/QtTest>
+
+#undef QVERIFY
+#define QVERIFY Q_ASSERT
+#undef QCOMPARE
+#define QCOMPARE(x, y) Q_ASSERT(x == y)
+
+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 )
+{
+ 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() ) );
+
+ // 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 ) ) );
+
+ runAllTests();
+}
+
+void ModelTest::runAllTests()
+{
+ if ( fetchingMore )
+ return;
+ nonDestructiveBasicTest();
+ rowCount();
+ columnCount();
+ hasIndex();
+ index();
+ parent();
+ data();
+}
+
+/*!
+ 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() );
+ fetchingMore = true;
+ 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() );
+ QVariant cache;
+ model->match ( QModelIndex(), -1, cache );
+ model->mimeTypes();
+ 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() );
+ QMap<int, QVariant> roles;
+ model->sibling ( 0, 0, QModelIndex() );
+ model->span ( QModelIndex() );
+ model->supportedDropActions();
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren()
+
+ Models that are dynamically populated are not as fully tested here.
+ */
+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;
+ }
+
+ 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
+ // check a row count where parent is valid
+ rows = model->rowCount ( secondLevelIndex );
+ QVERIFY( rows >= 0 );
+ if ( rows > 0 )
+ QVERIFY( model->hasChildren ( secondLevelIndex ) );
+ }
+
+ // The models rowCount() is tested more extensively in checkChildren(),
+ // but this catches the big mistakes
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren()
+ */
+void ModelTest::columnCount()
+{
+ // check top row
+ 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 );
+
+ // columnCount() is tested more extensively in checkChildren(),
+ // but this catches the big mistakes
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::hasIndex()
+ */
+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 ) );
+
+ int rows = model->rowCount();
+ int columns = model->columnCount();
+
+ // check out of bounds
+ QVERIFY( !model->hasIndex ( rows, columns ) );
+ QVERIFY( !model->hasIndex ( rows + 1, columns + 1 ) );
+
+ if ( rows > 0 )
+ QVERIFY( model->hasIndex ( 0, 0 ) );
+
+ // hasIndex() is tested more extensively in checkChildren(),
+ // but this catches the big mistakes
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::index()
+ */
+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() );
+
+ int rows = model->rowCount();
+ int columns = model->columnCount();
+
+ 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() );
+
+ // Make sure that the same index is *always* returned
+ 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
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::parent()
+ */
+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() );
+
+ if ( model->rowCount() == 0 )
+ return;
+
+ // Column 0 | Column 1 |
+ // QModelIndex() | |
+ // \- topIndex | topIndex1 |
+ // \- childIndex | childIndex1 |
+
+ // 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() );
+
+ // 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 );
+ }
+
+ // 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 );
+ }
+
+ // Full test, walk n levels deep through the model making sure that all
+ // parent's children correctly specify their parent.
+ checkChildren ( QModelIndex() );
+}
+
+/*!
+ Called from the parent() test.
+
+ A model that returns an index of parent X should also return X when asking
+ for the parent of the index.
+
+ This recursive function does pretty extensive testing on the whole model in an
+ effort to catch edge cases.
+
+ This function assumes that rowCount(), columnCount() and index() already work.
+ If they have a bug it will point it out, but the above tests should have already
+ 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 )
+{
+ // First just try walking back up the tree.
+ QModelIndex p = parent;
+ while ( p.isValid() )
+ p = p.parent();
+
+ // For models that are dynamically populated
+ if ( model->canFetchMore ( parent ) ) {
+ fetchingMore = true;
+ model->fetchMore ( parent );
+ fetchingMore = false;
+ }
+
+ int rows = model->rowCount ( parent );
+ int columns = model->columnCount ( 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 ) );
+
+ //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 ) ) {
+ fetchingMore = true;
+ 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 );
+ // rowCount() and columnCount() said that it existed...
+ 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 );
+
+ // 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 );
+
+ // Some basic checking on the index that is returned
+ 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() );
+
+ // 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() << index << parent << model->parent(index);
+// 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 );
+
+ // 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"; };*/
+
+ // make sure that after testing the children that the index doesn't change.
+ QModelIndex newerIndex = model->index ( r, c, parent );
+ QVERIFY( index == newerIndex );
+ }
+ }
+}
+
+/*!
+ Tests model's implementation of QAbstractItemModel::data()
+ */
+void ModelTest::data()
+{
+ // Invalid index should return an invalid qvariant
+ QVERIFY( !model->data ( QModelIndex() ).isValid() );
+
+ if ( model->rowCount() == 0 )
+ return;
+
+ // A valid index should have a valid QVariant data
+ 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 ) );
+
+ // 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) );
+ }
+ 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) );
+ }
+
+ // 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) );
+ }
+
+ // 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) );
+ }
+
+ // Check that the alignment is one we know about
+ 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 ) ) );
+ }
+
+ // 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) );
+ }
+
+ 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() ) {
+ int state = checkStateVariant.toInt();
+ QVERIFY( state == Qt::Unchecked ||
+ state == Qt::PartiallyChecked ||
+ state == Qt::Checked );
+ }
+}
+
+/*!
+ Store what is about to be inserted to make sure it actually happens
+
+ \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) );
+ 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 );
+}
+
+/*!
+ Confirm that what was said was going to happen actually did
+
+ \sa rowsAboutToBeInserted()
+ */
+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 );
+
+// 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 ) ) );
+
+ if (c.next != model->data(model->index(end + 1, 0, c.parent))) {
+ qDebug() << start << end;
+ 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 ) ) );
+}
+
+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 ) {
+ QPersistentModelIndex p = changing[i];
+ QVERIFY( p == model->index ( p.row(), p.column(), p.parent() ) );
+ }
+ changing.clear();
+}
+
+/*!
+ Store what is about to be inserted to make sure it actually happens
+
+ \sa rowsRemoved()
+ */
+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 );
+}
+
+/*!
+ Confirm that what was said was going to happen actually did
+
+ \sa rowsAboutToBeRemoved()
+ */
+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 ) ) );
+}
+
+
diff --git a/src/modeltest.h b/src/modeltest.h index 7e8c8267..68090f3f 100644 --- a/src/modeltest.h +++ b/src/modeltest.h @@ -1,94 +1,94 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the test suite of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:LGPL$ -** Commercial License Usage -** Licensees holding valid commercial Qt licenses may use this file in -** accordance with the commercial license agreement provided with the -** Software or, alternatively, in accordance with the terms contained in -** a written agreement between you and Digia. For licensing terms and -** conditions see http://qt.digia.com/licensing. For further information -** use the contact form at http://qt.digia.com/contact-us. -** -** GNU Lesser General Public License Usage -** Alternatively, this file may be used under the terms of the GNU Lesser -** General Public License version 2.1 as published by the Free Software -** Foundation and appearing in the file LICENSE.LGPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU Lesser General Public License version 2.1 requirements -** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. -** -** In addition, as a special exception, Digia gives you certain additional -** rights. These rights are described in the Digia Qt LGPL Exception -** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. -** -** GNU General Public License Usage -** Alternatively, this file may be used under the terms of the GNU -** General Public License version 3.0 as published by the Free Software -** Foundation and appearing in the file LICENSE.GPL included in the -** packaging of this file. Please review the following information to -** ensure the GNU General Public License version 3.0 requirements will be -** met: http://www.gnu.org/copyleft/gpl.html. -** -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - - -#ifndef MODELTEST_H -#define MODELTEST_H - -#include <QtCore/QObject> -#include <QtCore/QAbstractItemModel> -#include <QtCore/QStack> - -class ModelTest : public QObject -{ - Q_OBJECT - -public: - ModelTest( QAbstractItemModel *model, QObject *parent = 0 ); - -private Q_SLOTS: - 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 ); - -private: - void checkChildren( const QModelIndex &parent, int currentDepth = 0 ); - - QAbstractItemModel *model; - - struct Changing { - QModelIndex parent; - int oldSize; - QVariant last; - QVariant next; - }; - QStack<Changing> insert; - QStack<Changing> remove; - - bool fetchingMore; - - QList<QPersistentModelIndex> changing; -}; - -#endif +/****************************************************************************
+**
+** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies).
+** Contact: http://www.qt-project.org/legal
+**
+** This file is part of the test suite of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:LGPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and Digia. For licensing terms and
+** conditions see http://qt.digia.com/licensing. For further information
+** use the contact form at http://qt.digia.com/contact-us.
+**
+** GNU Lesser General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU Lesser
+** General Public License version 2.1 as published by the Free Software
+** Foundation and appearing in the file LICENSE.LGPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU Lesser General Public License version 2.1 requirements
+** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
+**
+** In addition, as a special exception, Digia gives you certain additional
+** rights. These rights are described in the Digia Qt LGPL Exception
+** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3.0 as published by the Free Software
+** Foundation and appearing in the file LICENSE.GPL included in the
+** packaging of this file. Please review the following information to
+** ensure the GNU General Public License version 3.0 requirements will be
+** met: http://www.gnu.org/copyleft/gpl.html.
+**
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+
+#ifndef MODELTEST_H
+#define MODELTEST_H
+
+#include <QtCore/QObject>
+#include <QtCore/QAbstractItemModel>
+#include <QtCore/QStack>
+
+class ModelTest : public QObject
+{
+ Q_OBJECT
+
+public:
+ ModelTest( QAbstractItemModel *model, QObject *parent = 0 );
+
+private Q_SLOTS:
+ 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 );
+
+private:
+ void checkChildren( const QModelIndex &parent, int currentDepth = 0 );
+
+ QAbstractItemModel *model;
+
+ struct Changing {
+ QModelIndex parent;
+ int oldSize;
+ QVariant last;
+ QVariant next;
+ };
+ QStack<Changing> insert;
+ QStack<Changing> remove;
+
+ bool fetchingMore;
+
+ QList<QPersistentModelIndex> changing;
+};
+
+#endif
diff --git a/src/modinfo.cpp b/src/modinfo.cpp index f72b5d82..b0351cf4 100644 --- a/src/modinfo.cpp +++ b/src/modinfo.cpp @@ -1,1003 +1,1003 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "modinfo.h" -#include "utility.h" -#include "installationtester.h" -#include "categories.h" -#include "report.h" -#include "modinfodialog.h" -#include "overwriteinfodialog.h" -#include "json.h" -#include "messagedialog.h" - -#include <gameinfo.h> -#include <versioninfo.h> - -#include <QApplication> - -#include <QDirIterator> -#include <QMutexLocker> -#include <QSettings> -#include <sstream> - - -using namespace MOBase; -using namespace MOShared; - - -std::vector<ModInfo::Ptr> ModInfo::s_Collection; -std::map<QString, unsigned int> ModInfo::s_ModsByName; -std::map<int, std::vector<unsigned int> > 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; -} - - -ModInfo::Ptr ModInfo::createFromPlugin(const QString &espName, const QStringList &bsaNames - , DirectoryEntry ** directoryStructure) -{ - QMutexLocker locker(&s_Mutex); - ModInfo::Ptr result = ModInfo::Ptr(new ModInfoForeign(espName, bsaNames, directoryStructure)); - s_Collection.push_back(result); - return result; -} - - -void ModInfo::createFromOverwrite() -{ - QMutexLocker locker(&s_Mutex); - - s_Collection.push_back(ModInfo::Ptr(new ModInfoOverwrite)); -} - - -unsigned int ModInfo::getNumMods() -{ - QMutexLocker locker(&s_Mutex); - return s_Collection.size(); -} - - -ModInfo::Ptr ModInfo::getByIndex(unsigned int index) -{ - QMutexLocker locker(&s_Mutex); - - if (index >= s_Collection.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - return s_Collection[index]; -} - - -std::vector<ModInfo::Ptr> ModInfo::getByModID(int modID) -{ - QMutexLocker locker(&s_Mutex); - - auto iter = s_ModsByModID.find(modID); - if (iter == s_ModsByModID.end()) { - return std::vector<ModInfo::Ptr>(); - } - - std::vector<ModInfo::Ptr> result; - for (auto idxIter = iter->second.begin(); idxIter != iter->second.end(); ++idxIter) { - result.push_back(getByIndex(*idxIter)); - } - - return result; -} - - -bool ModInfo::removeMod(unsigned int index) -{ - QMutexLocker locker(&s_Mutex); - - if (index >= s_Collection.size()) { - throw MyException(tr("invalid 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<unsigned int> indices = iter->second; - std::remove(indices.begin(), indices.end(), index); - 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::getIndex(const QString &name) -{ - QMutexLocker locker(&s_Mutex); - - std::map<QString, unsigned int>::iterator iter = s_ModsByName.find(name); - if (iter == s_ModsByName.end()) { - return UINT_MAX; - } - - return iter->second; -} - -unsigned int ModInfo::findMod(const boost::function<bool (ModInfo::Ptr)> &filter) -{ - for (unsigned int i = 0U; i < s_Collection.size(); ++i) { - if (filter(s_Collection[i])) { - return i; - } - } - return UINT_MAX; -} - - -void ModInfo::updateFromDisc(const QString &modDirectory, DirectoryEntry **directoryStructure, bool displayForeign) -{ - 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); - } - } - - { // list plugins in the data directory and make a foreign-managed mod out of each - std::vector<std::wstring> dlcPlugins = GameInfo::instance().getDLCPlugins(); - QDir dataDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/data"); - foreach (const QFileInfo &file, dataDir.entryInfoList(QStringList() << "*.esp" << "*.esm")) { - if ((file.baseName() != "Update") // hide update - && (file.baseName() != ToQString(GameInfo::instance().getGameName())) // hide the game esp - && (displayForeign // show non-dlc bundles only if the user wants them - || std::find(dlcPlugins.begin(), dlcPlugins.end(), ToWString(file.fileName())) != dlcPlugins.end())) { - QStringList archives; - foreach (const QString archiveName, dataDir.entryList(QStringList() << file.baseName() + "*.bsa")) { - archives.append(dataDir.absoluteFilePath(archiveName)); - } - - createFromPlugin(file.fileName(), archives, directoryStructure); - } - } - } - - createFromOverwrite(); - - std::sort(s_Collection.begin(), s_Collection.end(), ByName); - - updateIndices(); -} - - -void ModInfo::updateIndices() -{ - s_ModsByName.clear(); - s_ModsByModID.clear(); - QRegExp backupRegEx(".*backup[0-9]*$"); - - for (unsigned int i = 0; i < s_Collection.size(); ++i) { - QString modName = s_Collection[i]->name(); - 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<int> &modIDs, QObject *receiver) -{ - if (modIDs.size() != 0) { - NexusInterface::instance()->requestUpdates(modIDs, receiver, QVariant(), QString()); - } -} - - -int ModInfo::checkAllForUpdate(QObject *receiver) -{ - int result = 0; - std::vector<int> modIDs; - - modIDs.push_back(GameInfo::instance().getNexusModID()); - - for (std::vector<ModInfo::Ptr>::iterator iter = s_Collection.begin(); - iter != s_Collection.end(); ++iter) { - if ((*iter)->canBeUpdated()) { - modIDs.push_back((*iter)->getNexusID()); - if (modIDs.size() >= 255) { - checkChunkForUpdate(modIDs, receiver); - modIDs.clear(); - } - } - } - - checkChunkForUpdate(modIDs, receiver); - - return result; -} - -void ModInfo::setVersion(const VersionInfo &version) -{ - m_Version = version; -} - -bool ModInfo::hasFlag(ModInfo::EFlag flag) const -{ - std::vector<EFlag> flags = getFlags(); - return std::find(flags.begin(), flags.end(), flag) != flags.end(); -} - - -bool ModInfo::categorySet(int categoryID) const -{ - for (std::set<int>::const_iterator iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) { - if ((*iter == categoryID) || - (CategoryFactory::instance().isDecendantOf(*iter, categoryID))) { - return true; - } - } - - 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; - } - } - } - - // 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(); - } -} - - -ModInfoWithConflictInfo::ModInfoWithConflictInfo(DirectoryEntry **directoryStructure) - : m_DirectoryStructure(directoryStructure) {} - -void ModInfoWithConflictInfo::clearCaches() -{ - m_LastConflictCheck = QTime(); -} - -std::vector<ModInfo::EFlag> ModInfoWithConflictInfo::getFlags() const -{ - std::vector<ModInfo::EFlag> result; - switch (isConflicted()) { - case CONFLICT_MIXED: { - result.push_back(ModInfo::FLAG_CONFLICT_MIXED); - } break; - case CONFLICT_OVERWRITE: { - result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITE); - } break; - case CONFLICT_OVERWRITTEN: { - result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITTEN); - } break; - case CONFLICT_REDUNDANT: { - result.push_back(ModInfo::FLAG_CONFLICT_REDUNDANT); - } break; - default: { /* NOP */ } - } - return result; -} - - -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)) { - m_OverwriteList.clear(); - m_OverwrittenList.clear(); - bool regular = false; - - int dataID = 0; - if ((*m_DirectoryStructure)->originExists(L"data")) { - dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID(); - } - - std::wstring name = ToWString(this->name()); - if ((*m_DirectoryStructure)->originExists(name)) { - FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name); - std::vector<FileEntry::Ptr> files = origin.getFiles(); - // for all files in this origin - for (auto iter = files.begin(); iter != files.end(); ++iter) { - const std::vector<int> &alternatives = (*iter)->getAlternatives(); - if ((alternatives.size() == 0) - || (alternatives[0] == dataID)) { - // no alternatives -> no conflict - regular = true; - } else { - if ((*iter)->getOrigin() != origin.getID()) { - FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID((*iter)->getOrigin()); - unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); - m_OverwrittenList.insert(altIndex); - } - // for all non-providing alternative origins - for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) { - if ((*altIter != dataID) && (*altIter != origin.getID())) { - FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(*altIter); - unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName())); - if (origin.getPriority() > altOrigin.getPriority()) { - m_OverwriteList.insert(altIndex); - } else { - m_OverwrittenList.insert(altIndex); - } - } - } - } - } - } - - m_LastConflictCheck = QTime::currentTime(); - - 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()) { - if (!regular) { - m_CurrentConflictState = CONFLICT_REDUNDANT; - } else { - m_CurrentConflictState = CONFLICT_OVERWRITTEN; - } - } - else m_CurrentConflictState = CONFLICT_NONE; - } - - 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<FileEntry::Ptr> 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; - } -} - - - -ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStructure) - : ModInfoWithConflictInfo(directoryStructure) - , m_Name(path.dirName()) - , m_Path(path.absolutePath()) - , m_MetaInfoChanged(false) - , m_EndorsedState(ENDORSED_UNKNOWN) -{ - 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_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate); - if (metaFile.contains("endorsed")) { - if (metaFile.value("endorsed").canConvert<int>()) { - 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; - } - } - } - - 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<int> temp = m_Categories; - temp.erase(m_PrimaryCategory); - metaFile.setValue("category", QString("%1").arg(m_PrimaryCategory) + "," + SetJoin(temp, ",")); - metaFile.setValue("newestVersion", m_NewestVersion.canonicalString()); - metaFile.setValue("ignoredVersion", m_IgnoredVersion.canonicalString()); - metaFile.setValue("version", m_Version.canonicalString()); - if (m_NexusID != -1) { - metaFile.setValue("modid", m_NexusID); - } - metaFile.setValue("notes", m_Notes); - metaFile.setValue("nexusDescription", m_NexusDescription); - metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate)); - if (m_EndorsedState != ENDORSED_UNKNOWN) { - metaFile.setValue("endorsed", m_EndorsedState); - } - metaFile.sync(); // sync needs to be called to ensure the file is created - - if (metaFile.status() == QSettings::NoError) { - 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::updateAvailable() const -{ - if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { - return false; - } - return m_NewestVersion.isValid() && (m_Version < m_NewestVersion); -} - - -bool ModInfoRegular::downgradeAvailable() const -{ - if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) { - return false; - } - return m_NewestVersion.isValid() && (m_NewestVersion < m_Version); -} - - -void ModInfoRegular::nxmDescriptionAvailable(int, QVariant, QVariant resultData) -{ - QVariantMap result = resultData.toMap(); - m_NewestVersion.parse(result["version"].toString()); - m_NexusDescription = result["description"].toString(); - if ((m_EndorsedState != ENDORSED_NEVER) && (result.contains("voted_by_user"))) { - m_EndorsedState = result["voted_by_user"].toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; - } - m_LastNexusQuery = QDateTime::currentDateTime(); - m_MetaInfoChanged = true; - saveMeta(); - emit modDetailsUpdated(true); -} - - -void ModInfoRegular::nxmEndorsementToggled(int, QVariant, QVariant resultData) -{ - m_EndorsedState = resultData.toBool() ? ENDORSED_TRUE : ENDORSED_FALSE; - m_MetaInfoChanged = true; - saveMeta(); - emit modDetailsUpdated(true); -} - - -void ModInfoRegular::nxmRequestFailed(int, int, QVariant userData, const QString &errorMessage) -{ - QString fullMessage = errorMessage; - if (userData.canConvert<int>() && (userData.toInt() == 1)) { - fullMessage += "\nNexus will reject endorsements within 15 Minutes of a failed attempt, the error message may be misleading."; - } - if (QApplication::activeWindow() != NULL) { - MessageDialog::showMessage(fullMessage, QApplication::activeWindow()); - } - emit modDetailsUpdated(false); -} - - -bool ModInfoRegular::updateNXMInfo() -{ - if (m_NexusID > 0) { - m_NexusBridge.requestDescription(m_NexusID, QVariant()); - return true; - } - return false; -} - - -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<int>::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()); - } - } - } -} - - -bool ModInfoRegular::setName(const QString &name) -{ - if (name.contains('/') || name.contains('\\')) { - return false; - } - - QString newPath = m_Path.mid(0).replace(m_Path.length() - m_Name.length(), m_Name.length(), name); - 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; - } - } - - std::map<QString, unsigned int>::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; - - 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; - } - - return 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::setNewestVersion(const VersionInfo &version) { - m_NewestVersion = version; -} - -void ModInfoRegular::setNexusDescription(const QString &description) -{ - m_NexusDescription = description; - m_MetaInfoChanged = true; -} - -void ModInfoRegular::addNexusCategory(int categoryID) -{ - m_Categories.insert(CategoryFactory::instance().resolveNexusID(categoryID)); -} - -void ModInfoRegular::setIsEndorsed(bool endorsed) -{ - if (m_EndorsedState != ENDORSED_NEVER) { - m_EndorsedState = endorsed ? ENDORSED_TRUE : ENDORSED_FALSE; - m_MetaInfoChanged = true; - } -} - - -void ModInfoRegular::setNeverEndorse() -{ - m_EndorsedState = ENDORSED_NEVER; - m_MetaInfoChanged = true; -} - - -bool ModInfoRegular::remove() -{ - m_MetaInfoChanged = false; - return shellDelete(QStringList(absolutePath()), true); -} - -void ModInfoRegular::endorse(bool doEndorse) -{ - if (doEndorse != (m_EndorsedState == ENDORSED_TRUE)) { - m_NexusBridge.requestToggleEndorsement(getNexusID(), doEndorse, QVariant(1)); - } -} - - -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<ModInfo::EFlag> ModInfoRegular::getFlags() const -{ - std::vector<ModInfo::EFlag> result = ModInfoWithConflictInfo::getFlags(); - if ((m_NexusID != -1) && (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); - } - return result; -} - - -int ModInfoRegular::getHighlight() const -{ - return isValid() ? HIGHLIGHT_NONE: HIGHLIGHT_INVALID; -} - - -QString ModInfoRegular::getDescription() const -{ - if (!isValid()) { - return tr("%1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory").arg(name()); - } else { - const std::set<int> &categories = getCategories(); - std::wostringstream categoryString; - categoryString << ToWString(tr("Categories: <br>")); - CategoryFactory &categoryFactory = CategoryFactory::instance(); - for (std::set<int>::const_iterator catIter = categories.begin(); - catIter != categories.end(); ++catIter) { - if (catIter != categories.begin()) { - categoryString << " , "; - } - categoryString << "<span style=\"white-space: nowrap;\"><i>" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << "</font></span>"; - } - - return ToQString(categoryString.str()); - } -} - -QString ModInfoRegular::notes() const -{ - return m_Notes; -} - -QDateTime ModInfoRegular::creationTime() const -{ - return m_CreationTime; -} - -QString ModInfoRegular::getNexusDescription() const -{ - return m_NexusDescription; -} - -ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const -{ - return m_EndorsedState; -} - -QDateTime ModInfoRegular::getLastNexusQuery() const -{ - return m_LastNexusQuery; -} - - -QStringList ModInfoRegular::archives() const -{ - QStringList result; - QDir dir(this->absolutePath()); - foreach (const QString &archive, dir.entryList(QStringList("*.bsa"))) { - result.append(this->absolutePath() + "/" + archive); - } - return result; -} - -std::vector<QString> ModInfoRegular::getIniTweaks() const -{ - QString metaFileName = absolutePath().append("/meta.ini"); - QSettings metaFile(metaFileName, QSettings::IniFormat); - - std::vector<QString> result; - - int numTweaks = metaFile.beginReadArray("INI Tweaks"); - - 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; -} - -std::vector<ModInfo::EFlag> ModInfoBackup::getFlags() const -{ - std::vector<ModInfo::EFlag> 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, DirectoryEntry **directoryStructure) - : ModInfoRegular(path, directoryStructure) -{ -} - - -ModInfoOverwrite::ModInfoOverwrite() - : m_StartupTime(QDateTime::currentDateTime()) -{ - 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; -} - -QString ModInfoOverwrite::absolutePath() const -{ - return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOverwriteDir())); -} - -std::vector<ModInfo::EFlag> ModInfoOverwrite::getFlags() const -{ - std::vector<ModInfo::EFlag> result; - result.push_back(FLAG_OVERWRITE); - return result; -} - -int ModInfoOverwrite::getHighlight() const -{ - return (isValid() ? HIGHLIGHT_IMPORTANT : HIGHLIGHT_INVALID) | HIGHLIGHT_CENTER; -} - -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()); - foreach (const QString &archive, dir.entryList(QStringList("*.bsa"))) { - result.append(this->absolutePath() + "/" + archive); - } - return result; -} - -QString ModInfoForeign::name() const -{ - return m_Name; -} - -QDateTime ModInfoForeign::creationTime() const -{ - return m_CreationTime; -} - -QString ModInfoForeign::absolutePath() const -{ - return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/data"; -} - -std::vector<ModInfo::EFlag> ModInfoForeign::getFlags() const -{ - std::vector<ModInfo::EFlag> result = ModInfoWithConflictInfo::getFlags(); - result.push_back(FLAG_FOREIGN); - - return result; -} - -int ModInfoForeign::getHighlight() const -{ - return 0; -} - -QString ModInfoForeign::getDescription() const -{ - return tr("This pseudo mod represents content managed outside MO. It isn't modified by MO."); -} - -ModInfoForeign::ModInfoForeign(const QString &referenceFile, const QStringList &archives, - DirectoryEntry **directoryStructure) - : ModInfoWithConflictInfo(directoryStructure) - , m_ReferenceFile(referenceFile) - , m_Archives(archives) -{ - m_CreationTime = QFileInfo(referenceFile).created(); - m_Name = QFileInfo(m_ReferenceFile).baseName(); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "modinfo.h"
+#include "utility.h"
+#include "installationtester.h"
+#include "categories.h"
+#include "report.h"
+#include "modinfodialog.h"
+#include "overwriteinfodialog.h"
+#include "json.h"
+#include "messagedialog.h"
+
+#include <gameinfo.h>
+#include <versioninfo.h>
+
+#include <QApplication>
+
+#include <QDirIterator>
+#include <QMutexLocker>
+#include <QSettings>
+#include <sstream>
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+std::vector<ModInfo::Ptr> ModInfo::s_Collection;
+std::map<QString, unsigned int> ModInfo::s_ModsByName;
+std::map<int, std::vector<unsigned int> > 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;
+}
+
+
+ModInfo::Ptr ModInfo::createFromPlugin(const QString &espName, const QStringList &bsaNames
+ , DirectoryEntry ** directoryStructure)
+{
+ QMutexLocker locker(&s_Mutex);
+ ModInfo::Ptr result = ModInfo::Ptr(new ModInfoForeign(espName, bsaNames, directoryStructure));
+ s_Collection.push_back(result);
+ return result;
+}
+
+
+void ModInfo::createFromOverwrite()
+{
+ QMutexLocker locker(&s_Mutex);
+
+ s_Collection.push_back(ModInfo::Ptr(new ModInfoOverwrite));
+}
+
+
+unsigned int ModInfo::getNumMods()
+{
+ QMutexLocker locker(&s_Mutex);
+ return s_Collection.size();
+}
+
+
+ModInfo::Ptr ModInfo::getByIndex(unsigned int index)
+{
+ QMutexLocker locker(&s_Mutex);
+
+ if (index >= s_Collection.size()) {
+ throw MyException(tr("invalid index %1").arg(index));
+ }
+ return s_Collection[index];
+}
+
+
+std::vector<ModInfo::Ptr> ModInfo::getByModID(int modID)
+{
+ QMutexLocker locker(&s_Mutex);
+
+ auto iter = s_ModsByModID.find(modID);
+ if (iter == s_ModsByModID.end()) {
+ return std::vector<ModInfo::Ptr>();
+ }
+
+ std::vector<ModInfo::Ptr> result;
+ for (auto idxIter = iter->second.begin(); idxIter != iter->second.end(); ++idxIter) {
+ result.push_back(getByIndex(*idxIter));
+ }
+
+ return result;
+}
+
+
+bool ModInfo::removeMod(unsigned int index)
+{
+ QMutexLocker locker(&s_Mutex);
+
+ if (index >= s_Collection.size()) {
+ throw MyException(tr("invalid 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<unsigned int> indices = iter->second;
+ std::remove(indices.begin(), indices.end(), index);
+ 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::getIndex(const QString &name)
+{
+ QMutexLocker locker(&s_Mutex);
+
+ std::map<QString, unsigned int>::iterator iter = s_ModsByName.find(name);
+ if (iter == s_ModsByName.end()) {
+ return UINT_MAX;
+ }
+
+ return iter->second;
+}
+
+unsigned int ModInfo::findMod(const boost::function<bool (ModInfo::Ptr)> &filter)
+{
+ for (unsigned int i = 0U; i < s_Collection.size(); ++i) {
+ if (filter(s_Collection[i])) {
+ return i;
+ }
+ }
+ return UINT_MAX;
+}
+
+
+void ModInfo::updateFromDisc(const QString &modDirectory, DirectoryEntry **directoryStructure, bool displayForeign)
+{
+ 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);
+ }
+ }
+
+ { // list plugins in the data directory and make a foreign-managed mod out of each
+ std::vector<std::wstring> dlcPlugins = GameInfo::instance().getDLCPlugins();
+ QDir dataDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/data");
+ foreach (const QFileInfo &file, dataDir.entryInfoList(QStringList() << "*.esp" << "*.esm")) {
+ if ((file.baseName() != "Update") // hide update
+ && (file.baseName() != ToQString(GameInfo::instance().getGameName())) // hide the game esp
+ && (displayForeign // show non-dlc bundles only if the user wants them
+ || std::find(dlcPlugins.begin(), dlcPlugins.end(), ToWString(file.fileName())) != dlcPlugins.end())) {
+ QStringList archives;
+ foreach (const QString archiveName, dataDir.entryList(QStringList() << file.baseName() + "*.bsa")) {
+ archives.append(dataDir.absoluteFilePath(archiveName));
+ }
+
+ createFromPlugin(file.fileName(), archives, directoryStructure);
+ }
+ }
+ }
+
+ createFromOverwrite();
+
+ std::sort(s_Collection.begin(), s_Collection.end(), ByName);
+
+ updateIndices();
+}
+
+
+void ModInfo::updateIndices()
+{
+ s_ModsByName.clear();
+ s_ModsByModID.clear();
+ QRegExp backupRegEx(".*backup[0-9]*$");
+
+ for (unsigned int i = 0; i < s_Collection.size(); ++i) {
+ QString modName = s_Collection[i]->name();
+ 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<int> &modIDs, QObject *receiver)
+{
+ if (modIDs.size() != 0) {
+ NexusInterface::instance()->requestUpdates(modIDs, receiver, QVariant(), QString());
+ }
+}
+
+
+int ModInfo::checkAllForUpdate(QObject *receiver)
+{
+ int result = 0;
+ std::vector<int> modIDs;
+
+ modIDs.push_back(GameInfo::instance().getNexusModID());
+
+ for (std::vector<ModInfo::Ptr>::iterator iter = s_Collection.begin();
+ iter != s_Collection.end(); ++iter) {
+ if ((*iter)->canBeUpdated()) {
+ modIDs.push_back((*iter)->getNexusID());
+ if (modIDs.size() >= 255) {
+ checkChunkForUpdate(modIDs, receiver);
+ modIDs.clear();
+ }
+ }
+ }
+
+ checkChunkForUpdate(modIDs, receiver);
+
+ return result;
+}
+
+void ModInfo::setVersion(const VersionInfo &version)
+{
+ m_Version = version;
+}
+
+bool ModInfo::hasFlag(ModInfo::EFlag flag) const
+{
+ std::vector<EFlag> flags = getFlags();
+ return std::find(flags.begin(), flags.end(), flag) != flags.end();
+}
+
+
+bool ModInfo::categorySet(int categoryID) const
+{
+ for (std::set<int>::const_iterator iter = m_Categories.begin(); iter != m_Categories.end(); ++iter) {
+ if ((*iter == categoryID) ||
+ (CategoryFactory::instance().isDecendantOf(*iter, categoryID))) {
+ return true;
+ }
+ }
+
+ 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;
+ }
+ }
+ }
+
+ // 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();
+ }
+}
+
+
+ModInfoWithConflictInfo::ModInfoWithConflictInfo(DirectoryEntry **directoryStructure)
+ : m_DirectoryStructure(directoryStructure) {}
+
+void ModInfoWithConflictInfo::clearCaches()
+{
+ m_LastConflictCheck = QTime();
+}
+
+std::vector<ModInfo::EFlag> ModInfoWithConflictInfo::getFlags() const
+{
+ std::vector<ModInfo::EFlag> result;
+ switch (isConflicted()) {
+ case CONFLICT_MIXED: {
+ result.push_back(ModInfo::FLAG_CONFLICT_MIXED);
+ } break;
+ case CONFLICT_OVERWRITE: {
+ result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITE);
+ } break;
+ case CONFLICT_OVERWRITTEN: {
+ result.push_back(ModInfo::FLAG_CONFLICT_OVERWRITTEN);
+ } break;
+ case CONFLICT_REDUNDANT: {
+ result.push_back(ModInfo::FLAG_CONFLICT_REDUNDANT);
+ } break;
+ default: { /* NOP */ }
+ }
+ return result;
+}
+
+
+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)) {
+ m_OverwriteList.clear();
+ m_OverwrittenList.clear();
+ bool regular = false;
+
+ int dataID = 0;
+ if ((*m_DirectoryStructure)->originExists(L"data")) {
+ dataID = (*m_DirectoryStructure)->getOriginByName(L"data").getID();
+ }
+
+ std::wstring name = ToWString(this->name());
+ if ((*m_DirectoryStructure)->originExists(name)) {
+ FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name);
+ std::vector<FileEntry::Ptr> files = origin.getFiles();
+ // for all files in this origin
+ for (auto iter = files.begin(); iter != files.end(); ++iter) {
+ const std::vector<int> &alternatives = (*iter)->getAlternatives();
+ if ((alternatives.size() == 0)
+ || (alternatives[0] == dataID)) {
+ // no alternatives -> no conflict
+ regular = true;
+ } else {
+ if ((*iter)->getOrigin() != origin.getID()) {
+ FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID((*iter)->getOrigin());
+ unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName()));
+ m_OverwrittenList.insert(altIndex);
+ }
+ // for all non-providing alternative origins
+ for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) {
+ if ((*altIter != dataID) && (*altIter != origin.getID())) {
+ FilesOrigin &altOrigin = (*m_DirectoryStructure)->getOriginByID(*altIter);
+ unsigned int altIndex = ModInfo::getIndex(ToQString(altOrigin.getName()));
+ if (origin.getPriority() > altOrigin.getPriority()) {
+ m_OverwriteList.insert(altIndex);
+ } else {
+ m_OverwrittenList.insert(altIndex);
+ }
+ }
+ }
+ }
+ }
+ }
+
+ m_LastConflictCheck = QTime::currentTime();
+
+ 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()) {
+ if (!regular) {
+ m_CurrentConflictState = CONFLICT_REDUNDANT;
+ } else {
+ m_CurrentConflictState = CONFLICT_OVERWRITTEN;
+ }
+ }
+ else m_CurrentConflictState = CONFLICT_NONE;
+ }
+
+ 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<FileEntry::Ptr> 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;
+ }
+}
+
+
+
+ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStructure)
+ : ModInfoWithConflictInfo(directoryStructure)
+ , m_Name(path.dirName())
+ , m_Path(path.absolutePath())
+ , m_MetaInfoChanged(false)
+ , m_EndorsedState(ENDORSED_UNKNOWN)
+{
+ 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_LastNexusQuery = QDateTime::fromString(metaFile.value("lastNexusQuery", "").toString(), Qt::ISODate);
+ if (metaFile.contains("endorsed")) {
+ if (metaFile.value("endorsed").canConvert<int>()) {
+ 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;
+ }
+ }
+ }
+
+ 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<int> temp = m_Categories;
+ temp.erase(m_PrimaryCategory);
+ metaFile.setValue("category", QString("%1").arg(m_PrimaryCategory) + "," + SetJoin(temp, ","));
+ metaFile.setValue("newestVersion", m_NewestVersion.canonicalString());
+ metaFile.setValue("ignoredVersion", m_IgnoredVersion.canonicalString());
+ metaFile.setValue("version", m_Version.canonicalString());
+ if (m_NexusID != -1) {
+ metaFile.setValue("modid", m_NexusID);
+ }
+ metaFile.setValue("notes", m_Notes);
+ metaFile.setValue("nexusDescription", m_NexusDescription);
+ metaFile.setValue("lastNexusQuery", m_LastNexusQuery.toString(Qt::ISODate));
+ if (m_EndorsedState != ENDORSED_UNKNOWN) {
+ metaFile.setValue("endorsed", m_EndorsedState);
+ }
+ metaFile.sync(); // sync needs to be called to ensure the file is created
+
+ if (metaFile.status() == QSettings::NoError) {
+ 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::updateAvailable() const
+{
+ if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) {
+ return false;
+ }
+ return m_NewestVersion.isValid() && (m_Version < m_NewestVersion);
+}
+
+
+bool ModInfoRegular::downgradeAvailable() const
+{
+ if (m_IgnoredVersion.isValid() && (m_IgnoredVersion == m_NewestVersion)) {
+ return false;
+ }
+ return m_NewestVersion.isValid() && (m_NewestVersion < m_Version);
+}
+
+
+void ModInfoRegular::nxmDescriptionAvailable(int, QVariant, QVariant resultData)
+{
+ QVariantMap result = resultData.toMap();
+ m_NewestVersion.parse(result["version"].toString());
+ m_NexusDescription = result["description"].toString();
+ if ((m_EndorsedState != ENDORSED_NEVER) && (result.contains("voted_by_user"))) {
+ m_EndorsedState = result["voted_by_user"].toBool() ? ENDORSED_TRUE : ENDORSED_FALSE;
+ }
+ m_LastNexusQuery = QDateTime::currentDateTime();
+ m_MetaInfoChanged = true;
+ saveMeta();
+ emit modDetailsUpdated(true);
+}
+
+
+void ModInfoRegular::nxmEndorsementToggled(int, QVariant, QVariant resultData)
+{
+ m_EndorsedState = resultData.toBool() ? ENDORSED_TRUE : ENDORSED_FALSE;
+ m_MetaInfoChanged = true;
+ saveMeta();
+ emit modDetailsUpdated(true);
+}
+
+
+void ModInfoRegular::nxmRequestFailed(int, int, QVariant userData, const QString &errorMessage)
+{
+ QString fullMessage = errorMessage;
+ if (userData.canConvert<int>() && (userData.toInt() == 1)) {
+ fullMessage += "\nNexus will reject endorsements within 15 Minutes of a failed attempt, the error message may be misleading.";
+ }
+ if (QApplication::activeWindow() != NULL) {
+ MessageDialog::showMessage(fullMessage, QApplication::activeWindow());
+ }
+ emit modDetailsUpdated(false);
+}
+
+
+bool ModInfoRegular::updateNXMInfo()
+{
+ if (m_NexusID > 0) {
+ m_NexusBridge.requestDescription(m_NexusID, QVariant());
+ return true;
+ }
+ return false;
+}
+
+
+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<int>::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());
+ }
+ }
+ }
+}
+
+
+bool ModInfoRegular::setName(const QString &name)
+{
+ if (name.contains('/') || name.contains('\\')) {
+ return false;
+ }
+
+ QString newPath = m_Path.mid(0).replace(m_Path.length() - m_Name.length(), m_Name.length(), name);
+ 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;
+ }
+ }
+
+ std::map<QString, unsigned int>::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;
+
+ 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;
+ }
+
+ return 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::setNewestVersion(const VersionInfo &version) {
+ m_NewestVersion = version;
+}
+
+void ModInfoRegular::setNexusDescription(const QString &description)
+{
+ m_NexusDescription = description;
+ m_MetaInfoChanged = true;
+}
+
+void ModInfoRegular::addNexusCategory(int categoryID)
+{
+ m_Categories.insert(CategoryFactory::instance().resolveNexusID(categoryID));
+}
+
+void ModInfoRegular::setIsEndorsed(bool endorsed)
+{
+ if (m_EndorsedState != ENDORSED_NEVER) {
+ m_EndorsedState = endorsed ? ENDORSED_TRUE : ENDORSED_FALSE;
+ m_MetaInfoChanged = true;
+ }
+}
+
+
+void ModInfoRegular::setNeverEndorse()
+{
+ m_EndorsedState = ENDORSED_NEVER;
+ m_MetaInfoChanged = true;
+}
+
+
+bool ModInfoRegular::remove()
+{
+ m_MetaInfoChanged = false;
+ return shellDelete(QStringList(absolutePath()), true);
+}
+
+void ModInfoRegular::endorse(bool doEndorse)
+{
+ if (doEndorse != (m_EndorsedState == ENDORSED_TRUE)) {
+ m_NexusBridge.requestToggleEndorsement(getNexusID(), doEndorse, QVariant(1));
+ }
+}
+
+
+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<ModInfo::EFlag> ModInfoRegular::getFlags() const
+{
+ std::vector<ModInfo::EFlag> result = ModInfoWithConflictInfo::getFlags();
+ if ((m_NexusID != -1) && (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);
+ }
+ return result;
+}
+
+
+int ModInfoRegular::getHighlight() const
+{
+ return isValid() ? HIGHLIGHT_NONE: HIGHLIGHT_INVALID;
+}
+
+
+QString ModInfoRegular::getDescription() const
+{
+ if (!isValid()) {
+ return tr("%1 contains no esp/esm and no asset (textures, meshes, interface, ...) directory").arg(name());
+ } else {
+ const std::set<int> &categories = getCategories();
+ std::wostringstream categoryString;
+ categoryString << ToWString(tr("Categories: <br>"));
+ CategoryFactory &categoryFactory = CategoryFactory::instance();
+ for (std::set<int>::const_iterator catIter = categories.begin();
+ catIter != categories.end(); ++catIter) {
+ if (catIter != categories.begin()) {
+ categoryString << " , ";
+ }
+ categoryString << "<span style=\"white-space: nowrap;\"><i>" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << "</font></span>";
+ }
+
+ return ToQString(categoryString.str());
+ }
+}
+
+QString ModInfoRegular::notes() const
+{
+ return m_Notes;
+}
+
+QDateTime ModInfoRegular::creationTime() const
+{
+ return m_CreationTime;
+}
+
+QString ModInfoRegular::getNexusDescription() const
+{
+ return m_NexusDescription;
+}
+
+ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const
+{
+ return m_EndorsedState;
+}
+
+QDateTime ModInfoRegular::getLastNexusQuery() const
+{
+ return m_LastNexusQuery;
+}
+
+
+QStringList ModInfoRegular::archives() const
+{
+ QStringList result;
+ QDir dir(this->absolutePath());
+ foreach (const QString &archive, dir.entryList(QStringList("*.bsa"))) {
+ result.append(this->absolutePath() + "/" + archive);
+ }
+ return result;
+}
+
+std::vector<QString> ModInfoRegular::getIniTweaks() const
+{
+ QString metaFileName = absolutePath().append("/meta.ini");
+ QSettings metaFile(metaFileName, QSettings::IniFormat);
+
+ std::vector<QString> result;
+
+ int numTweaks = metaFile.beginReadArray("INI Tweaks");
+
+ 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;
+}
+
+std::vector<ModInfo::EFlag> ModInfoBackup::getFlags() const
+{
+ std::vector<ModInfo::EFlag> 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, DirectoryEntry **directoryStructure)
+ : ModInfoRegular(path, directoryStructure)
+{
+}
+
+
+ModInfoOverwrite::ModInfoOverwrite()
+ : m_StartupTime(QDateTime::currentDateTime())
+{
+ 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;
+}
+
+QString ModInfoOverwrite::absolutePath() const
+{
+ return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOverwriteDir()));
+}
+
+std::vector<ModInfo::EFlag> ModInfoOverwrite::getFlags() const
+{
+ std::vector<ModInfo::EFlag> result;
+ result.push_back(FLAG_OVERWRITE);
+ return result;
+}
+
+int ModInfoOverwrite::getHighlight() const
+{
+ return (isValid() ? HIGHLIGHT_IMPORTANT : HIGHLIGHT_INVALID) | HIGHLIGHT_CENTER;
+}
+
+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());
+ foreach (const QString &archive, dir.entryList(QStringList("*.bsa"))) {
+ result.append(this->absolutePath() + "/" + archive);
+ }
+ return result;
+}
+
+QString ModInfoForeign::name() const
+{
+ return m_Name;
+}
+
+QDateTime ModInfoForeign::creationTime() const
+{
+ return m_CreationTime;
+}
+
+QString ModInfoForeign::absolutePath() const
+{
+ return QDir::fromNativeSeparators(ToQString(GameInfo::instance().getGameDirectory())) + "/data";
+}
+
+std::vector<ModInfo::EFlag> ModInfoForeign::getFlags() const
+{
+ std::vector<ModInfo::EFlag> result = ModInfoWithConflictInfo::getFlags();
+ result.push_back(FLAG_FOREIGN);
+
+ return result;
+}
+
+int ModInfoForeign::getHighlight() const
+{
+ return 0;
+}
+
+QString ModInfoForeign::getDescription() const
+{
+ return tr("This pseudo mod represents content managed outside MO. It isn't modified by MO.");
+}
+
+ModInfoForeign::ModInfoForeign(const QString &referenceFile, const QStringList &archives,
+ DirectoryEntry **directoryStructure)
+ : ModInfoWithConflictInfo(directoryStructure)
+ , m_ReferenceFile(referenceFile)
+ , m_Archives(archives)
+{
+ m_CreationTime = QFileInfo(referenceFile).created();
+ m_Name = QFileInfo(m_ReferenceFile).baseName();
+}
diff --git a/src/modinfo.h b/src/modinfo.h index cacddadf..0c144936 100644 --- a/src/modinfo.h +++ b/src/modinfo.h @@ -1,1028 +1,1028 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef MODINFO_H -#define MODINFO_H - -#include "nexusinterface.h" -#include <versioninfo.h> -#include <imodinterface.h> - -#include <QString> -#include <QMutex> -#include <QIcon> -#include <QDir> -#include <QSharedPointer> -#include <QDateTime> -#include <map> -#include <set> -#include <vector> -#include <directoryentry.h> - -using MOBase::ModRepositoryFileInfo; - -/** - * @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 -{ - - Q_OBJECT - -public: - - typedef QSharedPointer<ModInfo> 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 - }; - - enum EHighlight { - HIGHLIGHT_NONE = 0, - HIGHLIGHT_INVALID = 1, - HIGHLIGHT_CENTER = 2, - HIGHLIGHT_IMPORTANT = 4 - }; - - enum EEndorsedState { - ENDORSED_FALSE, - ENDORSED_TRUE, - ENDORSED_UNKNOWN, - ENDORSED_NEVER - }; - -public: - - /** - * @brief read the mod directory and Mod ModInfo objects for all subdirectories - **/ - static void updateFromDisc(const QString &modDirectory, MOShared::DirectoryEntry **directoryStructure, bool displayForeign); - - 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<ModInfo::Ptr> 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<bool (ModInfo::Ptr)> &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<int> &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 &espName, const QStringList &bsaNames, MOShared::DirectoryEntry **directoryStructure); - - 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 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 changes/updates the nexus description text - * @param description the current description text - */ - virtual void setNexusDescription(const QString &description) = 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; - - /** - * 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 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; - - /** - * @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<EFlag> getFlags() const = 0; - - /** - * @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; - - /** - * @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<QString> 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 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<int> &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() {} - - /** - * @brief retrieve list of mods (as mod index) that are overwritten by this one. Updates may be delayed - * @return - */ - virtual std::set<unsigned int> getModOverwrite() { return std::set<unsigned int>(); } - - /** - * @brief retrieve list of mods (as mod index) that overwrite this one. Updates may be delayed - * @return - */ - virtual std::set<unsigned int> getModOverwritten() { return std::set<unsigned int>(); } - -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); - -protected: - - ModInfo(); - - static void updateIndices(); - -private: - - static void createFromOverwrite(); - -protected: - - static std::vector<ModInfo::Ptr> s_Collection; - static std::map<QString, unsigned int> s_ModsByName; - - int m_PrimaryCategory; - std::set<int> m_Categories; - - MOBase::VersionInfo m_Version; - -private: - - static QMutex s_Mutex; - static std::map<int, std::vector<unsigned int> > s_ModsByModID; - static int s_NextID; - - bool m_Valid; - -}; - - -class ModInfoWithConflictInfo : public ModInfo -{ - -public: - - ModInfoWithConflictInfo(MOShared::DirectoryEntry **directoryStructure); - - std::vector<ModInfo::EFlag> getFlags() const; - - /** - * @brief clear all caches held for this mod - */ - virtual void clearCaches(); - - virtual std::set<unsigned int> getModOverwrite() { return m_OverwriteList; } - - virtual std::set<unsigned int> getModOverwritten() { return m_OverwrittenList; } - -private: - - 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; - -private: - - MOShared::DirectoryEntry **m_DirectoryStructure; - - mutable EConflictType m_CurrentConflictState; - mutable QTime m_LastConflictCheck; - - mutable std::set<unsigned int> m_OverwriteList; // indices of mods overritten by this mod - mutable std::set<unsigned int> m_OverwrittenList; // indices of mods overwriting this mod - -}; - - -/** - * @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 ModInfoRegular : public ModInfoWithConflictInfo -{ - - Q_OBJECT - - 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 ¬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); - - /** - * @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; } - - /** - * 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<EFlag> getFlags() 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<QString> 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 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; - - /** - * @brief stores meta information back to disk - */ - virtual void saveMeta(); - - void readMeta(); - -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); - -protected: - - ModInfoRegular(const QDir &path, MOShared::DirectoryEntry **directoryStructure); - -private: - - QString m_Name; - QString m_Path; - QString m_InstallationFile; - QString m_Notes; - QString m_NexusDescription; - - QDateTime m_CreationTime; - QDateTime m_LastNexusQuery; - - int m_NexusID; - - bool m_MetaInfoChanged; - MOBase::VersionInfo m_NewestVersion; - MOBase::VersionInfo m_IgnoredVersion; - - EEndorsedState m_EndorsedState; - - NexusBridge m_NexusBridge; - -}; - - -class ModInfoBackup : public ModInfoRegular -{ - - 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<QString> getIniTweaks() const { return std::vector<QString>(); } - virtual std::vector<EFlag> getFlags() const; - virtual QString getDescription() const; - virtual QDateTime getLastNexusQuery() const { return QDateTime(); } - virtual void getNexusFiles(QList<MOBase::ModRepositoryFileInfo*>::const_iterator&, - QList<MOBase::ModRepositoryFileInfo*>::const_iterator&) {} - virtual QString getNexusDescription() const { return QString(); } - -private: - - ModInfoBackup(const QDir &path, MOShared::DirectoryEntry **directoryStructure); - -}; - - -class ModInfoOverwrite : public ModInfo -{ - - Q_OBJECT - - 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 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 m_StartupTime; } - virtual QString absolutePath() const; - virtual MOBase::VersionInfo getNewestVersion() const { return ""; } - virtual QString getInstallationFile() const { return ""; } - virtual int getFixedPriority() const { return INT_MAX; } - virtual int getNexusID() const { return -1; } - virtual std::vector<QString> getIniTweaks() const { return std::vector<QString>(); } - virtual std::vector<ModInfo::EFlag> 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; - -private: - - ModInfoOverwrite(); - -private: - - QDateTime m_StartupTime; - -}; - - -class ModInfoForeign : public ModInfoWithConflictInfo -{ - - Q_OBJECT - - 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 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 notes() const { return ""; } - virtual QDateTime creationTime() const; - virtual QString absolutePath() const; - virtual MOBase::VersionInfo getNewestVersion() const { return ""; } - virtual QString getInstallationFile() const { return ""; } - virtual int getNexusID() const { return -1; } - virtual std::vector<QString> getIniTweaks() const { return std::vector<QString>(); } - virtual std::vector<ModInfo::EFlag> 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; } - -protected: - - ModInfoForeign(const QString &referenceFile, const QStringList &archives, MOShared::DirectoryEntry **directoryStructure); - -private: - - QString m_Name; - QString m_ReferenceFile; - QStringList m_Archives; - QDateTime m_CreationTime; - int m_Priority; - -}; - -#endif // MODINFO_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef MODINFO_H
+#define MODINFO_H
+
+#include "nexusinterface.h"
+#include <versioninfo.h>
+#include <imodinterface.h>
+
+#include <QString>
+#include <QMutex>
+#include <QIcon>
+#include <QDir>
+#include <QSharedPointer>
+#include <QDateTime>
+#include <map>
+#include <set>
+#include <vector>
+#include <directoryentry.h>
+
+using MOBase::ModRepositoryFileInfo;
+
+/**
+ * @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
+{
+
+ Q_OBJECT
+
+public:
+
+ typedef QSharedPointer<ModInfo> 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
+ };
+
+ enum EHighlight {
+ HIGHLIGHT_NONE = 0,
+ HIGHLIGHT_INVALID = 1,
+ HIGHLIGHT_CENTER = 2,
+ HIGHLIGHT_IMPORTANT = 4
+ };
+
+ enum EEndorsedState {
+ ENDORSED_FALSE,
+ ENDORSED_TRUE,
+ ENDORSED_UNKNOWN,
+ ENDORSED_NEVER
+ };
+
+public:
+
+ /**
+ * @brief read the mod directory and Mod ModInfo objects for all subdirectories
+ **/
+ static void updateFromDisc(const QString &modDirectory, MOShared::DirectoryEntry **directoryStructure, bool displayForeign);
+
+ 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<ModInfo::Ptr> 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<bool (ModInfo::Ptr)> &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<int> &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 &espName, const QStringList &bsaNames, MOShared::DirectoryEntry **directoryStructure);
+
+ 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 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 changes/updates the nexus description text
+ * @param description the current description text
+ */
+ virtual void setNexusDescription(const QString &description) = 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;
+
+ /**
+ * 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 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;
+
+ /**
+ * @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<EFlag> getFlags() const = 0;
+
+ /**
+ * @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;
+
+ /**
+ * @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<QString> 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 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<int> &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() {}
+
+ /**
+ * @brief retrieve list of mods (as mod index) that are overwritten by this one. Updates may be delayed
+ * @return
+ */
+ virtual std::set<unsigned int> getModOverwrite() { return std::set<unsigned int>(); }
+
+ /**
+ * @brief retrieve list of mods (as mod index) that overwrite this one. Updates may be delayed
+ * @return
+ */
+ virtual std::set<unsigned int> getModOverwritten() { return std::set<unsigned int>(); }
+
+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);
+
+protected:
+
+ ModInfo();
+
+ static void updateIndices();
+
+private:
+
+ static void createFromOverwrite();
+
+protected:
+
+ static std::vector<ModInfo::Ptr> s_Collection;
+ static std::map<QString, unsigned int> s_ModsByName;
+
+ int m_PrimaryCategory;
+ std::set<int> m_Categories;
+
+ MOBase::VersionInfo m_Version;
+
+private:
+
+ static QMutex s_Mutex;
+ static std::map<int, std::vector<unsigned int> > s_ModsByModID;
+ static int s_NextID;
+
+ bool m_Valid;
+
+};
+
+
+class ModInfoWithConflictInfo : public ModInfo
+{
+
+public:
+
+ ModInfoWithConflictInfo(MOShared::DirectoryEntry **directoryStructure);
+
+ std::vector<ModInfo::EFlag> getFlags() const;
+
+ /**
+ * @brief clear all caches held for this mod
+ */
+ virtual void clearCaches();
+
+ virtual std::set<unsigned int> getModOverwrite() { return m_OverwriteList; }
+
+ virtual std::set<unsigned int> getModOverwritten() { return m_OverwrittenList; }
+
+private:
+
+ 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;
+
+private:
+
+ MOShared::DirectoryEntry **m_DirectoryStructure;
+
+ mutable EConflictType m_CurrentConflictState;
+ mutable QTime m_LastConflictCheck;
+
+ mutable std::set<unsigned int> m_OverwriteList; // indices of mods overritten by this mod
+ mutable std::set<unsigned int> m_OverwrittenList; // indices of mods overwriting this mod
+
+};
+
+
+/**
+ * @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 ModInfoRegular : public ModInfoWithConflictInfo
+{
+
+ Q_OBJECT
+
+ 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 ¬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);
+
+ /**
+ * @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; }
+
+ /**
+ * 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<EFlag> getFlags() 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<QString> 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 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;
+
+ /**
+ * @brief stores meta information back to disk
+ */
+ virtual void saveMeta();
+
+ void readMeta();
+
+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);
+
+protected:
+
+ ModInfoRegular(const QDir &path, MOShared::DirectoryEntry **directoryStructure);
+
+private:
+
+ QString m_Name;
+ QString m_Path;
+ QString m_InstallationFile;
+ QString m_Notes;
+ QString m_NexusDescription;
+
+ QDateTime m_CreationTime;
+ QDateTime m_LastNexusQuery;
+
+ int m_NexusID;
+
+ bool m_MetaInfoChanged;
+ MOBase::VersionInfo m_NewestVersion;
+ MOBase::VersionInfo m_IgnoredVersion;
+
+ EEndorsedState m_EndorsedState;
+
+ NexusBridge m_NexusBridge;
+
+};
+
+
+class ModInfoBackup : public ModInfoRegular
+{
+
+ 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<QString> getIniTweaks() const { return std::vector<QString>(); }
+ virtual std::vector<EFlag> getFlags() const;
+ virtual QString getDescription() const;
+ virtual QDateTime getLastNexusQuery() const { return QDateTime(); }
+ virtual void getNexusFiles(QList<MOBase::ModRepositoryFileInfo*>::const_iterator&,
+ QList<MOBase::ModRepositoryFileInfo*>::const_iterator&) {}
+ virtual QString getNexusDescription() const { return QString(); }
+
+private:
+
+ ModInfoBackup(const QDir &path, MOShared::DirectoryEntry **directoryStructure);
+
+};
+
+
+class ModInfoOverwrite : public ModInfo
+{
+
+ Q_OBJECT
+
+ 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 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 m_StartupTime; }
+ virtual QString absolutePath() const;
+ virtual MOBase::VersionInfo getNewestVersion() const { return ""; }
+ virtual QString getInstallationFile() const { return ""; }
+ virtual int getFixedPriority() const { return INT_MAX; }
+ virtual int getNexusID() const { return -1; }
+ virtual std::vector<QString> getIniTweaks() const { return std::vector<QString>(); }
+ virtual std::vector<ModInfo::EFlag> 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;
+
+private:
+
+ ModInfoOverwrite();
+
+private:
+
+ QDateTime m_StartupTime;
+
+};
+
+
+class ModInfoForeign : public ModInfoWithConflictInfo
+{
+
+ Q_OBJECT
+
+ 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 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 notes() const { return ""; }
+ virtual QDateTime creationTime() const;
+ virtual QString absolutePath() const;
+ virtual MOBase::VersionInfo getNewestVersion() const { return ""; }
+ virtual QString getInstallationFile() const { return ""; }
+ virtual int getNexusID() const { return -1; }
+ virtual std::vector<QString> getIniTweaks() const { return std::vector<QString>(); }
+ virtual std::vector<ModInfo::EFlag> 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; }
+
+protected:
+
+ ModInfoForeign(const QString &referenceFile, const QStringList &archives, MOShared::DirectoryEntry **directoryStructure);
+
+private:
+
+ QString m_Name;
+ QString m_ReferenceFile;
+ QStringList m_Archives;
+ QDateTime m_CreationTime;
+ int m_Priority;
+
+};
+
+#endif // MODINFO_H
diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 96a575a0..d22fe138 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -1,1256 +1,1256 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "modinfodialog.h" -#include "ui_modinfodialog.h" - -#include "report.h" -#include "utility.h" -#include "messagedialog.h" -#include "bbcode.h" -#include "questionboxmemory.h" -#include "settings.h" -#include <gameinfo.h> - -#include <QDir> -#include <QDirIterator> -#include <QPushButton> -#include <QInputDialog> -#include <QMessageBox> -#include <QMenu> -#include <QFileSystemModel> -#include <Shlwapi.h> -#include <sstream> -#include <QInputDialog> - - -using namespace MOBase; -using namespace MOShared; - - -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) {} -private: - int m_SortValue; -}; - - -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(NULL), m_RenameAction(NULL), m_OpenAction(NULL), - m_Directory(directory), m_Origin(NULL) -{ - ui->setupUi(this); - this->setWindowTitle(modInfo->name()); - this->setWindowModality(Qt::WindowModal); - - m_RootPath = modInfo->absolutePath(); - - QString metaFileName = m_RootPath.mid(0).append("/meta.ini"); - m_Settings = new QSettings(metaFileName, QSettings::IniFormat); - - QLineEdit *modIDEdit = findChild<QLineEdit*>("modIDEdit"); - ui->modIDEdit->setValidator(new QIntValidator(modIDEdit)); - ui->modIDEdit->setText(QString("%1").arg(modInfo->getNexusID())); - - 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, SIGNAL(anchorClicked(QUrl)), this, SLOT(linkClicked(QUrl))); - - if (directory->originExists(ToWString(modInfo->name()))) { - m_Origin = &directory->getOriginByName(ToWString(modInfo->name())); - if (m_Origin->isDisabled()) { - m_Origin = NULL; - } - } - - 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)); - } - refreshLists(); - initINITweaks(); - - ui->tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != NULL); - - if (ui->tabWidget->currentIndex() == TAB_NEXUS) { - activateNexusTab(); - } - - 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; - 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<QListWidgetItem*> 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<QTreeView*>("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; - } - } - return -1; -} - - -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<QTabBar*>("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); -} - - -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 != NULL) { - std::vector<FileEntry::Ptr> 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<int> alternatives = (*iter)->getAlternatives(); - if (!alternatives.empty()) { - std::wostringstream altString; - for (std::vector<int>::iterator altIter = alternatives.begin(); - altIter != alternatives.end(); ++altIter) { - if (altIter != alternatives.begin()) { - altString << ", "; - } - altString << m_Directory->getOriginByID(*altIter).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[0]).getName())); - item->setData(1, Qt::UserRole + 1, alternatives[0]); - 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)) { - 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<float>(image.width()) / static_cast<float>(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); -} - - -void ModInfoDialog::addCategories(const CategoryFactory &factory, const std::set<int> &enabledCategories, QTreeWidgetItem *root, int rootLevel) -{ - for (unsigned int i = 0; i < 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::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<QTabWidget*>("tabWidget"); - if (tabWidget->isTabEnabled(tab)) { - tabWidget->setCurrentIndex(tab); - } -} - -void ModInfoDialog::thumbnailClicked(const QString &fileName) -{ - QLabel *imageLabel = findChild<QLabel*>("imageLabel"); - QImage image(fileName); - if (static_cast<float>(image.width()) / static_cast<float>(image.height()) > 1.34) { - image = image.scaledToWidth(imageLabel->width()); - } else { - image = image.scaledToHeight(imageLabel->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; -} - - -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::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::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::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<QTextEdit*>("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()); - } - } - 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; - } - - 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; - } - - 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::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_iniFileView_textChanged() -{ - QPushButton* saveButton = findChild<QPushButton*>("saveButton"); - saveButton->setEnabled(true); -} - - -void ModInfoDialog::on_textFileView_textChanged() -{ - ui->saveTXTButton->setEnabled(true); -} - - -void ModInfoDialog::on_activateESP_clicked() -{ - QListWidget *activeESPList = findChild<QListWidget*>("activeESPList"); - QListWidget *inactiveESPList = findChild<QListWidget*>("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")); - } -} - - -void ModInfoDialog::on_deactivateESP_clicked() -{ - QListWidget *activeESPList = findChild<QListWidget*>("activeESPList"); - QListWidget *inactiveESPList = findChild<QListWidget*>("inactiveESPList"); - - int selectedRow = activeESPList->currentRow(); - if (selectedRow < 0) { - return; - } - - QDir root(m_RootPath); - - QListWidgetItem *selectedItem = activeESPList->takeItem(selectedRow); - - // 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::linkClicked(const QUrl &url) -{ - if (url.toString().startsWith(ToQString(GameInfo::instance().getNexusPage(false)))) { - this->close(); - emit nexusLinkActivated(url.toString()); - } else { - ::ShellExecuteW(NULL, L"open", ToWString(url.toString()).c_str(), NULL, NULL, SW_SHOWNORMAL); - } -} - - -void ModInfoDialog::refreshNexusData(int modID) -{ - if ((!m_RequestStarted) && (modID > 0)) { - m_RequestStarted = true; - - m_ModInfo->updateNXMInfo(); - - MessageDialog::showMessage(tr("Info requested, please wait"), this); - } -} - - -/*void ModInfoDialog::nxmDescriptionAvailable(int, QVariant, QVariant resultData, int requestID) -{ - std::set<int>::iterator idIter = m_RequestIDs.find(requestID); - if (idIter == m_RequestIDs.end()) { - return; - } else { - m_RequestIDs.erase(idIter); - } - - QVariantMap result = resultData.toMap(); - - if (!result["description"].isNull()) { - QString descriptionAsHTML = - QString("<html>" - "<head><style>body {background: #707070; } a { color: #5EA2E5; }</style></head>" - "<body>%1</body>" - "</html>").arg(BBCode::convertToHTML(result["description"].toString())); - -// 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)"))); - } - - QLineEdit *versionEdit = findChild<QLineEdit*>("versionEdit"); - QString version = result["version"].toString(); - - if (!version.isEmpty()) { - m_ModInfo->setNewestVersion(version); - - VersionInfo currentVersion(versionEdit->text()); - VersionInfo newestVersion(version); - - QPalette versionColor; - if (currentVersion < newestVersion) { - versionColor.setColor(QPalette::Text, Qt::red); - versionEdit->setToolTip(tr("Current Version: %1").arg(version)); - } else { - versionColor.setColor(QPalette::Text, Qt::green); - versionEdit->setToolTip(tr("No update available")); - } - versionEdit->setPalette(versionColor); - } -}*/ - - -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::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("<html>" - "<head><style>body {background: #707070; } a { color: #5EA2E5; }</style></head>" - "<body>%1</body>" - "</html>").arg(BBCode::convertToHTML(nexusDescription)); - - // 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(tr("(description incomplete, please visit nexus)")); - } - - updateVersionColor(); - } -} - - -void ModInfoDialog::activateNexusTab() -{ - QLineEdit *modIDEdit = findChild<QLineEdit*>("modIDEdit"); - int modID = modIDEdit->text().toInt(); - if (modID != 0) { - QString nexusLink = QString("%1/downloads/file.php?id=%2").arg(ToQString(GameInfo::instance().getNexusPage(false))).arg(modID); - QLabel *visitNexusLabel = findChild<QLabel*>("visitNexusLabel"); - visitNexusLabel->setText(tr("<a href=\"%1\">Visit on Nexus</a>").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<QLineEdit*>("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_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->setHtml(""); - if (modID != 0) { - m_RequestStarted = false; - refreshNexusData(modID); - } - } -} - -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()); - 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::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); - } -} - - -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::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(NULL, L"open", ToWString(fileName).c_str(), NULL, NULL, 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::createDirectoryTriggered() -{ - QModelIndex selection = m_FileSelection.at(0); - - QModelIndex index = m_FileSystemModel->isDir(selection) ? selection - : selection.parent(); - index = index.sibling(index.row(), 0); - - QString name = tr("New Folder"); - QString path = m_FileSystemModel->filePath(index).append("/"); - - QModelIndex existingIndex = m_FileSystemModel->index(path + name); - int suffix = 1; - while (existingIndex.isValid()) { - name = tr("New Folder") + QString::number(suffix++); - existingIndex = m_FileSystemModel->index(path + name); - } - - QModelIndex newIndex = m_FileSystemModel->mkdir(index, name); - if (!newIndex.isValid()) { - reportError(tr("Failed to create \"%1\"").arg(name)); - return; - } - - ui->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); - - menu.addAction(m_NewFolderAction); - - bool hasFiles = false; - - foreach(QModelIndex idx, m_FileSelection) { - if (m_FileSystemModel->fileInfo(idx).isFile()) { - hasFiles = true; - break; - } - } - - if (selectionModel->hasSelection()) { - if (hasFiles) { - menu.addAction(m_OpenAction); - } - menu.addAction(m_RenameAction); - menu.addAction(m_DeleteAction); - if (m_FileSystemModel->fileName(m_FileSelection.at(0)).endsWith(ModInfo::s_HiddenExt)) { - menu.addAction(m_UnhideAction); - } else { - menu.addAction(m_HideAction); - } - } 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 != NULL) && ((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::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_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; - } - } - - 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))); - return false; - } -} - - -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::on_overwriteTree_customContextMenuRequested(const QPoint &pos) -{ - m_ConflictsContextItem = ui->overwriteTree->itemAt(pos.x(), pos.y()); - - if (m_ConflictsContextItem != NULL) { - // 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_refreshButton_clicked() -{ - m_ModInfo->updateNXMInfo(); - - MessageDialog::showMessage(tr("Info requested, please wait"), this); -} - -void ModInfoDialog::on_endorseBtn_clicked() -{ - emit endorseMod(m_ModInfo); -} - -void ModInfoDialog::on_nextButton_clicked() -{ - emit modOpenNext(); - 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::on_iniTweaksList_customContextMenuRequested(const QPoint &pos) -{ - QMenu menu; - menu.addAction(tr("Create Tweak"), this, SLOT(createTweak())); - menu.exec(ui->iniTweaksList->mapToGlobal(pos)); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "modinfodialog.h"
+#include "ui_modinfodialog.h"
+
+#include "report.h"
+#include "utility.h"
+#include "messagedialog.h"
+#include "bbcode.h"
+#include "questionboxmemory.h"
+#include "settings.h"
+#include <gameinfo.h>
+
+#include <QDir>
+#include <QDirIterator>
+#include <QPushButton>
+#include <QInputDialog>
+#include <QMessageBox>
+#include <QMenu>
+#include <QFileSystemModel>
+#include <Shlwapi.h>
+#include <sstream>
+#include <QInputDialog>
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+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) {}
+private:
+ int m_SortValue;
+};
+
+
+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(NULL), m_RenameAction(NULL), m_OpenAction(NULL),
+ m_Directory(directory), m_Origin(NULL)
+{
+ ui->setupUi(this);
+ this->setWindowTitle(modInfo->name());
+ this->setWindowModality(Qt::WindowModal);
+
+ m_RootPath = modInfo->absolutePath();
+
+ QString metaFileName = m_RootPath.mid(0).append("/meta.ini");
+ m_Settings = new QSettings(metaFileName, QSettings::IniFormat);
+
+ QLineEdit *modIDEdit = findChild<QLineEdit*>("modIDEdit");
+ ui->modIDEdit->setValidator(new QIntValidator(modIDEdit));
+ ui->modIDEdit->setText(QString("%1").arg(modInfo->getNexusID()));
+
+ 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, SIGNAL(anchorClicked(QUrl)), this, SLOT(linkClicked(QUrl)));
+
+ if (directory->originExists(ToWString(modInfo->name()))) {
+ m_Origin = &directory->getOriginByName(ToWString(modInfo->name()));
+ if (m_Origin->isDisabled()) {
+ m_Origin = NULL;
+ }
+ }
+
+ 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));
+ }
+ refreshLists();
+ initINITweaks();
+
+ ui->tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != NULL);
+
+ if (ui->tabWidget->currentIndex() == TAB_NEXUS) {
+ activateNexusTab();
+ }
+
+ 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;
+ 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<QListWidgetItem*> 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<QTreeView*>("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;
+ }
+ }
+ return -1;
+}
+
+
+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<QTabBar*>("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);
+}
+
+
+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 != NULL) {
+ std::vector<FileEntry::Ptr> 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<int> alternatives = (*iter)->getAlternatives();
+ if (!alternatives.empty()) {
+ std::wostringstream altString;
+ for (std::vector<int>::iterator altIter = alternatives.begin();
+ altIter != alternatives.end(); ++altIter) {
+ if (altIter != alternatives.begin()) {
+ altString << ", ";
+ }
+ altString << m_Directory->getOriginByID(*altIter).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[0]).getName()));
+ item->setData(1, Qt::UserRole + 1, alternatives[0]);
+ 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)) {
+ 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<float>(image.width()) / static_cast<float>(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);
+}
+
+
+void ModInfoDialog::addCategories(const CategoryFactory &factory, const std::set<int> &enabledCategories, QTreeWidgetItem *root, int rootLevel)
+{
+ for (unsigned int i = 0; i < 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::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<QTabWidget*>("tabWidget");
+ if (tabWidget->isTabEnabled(tab)) {
+ tabWidget->setCurrentIndex(tab);
+ }
+}
+
+void ModInfoDialog::thumbnailClicked(const QString &fileName)
+{
+ QLabel *imageLabel = findChild<QLabel*>("imageLabel");
+ QImage image(fileName);
+ if (static_cast<float>(image.width()) / static_cast<float>(image.height()) > 1.34) {
+ image = image.scaledToWidth(imageLabel->width());
+ } else {
+ image = image.scaledToHeight(imageLabel->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;
+}
+
+
+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::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::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::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<QTextEdit*>("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());
+ }
+ }
+ 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;
+ }
+
+ 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;
+ }
+
+ 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::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_iniFileView_textChanged()
+{
+ QPushButton* saveButton = findChild<QPushButton*>("saveButton");
+ saveButton->setEnabled(true);
+}
+
+
+void ModInfoDialog::on_textFileView_textChanged()
+{
+ ui->saveTXTButton->setEnabled(true);
+}
+
+
+void ModInfoDialog::on_activateESP_clicked()
+{
+ QListWidget *activeESPList = findChild<QListWidget*>("activeESPList");
+ QListWidget *inactiveESPList = findChild<QListWidget*>("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"));
+ }
+}
+
+
+void ModInfoDialog::on_deactivateESP_clicked()
+{
+ QListWidget *activeESPList = findChild<QListWidget*>("activeESPList");
+ QListWidget *inactiveESPList = findChild<QListWidget*>("inactiveESPList");
+
+ int selectedRow = activeESPList->currentRow();
+ if (selectedRow < 0) {
+ return;
+ }
+
+ QDir root(m_RootPath);
+
+ QListWidgetItem *selectedItem = activeESPList->takeItem(selectedRow);
+
+ // 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::linkClicked(const QUrl &url)
+{
+ if (url.toString().startsWith(ToQString(GameInfo::instance().getNexusPage(false)))) {
+ this->close();
+ emit nexusLinkActivated(url.toString());
+ } else {
+ ::ShellExecuteW(NULL, L"open", ToWString(url.toString()).c_str(), NULL, NULL, SW_SHOWNORMAL);
+ }
+}
+
+
+void ModInfoDialog::refreshNexusData(int modID)
+{
+ if ((!m_RequestStarted) && (modID > 0)) {
+ m_RequestStarted = true;
+
+ m_ModInfo->updateNXMInfo();
+
+ MessageDialog::showMessage(tr("Info requested, please wait"), this);
+ }
+}
+
+
+/*void ModInfoDialog::nxmDescriptionAvailable(int, QVariant, QVariant resultData, int requestID)
+{
+ std::set<int>::iterator idIter = m_RequestIDs.find(requestID);
+ if (idIter == m_RequestIDs.end()) {
+ return;
+ } else {
+ m_RequestIDs.erase(idIter);
+ }
+
+ QVariantMap result = resultData.toMap();
+
+ if (!result["description"].isNull()) {
+ QString descriptionAsHTML =
+ QString("<html>"
+ "<head><style>body {background: #707070; } a { color: #5EA2E5; }</style></head>"
+ "<body>%1</body>"
+ "</html>").arg(BBCode::convertToHTML(result["description"].toString()));
+
+// 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)")));
+ }
+
+ QLineEdit *versionEdit = findChild<QLineEdit*>("versionEdit");
+ QString version = result["version"].toString();
+
+ if (!version.isEmpty()) {
+ m_ModInfo->setNewestVersion(version);
+
+ VersionInfo currentVersion(versionEdit->text());
+ VersionInfo newestVersion(version);
+
+ QPalette versionColor;
+ if (currentVersion < newestVersion) {
+ versionColor.setColor(QPalette::Text, Qt::red);
+ versionEdit->setToolTip(tr("Current Version: %1").arg(version));
+ } else {
+ versionColor.setColor(QPalette::Text, Qt::green);
+ versionEdit->setToolTip(tr("No update available"));
+ }
+ versionEdit->setPalette(versionColor);
+ }
+}*/
+
+
+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::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("<html>"
+ "<head><style>body {background: #707070; } a { color: #5EA2E5; }</style></head>"
+ "<body>%1</body>"
+ "</html>").arg(BBCode::convertToHTML(nexusDescription));
+
+ // 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(tr("(description incomplete, please visit nexus)"));
+ }
+
+ updateVersionColor();
+ }
+}
+
+
+void ModInfoDialog::activateNexusTab()
+{
+ QLineEdit *modIDEdit = findChild<QLineEdit*>("modIDEdit");
+ int modID = modIDEdit->text().toInt();
+ if (modID != 0) {
+ QString nexusLink = QString("%1/downloads/file.php?id=%2").arg(ToQString(GameInfo::instance().getNexusPage(false))).arg(modID);
+ QLabel *visitNexusLabel = findChild<QLabel*>("visitNexusLabel");
+ visitNexusLabel->setText(tr("<a href=\"%1\">Visit on Nexus</a>").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<QLineEdit*>("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_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->setHtml("");
+ if (modID != 0) {
+ m_RequestStarted = false;
+ refreshNexusData(modID);
+ }
+ }
+}
+
+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());
+ 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::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);
+ }
+}
+
+
+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::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(NULL, L"open", ToWString(fileName).c_str(), NULL, NULL, 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::createDirectoryTriggered()
+{
+ QModelIndex selection = m_FileSelection.at(0);
+
+ QModelIndex index = m_FileSystemModel->isDir(selection) ? selection
+ : selection.parent();
+ index = index.sibling(index.row(), 0);
+
+ QString name = tr("New Folder");
+ QString path = m_FileSystemModel->filePath(index).append("/");
+
+ QModelIndex existingIndex = m_FileSystemModel->index(path + name);
+ int suffix = 1;
+ while (existingIndex.isValid()) {
+ name = tr("New Folder") + QString::number(suffix++);
+ existingIndex = m_FileSystemModel->index(path + name);
+ }
+
+ QModelIndex newIndex = m_FileSystemModel->mkdir(index, name);
+ if (!newIndex.isValid()) {
+ reportError(tr("Failed to create \"%1\"").arg(name));
+ return;
+ }
+
+ ui->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);
+
+ menu.addAction(m_NewFolderAction);
+
+ bool hasFiles = false;
+
+ foreach(QModelIndex idx, m_FileSelection) {
+ if (m_FileSystemModel->fileInfo(idx).isFile()) {
+ hasFiles = true;
+ break;
+ }
+ }
+
+ if (selectionModel->hasSelection()) {
+ if (hasFiles) {
+ menu.addAction(m_OpenAction);
+ }
+ menu.addAction(m_RenameAction);
+ menu.addAction(m_DeleteAction);
+ if (m_FileSystemModel->fileName(m_FileSelection.at(0)).endsWith(ModInfo::s_HiddenExt)) {
+ menu.addAction(m_UnhideAction);
+ } else {
+ menu.addAction(m_HideAction);
+ }
+ } 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 != NULL) && ((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::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_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;
+ }
+ }
+
+ 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)));
+ return false;
+ }
+}
+
+
+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::on_overwriteTree_customContextMenuRequested(const QPoint &pos)
+{
+ m_ConflictsContextItem = ui->overwriteTree->itemAt(pos.x(), pos.y());
+
+ if (m_ConflictsContextItem != NULL) {
+ // 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_refreshButton_clicked()
+{
+ m_ModInfo->updateNXMInfo();
+
+ MessageDialog::showMessage(tr("Info requested, please wait"), this);
+}
+
+void ModInfoDialog::on_endorseBtn_clicked()
+{
+ emit endorseMod(m_ModInfo);
+}
+
+void ModInfoDialog::on_nextButton_clicked()
+{
+ emit modOpenNext();
+ 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::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 63875b70..652bcab6 100644 --- a/src/modinfodialog.h +++ b/src/modinfodialog.h @@ -1,237 +1,237 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef MODINFODIALOG_H -#define MODINFODIALOG_H - - -#include "modinfo.h" -#include "categories.h" -#include "tutorabledialog.h" - -#include <QDialog> -#include <QSignalMapper> -#include <QSettings> -#include <QNetworkAccessManager> -#include <QNetworkReply> -#include <QModelIndex> -#include <QAction> -#include <QListWidgetItem> -#include <QTreeWidgetItem> -#include <QTextCodec> -#include <set> -#include <directoryentry.h> - - -namespace Ui { - class ModInfoDialog; -} - -class QFileSystemModel; -class QTreeView; - - -/** - * 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 -{ - Q_OBJECT - -public: - - 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; - -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); - -public slots: - - void modDetailsUpdated(bool success); - -private: - - void initFiletree(ModInfo::Ptr modInfo); - void initINITweaks(); - - void refreshLists(); - - void addCategories(const CategoryFactory &factory, const std::set<int> &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 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: - - Ui::ModInfoDialog *ui; - - ModInfo::Ptr m_ModInfo; - int m_OriginID; - - QSignalMapper m_ThumbnailMapper; - QString m_RootPath; - - QFileSystemModel *m_FileSystemModel; - QTreeView *m_FileTree; - QModelIndexList m_FileSelection; - - QSettings *m_Settings; - - std::set<int> m_RequestIDs; - bool m_RequestStarted; - - QAction *m_DeleteAction; - QAction *m_RenameAction; - QAction *m_OpenAction; - QAction *m_NewFolderAction; - QAction *m_HideAction; - QAction *m_UnhideAction; - - QTreeWidgetItem *m_ConflictsContextItem; - - const MOShared::DirectoryEntry *m_Directory; - MOShared::FilesOrigin *m_Origin; - - std::map<int, int> m_RealTabPos; - -}; - -#endif // MODINFODIALOG_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef MODINFODIALOG_H
+#define MODINFODIALOG_H
+
+
+#include "modinfo.h"
+#include "categories.h"
+#include "tutorabledialog.h"
+
+#include <QDialog>
+#include <QSignalMapper>
+#include <QSettings>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QModelIndex>
+#include <QAction>
+#include <QListWidgetItem>
+#include <QTreeWidgetItem>
+#include <QTextCodec>
+#include <set>
+#include <directoryentry.h>
+
+
+namespace Ui {
+ class ModInfoDialog;
+}
+
+class QFileSystemModel;
+class QTreeView;
+
+
+/**
+ * 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
+{
+ Q_OBJECT
+
+public:
+
+ 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;
+
+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);
+
+public slots:
+
+ void modDetailsUpdated(bool success);
+
+private:
+
+ void initFiletree(ModInfo::Ptr modInfo);
+ void initINITweaks();
+
+ void refreshLists();
+
+ void addCategories(const CategoryFactory &factory, const std::set<int> &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 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:
+
+ Ui::ModInfoDialog *ui;
+
+ ModInfo::Ptr m_ModInfo;
+ int m_OriginID;
+
+ QSignalMapper m_ThumbnailMapper;
+ QString m_RootPath;
+
+ QFileSystemModel *m_FileSystemModel;
+ QTreeView *m_FileTree;
+ QModelIndexList m_FileSelection;
+
+ QSettings *m_Settings;
+
+ std::set<int> m_RequestIDs;
+ bool m_RequestStarted;
+
+ QAction *m_DeleteAction;
+ QAction *m_RenameAction;
+ QAction *m_OpenAction;
+ QAction *m_NewFolderAction;
+ QAction *m_HideAction;
+ QAction *m_UnhideAction;
+
+ QTreeWidgetItem *m_ConflictsContextItem;
+
+ const MOShared::DirectoryEntry *m_Directory;
+ MOShared::FilesOrigin *m_Origin;
+
+ std::map<int, int> m_RealTabPos;
+
+};
+
+#endif // MODINFODIALOG_H
diff --git a/src/modlist.cpp b/src/modlist.cpp index 7e5a5f58..ff126faa 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -1,993 +1,993 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "modlist.h" - -#include "report.h" -#include "messagedialog.h" -#include "installationtester.h" -#include "qtgroupingproxy.h" -#include "viewmarkingscrollbar.h" -#include <gameinfo.h> -#include <utility.h> -#include <QFileInfo> -#include <QDir> -#include <QDirIterator> -#include <QMimeData> -#include <stdexcept> -#include <QStandardItemModel> -#include <QMessageBox> -#include <QStringList> -#include <QEvent> -#include <QContextMenuEvent> -#include <QMenu> -#include <QCheckBox> -#include <QWidgetAction> -#include <QAbstractItemView> -#include <QSortFilterProxyModel> -#include <sstream> -#include <algorithm> - - -using namespace MOBase; - - -ModList::ModList(QObject *parent) - : QAbstractItemModel(parent), m_Profile(NULL), m_Modified(false), - m_FontMetrics(QFont()), m_DropOnItems(false) -{ -} - - -void ModList::setProfile(Profile *profile) -{ - m_Profile = profile; -} - - -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; - } -} - - -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(); - } - } 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: 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("<i>%1</i>").arg(modInfo->notes().replace("\n", "<br>")); - 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 ""; - } -} - - -QVariant ModList::data(const QModelIndex &modelIndex, int role) const -{ - if (m_Profile == NULL) 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) { - 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 tr("None"); - return QVariant(); - } - } - } else if (column == COL_INSTALLTIME) { - // display installation time for mods that can be updated - if (modInfo->canBeUpdated()) { - 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(); - } - } 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<int> 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); - } - } 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::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 (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 (m_Overwrite.find(modIndex) != m_Overwrite.end()) { - return QColor(0, 255, 0, 64); - } else if (m_Overwritten.find(modIndex) != m_Overwritten.end()) { - return QColor(255, 0, 0, 64); - } else { - return QVariant(); - } - } else if (role == Qt::ToolTipRole) { - if (column == COL_FLAGS) { - QString result; - - std::vector<ModInfo::EFlag> flags = modInfo->getFlags(); - - for (auto iter = flags.begin(); iter != flags.end(); ++iter) { - if (result.length() != 0) result += "<br>"; - result += getFlagText(*iter, modInfo); - } - - return result; - } 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 += "<br>" + 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<int> &categories = modInfo->getCategories(); - std::wostringstream categoryString; - categoryString << ToWString(tr("Categories: <br>")); - CategoryFactory &categoryFactory = CategoryFactory::instance(); - for (std::set<int>::const_iterator catIter = categories.begin(); - catIter != categories.end(); ++catIter) { - if (catIter != categories.begin()) { - categoryString << " , "; - } - try { - categoryString << "<span style=\"white-space: nowrap;\"><i>" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << "</font></span>"; - } catch (const std::exception &e) { - qCritical("failed to generate tooltip: %s", e.what()); - return QString(); - } - } - - return ToQString(categoryString.str()); - } else { - 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"), NULL); - return false; - } - - // before we rename, write back the current profile so we don't lose changes and to ensure - // there is no scheduled asynchronous rewrite anytime soon - m_Profile->writeModlistNow(); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(index); - QString oldName = modInfo->name(); - if (modInfo->setName(nameFixed)) { - // this just disabled the mod in all profiles. The recipient of modRenamed must fix that - 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 == NULL) return false; - - if (static_cast<unsigned int>(index.row()) >= ModInfo::getNumMods()) { - return false; - } - - int modID = index.row(); - - ModInfo::Ptr info = ModInfo::getByIndex(modID); - IModList::ModStates oldState = state(modID); - - bool result = false; - - if (role == Qt::CheckStateRole) { - bool enabled = value.toInt() == Qt::Checked; - if (m_Profile->modEnabled(modID) != enabled) { - m_Profile->setModEnabled(modID, enabled); - m_Modified = true; - emit modlist_changed(index, role); - } - result = true; - } 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 modlist_changed(index, role); - 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); - 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); - } - } - - 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; -} - - -ModList::EColumn ModList::getEnabledColumn(int index) const -{ - for (int i = 0; i <= COL_LASTCOLUMN; ++i) { - if (index == 0) { - return static_cast<EColumn>(i); - } else { - --index; - } - } - return ModList::COL_NAME; -} - - -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); -} - - -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; - } - } - std::vector<ModInfo::EFlag> 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; - } - return result; -} - - -QStringList ModList::mimeTypes() const -{ - QStringList result = QAbstractItemModel::mimeTypes(); - result.append("text/uri-list"); - return result; -} - - -void ModList::changeModPriority(std::vector<int> sourceIndices, int newPriority) -{ - if (m_Profile == NULL) 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<int>::const_iterator iter = sourceIndices.begin(); - iter != sourceIndices.end(); ++iter) { - if (profile->getModPriority(*iter) < newPriority) { - --newPriority; - break; - } - } - - for (std::vector<int>::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 modorder_changed(); -} - - -void ModList::changeModPriority(int sourceIndex, int newPriority) -{ - if (m_Profile == NULL) return; - emit layoutAboutToBeChanged(); - - m_Profile->setModPriority(sourceIndex, newPriority); - - emit layoutChanged(); - - emit modorder_changed(); -} - -void ModList::setOverwriteMarkers(const std::set<unsigned int> &overwrite, const std::set<unsigned int> &overwritten) -{ - m_Overwrite = overwrite; - m_Overwritten = overwritten; - notifyChange(0, rowCount()); -} - -void ModList::modInfoAboutToChange(ModInfo::Ptr info) -{ - m_ChangeInfo.name = info->name(); - m_ChangeInfo.state = state(info->name()); -} - -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); - } - int row = ModInfo::getIndex(info->name()); - info->testValid(); - emit dataChanged(index(row, 0), index(row, columnCount())); - } else { - qCritical("modInfoChanged not called after modInfoAboutToChange"); - } -} - -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; - } - } - return result; -} - -IModList::ModStates ModList::state(const QString &name) const -{ - unsigned int modIndex = ModInfo::getIndex(name); - - return state(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<int>(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; - } -} - -bool ModList::onModStateChanged(const std::function<void (const QString &, IModList::ModStates)> &func) -{ - auto conn = m_ModStateChanged.connect(func); - return conn.connected(); -} - -bool ModList::onModMoved(const std::function<void (const QString &, int, int)> &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; - - if (row == -1) { - row = parent.row(); - } - - ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - QDir modDirectory(modInfo->absolutePath()); - QDir gameDirectory(QDir::fromNativeSeparators(ToQString(MOShared::GameInfo::instance().getOverwriteDir()))); - - unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { - std::vector<ModInfo::EFlag> flags = mod->getFlags(); - return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); }); - - QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name(); - - foreach (const QUrl &url, mimeData->urls()) { - QString relativePath = gameDirectory.relativeFilePath(url.toLocalFile()); - if (relativePath.startsWith("..")) { - qDebug("%s drop ignored", qPrintable(url.toLocalFile())); - continue; - } - source.append(url.toLocalFile()); - target.append(modDirectory.absoluteFilePath(relativePath)); - emit fileMoved(relativePath, overwriteName, modInfo->name()); - } - - if (source.count() != 0) { - shellMove(source, target); - } - - return true; -} - - -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<int> sourceRows; - - while (!stream.atEnd()) { - int sourceRow, col; - QMap<int, QVariant> roleDataMap; - stream >> sourceRow >> col >> roleDataMap; - if (col == 0) { - sourceRows.push_back(sourceRow); - } - } - - if (row == -1) { - row = parent.row(); - } - - if ((row < 0) || (static_cast<unsigned int>(row) >= ModInfo::getNumMods())) { - return false; - } - - int newPriority = 0; - { - if ((row < 0) || (row > static_cast<int>(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; -} - - -bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent) -{ - if (action == Qt::IgnoreAction) { - return true; - } - - if (m_Profile == NULL) return false; - if (mimeData->hasUrls()) { - return dropURLs(mimeData, row, parent); - } else { - return dropMod(mimeData, row, parent); - } - -} - - -void ModList::removeRowForce(int row) -{ - if (static_cast<unsigned int>(row) >= ModInfo::getNumMods()) { - return; - } - if (m_Profile == NULL) return; - - m_Profile->setModEnabled(row, false); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - - bool wasEnabled = m_Profile->modEnabled(row); - beginRemoveRows(QModelIndex(), row, row); - - m_Profile->cancelWriteModlist(); // don't write modlist while we're changing it - ModInfo::removeMod(row); - 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 - - endRemoveRows(); - if (wasEnabled) { - emit removeOrigin(modInfo->name()); - } - - emit modUninstalled(modInfo->getInstallationFile()); -} - - -void ModList::removeRow(int row, const QModelIndex&) -{ - if (static_cast<unsigned int>(row) >= ModInfo::getNumMods()) { - return; - } - if (m_Profile == NULL) return; - - m_Profile->setModEnabled(row, false); - - ModInfo::Ptr modInfo = ModInfo::getByIndex(row); - if (!modInfo->isRegular()) return; - - 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) { - removeRowForce(row); - } -} - - -void ModList::notifyChange(int rowStart, int rowEnd) -{ - if (rowStart < 0) { - 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::parent(const QModelIndex&) const -{ - return QModelIndex(); -} - -QMap<int, QVariant> ModList::itemData(const QModelIndex &index) const -{ - QMap<int, QVariant> result = QAbstractItemModel::itemData(index); - result[Qt::UserRole] = data(index, Qt::UserRole); - return result; -} - - -void ModList::dropModeUpdate(bool dropOnItems) -{ - if (m_DropOnItems != dropOnItems) { - m_DropOnItems = dropOnItems; - } -} - - -QString ModList::getColumnName(int column) -{ - switch (column) { - case COL_FLAGS: return tr("Flags"); - 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_INSTALLTIME: return tr("Time this mod was installed"); - default: return tr("unknown"); - } -} - - -bool ModList::eventFilter(QObject *obj, QEvent *event) -{ - if (event->type() == QEvent::ContextMenu) { - QContextMenuEvent *contextEvent = static_cast<QContextMenuEvent*>(event); - QWidget *object = qobject_cast<QWidget*>(obj); - if ((object != NULL) && (contextEvent->reason() == QContextMenuEvent::Mouse)) { - emit requestColumnSelect(object->mapToGlobal(contextEvent->pos())); - - return true; - } - } else if ((event->type() == QEvent::KeyPress) && (m_Profile != NULL)) { - QAbstractItemView *itemView = qobject_cast<QAbstractItemView*>(obj); - QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event); - if ((itemView != NULL) - && (keyEvent->modifiers() == Qt::ControlModifier) - && ((keyEvent->key() == Qt::Key_Up) - || (keyEvent->key() == Qt::Key_Down))) { - QItemSelectionModel *selectionModel = itemView->selectionModel(); - const QAbstractProxyModel *proxyModel = qobject_cast<const QAbstractProxyModel*>(selectionModel->model()); - const QSortFilterProxyModel *filterModel = NULL; - while ((filterModel == NULL) && (proxyModel != NULL)) { - filterModel = qobject_cast<const QSortFilterProxyModel*>(proxyModel); - if (filterModel == NULL) { - proxyModel = qobject_cast<const QAbstractProxyModel*>(proxyModel->sourceModel()); - } - } - if (filterModel == NULL) { - return true; - } - int diff = -1; - if (((keyEvent->key() == Qt::Key_Up) && (filterModel->sortOrder() == Qt::DescendingOrder)) || - ((keyEvent->key() == Qt::Key_Down) && (filterModel->sortOrder() == Qt::AscendingOrder))) { - diff = 1; - } - QModelIndexList rows = selectionModel->selectedRows(); - if (keyEvent->key() == Qt::Key_Down) { - for (int i = 0; i < rows.size() / 2; ++i) { - rows.swap(i, rows.size() - i - 1); - } - } - foreach (QModelIndex idx, rows) { - if (filterModel != NULL) { - idx = filterModel->mapToSource(idx); - } - int newPriority = m_Profile->getModPriority(idx.row()) + diff; - if ((newPriority >= 0) && (newPriority < static_cast<int>(m_Profile->numRegularMods()))) { - m_Profile->setModPriority(idx.row(), newPriority); - notifyChange(idx.row()); - } - } - return true; - } else if (keyEvent->key() == Qt::Key_Delete) { - 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; - } else if (keyEvent->key() == Qt::Key_Space) { - QItemSelectionModel *selectionModel = itemView->selectionModel(); - const QSortFilterProxyModel *proxyModel = qobject_cast<const QSortFilterProxyModel*>(selectionModel->model()); - - QModelIndex minRow, maxRow; - foreach (QModelIndex idx, selectionModel->selectedRows()) { - if (proxyModel != NULL) { - 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 QObject::eventFilter(obj, event); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "modlist.h"
+
+#include "report.h"
+#include "messagedialog.h"
+#include "installationtester.h"
+#include "qtgroupingproxy.h"
+#include "viewmarkingscrollbar.h"
+#include <gameinfo.h>
+#include <utility.h>
+#include <QFileInfo>
+#include <QDir>
+#include <QDirIterator>
+#include <QMimeData>
+#include <stdexcept>
+#include <QStandardItemModel>
+#include <QMessageBox>
+#include <QStringList>
+#include <QEvent>
+#include <QContextMenuEvent>
+#include <QMenu>
+#include <QCheckBox>
+#include <QWidgetAction>
+#include <QAbstractItemView>
+#include <QSortFilterProxyModel>
+#include <sstream>
+#include <algorithm>
+
+
+using namespace MOBase;
+
+
+ModList::ModList(QObject *parent)
+ : QAbstractItemModel(parent), m_Profile(NULL), m_Modified(false),
+ m_FontMetrics(QFont()), m_DropOnItems(false)
+{
+}
+
+
+void ModList::setProfile(Profile *profile)
+{
+ m_Profile = profile;
+}
+
+
+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;
+ }
+}
+
+
+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();
+ }
+ } 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: 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("<i>%1</i>").arg(modInfo->notes().replace("\n", "<br>"));
+ 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 "";
+ }
+}
+
+
+QVariant ModList::data(const QModelIndex &modelIndex, int role) const
+{
+ if (m_Profile == NULL) 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) {
+ 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 tr("None");
+ return QVariant();
+ }
+ }
+ } else if (column == COL_INSTALLTIME) {
+ // display installation time for mods that can be updated
+ if (modInfo->canBeUpdated()) {
+ 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();
+ }
+ } 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<int> 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);
+ }
+ } 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::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 (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 (m_Overwrite.find(modIndex) != m_Overwrite.end()) {
+ return QColor(0, 255, 0, 64);
+ } else if (m_Overwritten.find(modIndex) != m_Overwritten.end()) {
+ return QColor(255, 0, 0, 64);
+ } else {
+ return QVariant();
+ }
+ } else if (role == Qt::ToolTipRole) {
+ if (column == COL_FLAGS) {
+ QString result;
+
+ std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
+
+ for (auto iter = flags.begin(); iter != flags.end(); ++iter) {
+ if (result.length() != 0) result += "<br>";
+ result += getFlagText(*iter, modInfo);
+ }
+
+ return result;
+ } 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 += "<br>" + 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<int> &categories = modInfo->getCategories();
+ std::wostringstream categoryString;
+ categoryString << ToWString(tr("Categories: <br>"));
+ CategoryFactory &categoryFactory = CategoryFactory::instance();
+ for (std::set<int>::const_iterator catIter = categories.begin();
+ catIter != categories.end(); ++catIter) {
+ if (catIter != categories.begin()) {
+ categoryString << " , ";
+ }
+ try {
+ categoryString << "<span style=\"white-space: nowrap;\"><i>" << ToWString(categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(*catIter))) << "</font></span>";
+ } catch (const std::exception &e) {
+ qCritical("failed to generate tooltip: %s", e.what());
+ return QString();
+ }
+ }
+
+ return ToQString(categoryString.str());
+ } else {
+ 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"), NULL);
+ return false;
+ }
+
+ // before we rename, write back the current profile so we don't lose changes and to ensure
+ // there is no scheduled asynchronous rewrite anytime soon
+ m_Profile->writeModlistNow();
+
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
+ QString oldName = modInfo->name();
+ if (modInfo->setName(nameFixed)) {
+ // this just disabled the mod in all profiles. The recipient of modRenamed must fix that
+ 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 == NULL) return false;
+
+ if (static_cast<unsigned int>(index.row()) >= ModInfo::getNumMods()) {
+ return false;
+ }
+
+ int modID = index.row();
+
+ ModInfo::Ptr info = ModInfo::getByIndex(modID);
+ IModList::ModStates oldState = state(modID);
+
+ bool result = false;
+
+ if (role == Qt::CheckStateRole) {
+ bool enabled = value.toInt() == Qt::Checked;
+ if (m_Profile->modEnabled(modID) != enabled) {
+ m_Profile->setModEnabled(modID, enabled);
+ m_Modified = true;
+ emit modlist_changed(index, role);
+ }
+ result = true;
+ } 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 modlist_changed(index, role);
+ 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);
+ 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);
+ }
+ }
+
+ 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;
+}
+
+
+ModList::EColumn ModList::getEnabledColumn(int index) const
+{
+ for (int i = 0; i <= COL_LASTCOLUMN; ++i) {
+ if (index == 0) {
+ return static_cast<EColumn>(i);
+ } else {
+ --index;
+ }
+ }
+ return ModList::COL_NAME;
+}
+
+
+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);
+}
+
+
+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;
+ }
+ }
+ std::vector<ModInfo::EFlag> 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;
+ }
+ return result;
+}
+
+
+QStringList ModList::mimeTypes() const
+{
+ QStringList result = QAbstractItemModel::mimeTypes();
+ result.append("text/uri-list");
+ return result;
+}
+
+
+void ModList::changeModPriority(std::vector<int> sourceIndices, int newPriority)
+{
+ if (m_Profile == NULL) 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<int>::const_iterator iter = sourceIndices.begin();
+ iter != sourceIndices.end(); ++iter) {
+ if (profile->getModPriority(*iter) < newPriority) {
+ --newPriority;
+ break;
+ }
+ }
+
+ for (std::vector<int>::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 modorder_changed();
+}
+
+
+void ModList::changeModPriority(int sourceIndex, int newPriority)
+{
+ if (m_Profile == NULL) return;
+ emit layoutAboutToBeChanged();
+
+ m_Profile->setModPriority(sourceIndex, newPriority);
+
+ emit layoutChanged();
+
+ emit modorder_changed();
+}
+
+void ModList::setOverwriteMarkers(const std::set<unsigned int> &overwrite, const std::set<unsigned int> &overwritten)
+{
+ m_Overwrite = overwrite;
+ m_Overwritten = overwritten;
+ notifyChange(0, rowCount());
+}
+
+void ModList::modInfoAboutToChange(ModInfo::Ptr info)
+{
+ m_ChangeInfo.name = info->name();
+ m_ChangeInfo.state = state(info->name());
+}
+
+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);
+ }
+ int row = ModInfo::getIndex(info->name());
+ info->testValid();
+ emit dataChanged(index(row, 0), index(row, columnCount()));
+ } else {
+ qCritical("modInfoChanged not called after modInfoAboutToChange");
+ }
+}
+
+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;
+ }
+ }
+ return result;
+}
+
+IModList::ModStates ModList::state(const QString &name) const
+{
+ unsigned int modIndex = ModInfo::getIndex(name);
+
+ return state(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<int>(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;
+ }
+}
+
+bool ModList::onModStateChanged(const std::function<void (const QString &, IModList::ModStates)> &func)
+{
+ auto conn = m_ModStateChanged.connect(func);
+ return conn.connected();
+}
+
+bool ModList::onModMoved(const std::function<void (const QString &, int, int)> &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;
+
+ if (row == -1) {
+ row = parent.row();
+ }
+
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
+ QDir modDirectory(modInfo->absolutePath());
+ QDir gameDirectory(QDir::fromNativeSeparators(ToQString(MOShared::GameInfo::instance().getOverwriteDir())));
+
+ unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool {
+ std::vector<ModInfo::EFlag> flags = mod->getFlags();
+ return std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end(); });
+
+ QString overwriteName = ModInfo::getByIndex(overwriteIndex)->name();
+
+ foreach (const QUrl &url, mimeData->urls()) {
+ QString relativePath = gameDirectory.relativeFilePath(url.toLocalFile());
+ if (relativePath.startsWith("..")) {
+ qDebug("%s drop ignored", qPrintable(url.toLocalFile()));
+ continue;
+ }
+ source.append(url.toLocalFile());
+ target.append(modDirectory.absoluteFilePath(relativePath));
+ emit fileMoved(relativePath, overwriteName, modInfo->name());
+ }
+
+ if (source.count() != 0) {
+ shellMove(source, target);
+ }
+
+ return true;
+}
+
+
+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<int> sourceRows;
+
+ while (!stream.atEnd()) {
+ int sourceRow, col;
+ QMap<int, QVariant> roleDataMap;
+ stream >> sourceRow >> col >> roleDataMap;
+ if (col == 0) {
+ sourceRows.push_back(sourceRow);
+ }
+ }
+
+ if (row == -1) {
+ row = parent.row();
+ }
+
+ if ((row < 0) || (static_cast<unsigned int>(row) >= ModInfo::getNumMods())) {
+ return false;
+ }
+
+ int newPriority = 0;
+ {
+ if ((row < 0) || (row > static_cast<int>(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;
+}
+
+
+bool ModList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent)
+{
+ if (action == Qt::IgnoreAction) {
+ return true;
+ }
+
+ if (m_Profile == NULL) return false;
+ if (mimeData->hasUrls()) {
+ return dropURLs(mimeData, row, parent);
+ } else {
+ return dropMod(mimeData, row, parent);
+ }
+
+}
+
+
+void ModList::removeRowForce(int row)
+{
+ if (static_cast<unsigned int>(row) >= ModInfo::getNumMods()) {
+ return;
+ }
+ if (m_Profile == NULL) return;
+
+ m_Profile->setModEnabled(row, false);
+
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
+
+ bool wasEnabled = m_Profile->modEnabled(row);
+ beginRemoveRows(QModelIndex(), row, row);
+
+ m_Profile->cancelWriteModlist(); // don't write modlist while we're changing it
+ ModInfo::removeMod(row);
+ 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
+
+ endRemoveRows();
+ if (wasEnabled) {
+ emit removeOrigin(modInfo->name());
+ }
+
+ emit modUninstalled(modInfo->getInstallationFile());
+}
+
+
+void ModList::removeRow(int row, const QModelIndex&)
+{
+ if (static_cast<unsigned int>(row) >= ModInfo::getNumMods()) {
+ return;
+ }
+ if (m_Profile == NULL) return;
+
+ m_Profile->setModEnabled(row, false);
+
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
+ if (!modInfo->isRegular()) return;
+
+ 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) {
+ removeRowForce(row);
+ }
+}
+
+
+void ModList::notifyChange(int rowStart, int rowEnd)
+{
+ if (rowStart < 0) {
+ 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::parent(const QModelIndex&) const
+{
+ return QModelIndex();
+}
+
+QMap<int, QVariant> ModList::itemData(const QModelIndex &index) const
+{
+ QMap<int, QVariant> result = QAbstractItemModel::itemData(index);
+ result[Qt::UserRole] = data(index, Qt::UserRole);
+ return result;
+}
+
+
+void ModList::dropModeUpdate(bool dropOnItems)
+{
+ if (m_DropOnItems != dropOnItems) {
+ m_DropOnItems = dropOnItems;
+ }
+}
+
+
+QString ModList::getColumnName(int column)
+{
+ switch (column) {
+ case COL_FLAGS: return tr("Flags");
+ 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_INSTALLTIME: return tr("Time this mod was installed");
+ default: return tr("unknown");
+ }
+}
+
+
+bool ModList::eventFilter(QObject *obj, QEvent *event)
+{
+ if (event->type() == QEvent::ContextMenu) {
+ QContextMenuEvent *contextEvent = static_cast<QContextMenuEvent*>(event);
+ QWidget *object = qobject_cast<QWidget*>(obj);
+ if ((object != NULL) && (contextEvent->reason() == QContextMenuEvent::Mouse)) {
+ emit requestColumnSelect(object->mapToGlobal(contextEvent->pos()));
+
+ return true;
+ }
+ } else if ((event->type() == QEvent::KeyPress) && (m_Profile != NULL)) {
+ QAbstractItemView *itemView = qobject_cast<QAbstractItemView*>(obj);
+ QKeyEvent *keyEvent = static_cast<QKeyEvent*>(event);
+ if ((itemView != NULL)
+ && (keyEvent->modifiers() == Qt::ControlModifier)
+ && ((keyEvent->key() == Qt::Key_Up)
+ || (keyEvent->key() == Qt::Key_Down))) {
+ QItemSelectionModel *selectionModel = itemView->selectionModel();
+ const QAbstractProxyModel *proxyModel = qobject_cast<const QAbstractProxyModel*>(selectionModel->model());
+ const QSortFilterProxyModel *filterModel = NULL;
+ while ((filterModel == NULL) && (proxyModel != NULL)) {
+ filterModel = qobject_cast<const QSortFilterProxyModel*>(proxyModel);
+ if (filterModel == NULL) {
+ proxyModel = qobject_cast<const QAbstractProxyModel*>(proxyModel->sourceModel());
+ }
+ }
+ if (filterModel == NULL) {
+ return true;
+ }
+ int diff = -1;
+ if (((keyEvent->key() == Qt::Key_Up) && (filterModel->sortOrder() == Qt::DescendingOrder)) ||
+ ((keyEvent->key() == Qt::Key_Down) && (filterModel->sortOrder() == Qt::AscendingOrder))) {
+ diff = 1;
+ }
+ QModelIndexList rows = selectionModel->selectedRows();
+ if (keyEvent->key() == Qt::Key_Down) {
+ for (int i = 0; i < rows.size() / 2; ++i) {
+ rows.swap(i, rows.size() - i - 1);
+ }
+ }
+ foreach (QModelIndex idx, rows) {
+ if (filterModel != NULL) {
+ idx = filterModel->mapToSource(idx);
+ }
+ int newPriority = m_Profile->getModPriority(idx.row()) + diff;
+ if ((newPriority >= 0) && (newPriority < static_cast<int>(m_Profile->numRegularMods()))) {
+ m_Profile->setModPriority(idx.row(), newPriority);
+ notifyChange(idx.row());
+ }
+ }
+ return true;
+ } else if (keyEvent->key() == Qt::Key_Delete) {
+ 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;
+ } else if (keyEvent->key() == Qt::Key_Space) {
+ QItemSelectionModel *selectionModel = itemView->selectionModel();
+ const QSortFilterProxyModel *proxyModel = qobject_cast<const QSortFilterProxyModel*>(selectionModel->model());
+
+ QModelIndex minRow, maxRow;
+ foreach (QModelIndex idx, selectionModel->selectedRows()) {
+ if (proxyModel != NULL) {
+ 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 QObject::eventFilter(obj, event);
+}
diff --git a/src/modlist.h b/src/modlist.h index 18342aa5..2e8d6a84 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -1,293 +1,293 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef MODLIST_H -#define MODLIST_H - - -#include "categories.h" -#include "nexusinterface.h" -#include "modinfo.h" -#include "profile.h" - -#include <imodlist.h> - -#include <QFile> -#include <QListWidget> -#include <QNetworkReply> -#include <QNetworkAccessManager> -#include <boost/signals2.hpp> -#include <set> -#include <vector> -#include <QVector> - - -/** - * Model presenting an overview of the installed mod - * This is used in a view in the main window of MO. It combines general information about - * the mods from ModInfo with status information from the Profile - **/ -class ModList : public QAbstractItemModel, public MOBase::IModList -{ - Q_OBJECT - -public: - - enum EColumn { - COL_NAME, - COL_FLAGS, - COL_CATEGORY, - COL_MODID, - COL_VERSION, - COL_INSTALLTIME, - COL_PRIORITY, - - COL_LASTCOLUMN = COL_PRIORITY - }; - - typedef boost::signals2::signal<void (const QString &, ModStates)> SignalModStateChanged; - typedef boost::signals2::signal<void (const QString &, int, int)> 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 = NULL); - - /** - * @brief set the profile used for status information - * - * @param profile the profile to use - **/ - void setProfile(Profile *profile); - - /** - * @brief retrieve the current sorting mode - * @note this is used to store the sorting mode between sessions - * @return current sorting mode, encoded to be compatible to previous versions - **/ - int getCurrentSortingMode() const; - - /** - * @brief remove the specified mod without asking for confirmation - * @param row the row to remove - */ - void removeRowForce(int row); - - void notifyChange(int rowStart, int rowEnd = -1); - static QString getColumnName(int column); - - void changeModPriority(int sourceIndex, int newPriority); - - void setOverwriteMarkers(const std::set<unsigned int> &overwrite, const std::set<unsigned int> &overwritten); - - void modInfoAboutToChange(ModInfo::Ptr info); - void modInfoChanged(ModInfo::Ptr info); - -public: - - /// \copydoc MOBase::IModList::state - virtual ModStates state(const QString &name) const; - - /// \copydoc MOBase::IModList::priority - virtual int priority(const QString &name) const; - - /// \copydoc MOBase::IModList::setPriority - virtual bool setPriority(const QString &name, int newPriority); - - /// \copydoc MOBase::IModList::onModStateChanged - virtual bool onModStateChanged(const std::function<void (const QString &, ModStates)> &func); - - /// \copydoc MOBase::IModList::onModMoved - virtual bool onModMoved(const std::function<void (const QString &, int, int)> &func); - -public: // implementation of virtual functions of QAbstractItemModel - - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual bool hasChildren(const QModelIndex &parent = QModelIndex()) const; - virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; - virtual Qt::DropActions supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; } - virtual QStringList mimeTypes() const; - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - virtual void removeRow(int row, const QModelIndex &parent); - - virtual QModelIndex index(int row, int column, - const QModelIndex &parent = QModelIndex()) const; - virtual QModelIndex parent(const QModelIndex &child) const; - - virtual QMap<int, QVariant> itemData(const QModelIndex &index) const; - -public slots: - - 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); - -protected: - - // 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<int> sourceIndices, int newPriority); - - QVariant getOverwriteData(int column, int role) const; - - QString getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const; - - static QString getColumnToolTip(int column); - - ModList::EColumn getEnabledColumn(int index) const; - - QVariant categoryData(int categoryID, int column, int role) const; - QVariant modData(int modID, int modelColumn, int role) const; - - bool renameMod(int index, const QString &newName); - - bool dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent); - - bool dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent); - - ModStates state(unsigned int modIndex) const; - -private slots: - -private: - - struct TModInfo { - TModInfo(unsigned int index, ModInfo::Ptr modInfo) - : modInfo(modInfo), nameOrder(index) {} - ModInfo::Ptr modInfo; - unsigned int nameOrder; - unsigned int priorityOrder; - unsigned int modIDOrder; - unsigned int categoryOrder; - }; - - struct TModInfoChange { - QString name; - QFlags<IModList::ModState> state; - }; - -private: - - Profile *m_Profile; - - NexusInterface *m_NexusInterface; - std::set<int> m_RequestIDs; - - mutable bool m_Modified; - - QFontMetrics m_FontMetrics; - - bool m_DropOnItems; - - std::set<unsigned int> m_Overwrite; - std::set<unsigned int> m_Overwritten; - - TModInfoChange m_ChangeInfo; - - SignalModStateChanged m_ModStateChanged; - SignalModMoved m_ModMoved; - -}; - -#endif // MODLIST_H - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef MODLIST_H
+#define MODLIST_H
+
+
+#include "categories.h"
+#include "nexusinterface.h"
+#include "modinfo.h"
+#include "profile.h"
+
+#include <imodlist.h>
+
+#include <QFile>
+#include <QListWidget>
+#include <QNetworkReply>
+#include <QNetworkAccessManager>
+#include <boost/signals2.hpp>
+#include <set>
+#include <vector>
+#include <QVector>
+
+
+/**
+ * Model presenting an overview of the installed mod
+ * This is used in a view in the main window of MO. It combines general information about
+ * the mods from ModInfo with status information from the Profile
+ **/
+class ModList : public QAbstractItemModel, public MOBase::IModList
+{
+ Q_OBJECT
+
+public:
+
+ enum EColumn {
+ COL_NAME,
+ COL_FLAGS,
+ COL_CATEGORY,
+ COL_MODID,
+ COL_VERSION,
+ COL_INSTALLTIME,
+ COL_PRIORITY,
+
+ COL_LASTCOLUMN = COL_PRIORITY
+ };
+
+ typedef boost::signals2::signal<void (const QString &, ModStates)> SignalModStateChanged;
+ typedef boost::signals2::signal<void (const QString &, int, int)> 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 = NULL);
+
+ /**
+ * @brief set the profile used for status information
+ *
+ * @param profile the profile to use
+ **/
+ void setProfile(Profile *profile);
+
+ /**
+ * @brief retrieve the current sorting mode
+ * @note this is used to store the sorting mode between sessions
+ * @return current sorting mode, encoded to be compatible to previous versions
+ **/
+ int getCurrentSortingMode() const;
+
+ /**
+ * @brief remove the specified mod without asking for confirmation
+ * @param row the row to remove
+ */
+ void removeRowForce(int row);
+
+ void notifyChange(int rowStart, int rowEnd = -1);
+ static QString getColumnName(int column);
+
+ void changeModPriority(int sourceIndex, int newPriority);
+
+ void setOverwriteMarkers(const std::set<unsigned int> &overwrite, const std::set<unsigned int> &overwritten);
+
+ void modInfoAboutToChange(ModInfo::Ptr info);
+ void modInfoChanged(ModInfo::Ptr info);
+
+public:
+
+ /// \copydoc MOBase::IModList::state
+ virtual ModStates state(const QString &name) const;
+
+ /// \copydoc MOBase::IModList::priority
+ virtual int priority(const QString &name) const;
+
+ /// \copydoc MOBase::IModList::setPriority
+ virtual bool setPriority(const QString &name, int newPriority);
+
+ /// \copydoc MOBase::IModList::onModStateChanged
+ virtual bool onModStateChanged(const std::function<void (const QString &, ModStates)> &func);
+
+ /// \copydoc MOBase::IModList::onModMoved
+ virtual bool onModMoved(const std::function<void (const QString &, int, int)> &func);
+
+public: // implementation of virtual functions of QAbstractItemModel
+
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual bool hasChildren(const QModelIndex &parent = QModelIndex()) const;
+ virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+ virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const;
+ virtual Qt::DropActions supportedDropActions() const { return Qt::MoveAction | Qt::CopyAction; }
+ virtual QStringList mimeTypes() const;
+ virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
+ virtual void removeRow(int row, const QModelIndex &parent);
+
+ virtual QModelIndex index(int row, int column,
+ const QModelIndex &parent = QModelIndex()) const;
+ virtual QModelIndex parent(const QModelIndex &child) const;
+
+ virtual QMap<int, QVariant> itemData(const QModelIndex &index) const;
+
+public slots:
+
+ 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);
+
+protected:
+
+ // 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<int> sourceIndices, int newPriority);
+
+ QVariant getOverwriteData(int column, int role) const;
+
+ QString getFlagText(ModInfo::EFlag flag, ModInfo::Ptr modInfo) const;
+
+ static QString getColumnToolTip(int column);
+
+ ModList::EColumn getEnabledColumn(int index) const;
+
+ QVariant categoryData(int categoryID, int column, int role) const;
+ QVariant modData(int modID, int modelColumn, int role) const;
+
+ bool renameMod(int index, const QString &newName);
+
+ bool dropURLs(const QMimeData *mimeData, int row, const QModelIndex &parent);
+
+ bool dropMod(const QMimeData *mimeData, int row, const QModelIndex &parent);
+
+ ModStates state(unsigned int modIndex) const;
+
+private slots:
+
+private:
+
+ struct TModInfo {
+ TModInfo(unsigned int index, ModInfo::Ptr modInfo)
+ : modInfo(modInfo), nameOrder(index) {}
+ ModInfo::Ptr modInfo;
+ unsigned int nameOrder;
+ unsigned int priorityOrder;
+ unsigned int modIDOrder;
+ unsigned int categoryOrder;
+ };
+
+ struct TModInfoChange {
+ QString name;
+ QFlags<IModList::ModState> state;
+ };
+
+private:
+
+ Profile *m_Profile;
+
+ NexusInterface *m_NexusInterface;
+ std::set<int> m_RequestIDs;
+
+ mutable bool m_Modified;
+
+ QFontMetrics m_FontMetrics;
+
+ bool m_DropOnItems;
+
+ std::set<unsigned int> m_Overwrite;
+ std::set<unsigned int> m_Overwritten;
+
+ TModInfoChange m_ChangeInfo;
+
+ SignalModStateChanged m_ModStateChanged;
+ SignalModMoved m_ModMoved;
+
+};
+
+#endif // MODLIST_H
+
diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp index 88b7bef0..77702689 100644 --- a/src/modlistsortproxy.cpp +++ b/src/modlistsortproxy.cpp @@ -1,376 +1,376 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "modlistsortproxy.h" -#include "modinfo.h" -#include "profile.h" -#include "modlist.h" -#include <QMenu> -#include <QCheckBox> -#include <QWidgetAction> -#include <QMessageBox> - - -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::updateFilterActive() -{ - m_FilterActive = (m_CategoryFilter.size() > 0) || !m_CurrentFilter.isEmpty(); - emit filterActive(m_FilterActive); -} - -void ModListSortProxy::setCategoryFilter(const std::vector<int> &categories) -{ - m_CategoryFilter = categories; - updateFilterActive(); - this->invalidate(); -} - -Qt::ItemFlags ModListSortProxy::flags(const QModelIndex &modelIndex) const -{ - Qt::ItemFlags flags = sourceModel()->flags(mapToSource(modelIndex)); - if (sortColumn() != ModList::COL_PRIORITY) { - flags &= ~Qt::ItemIsDragEnabled; - } - - return flags; -} - - -void ModListSortProxy::displayColumnSelection(const QPoint &pos) -{ - QMenu menu; - - for (int i = 0; i <= ModList::COL_LASTCOLUMN; ++i) { - QCheckBox *checkBox = new QCheckBox(&menu); - checkBox->setText(ModList::getColumnName(i)); - checkBox->setChecked(m_EnabledColumns.test(i) ? Qt::Checked : Qt::Unchecked); - QWidgetAction *checkableAction = new QWidgetAction(&menu); - checkableAction->setDefaultWidget(checkBox); - menu.addAction(checkableAction); - } - menu.exec(pos); - int i = 0; - - emit layoutAboutToBeChanged(); - m_EnabledColumns.reset(); - foreach (const QAction *action, menu.actions()) { - const QWidgetAction *widgetAction = qobject_cast<const QWidgetAction*>(action); - if (widgetAction != NULL) { - const QCheckBox *checkBox = qobject_cast<const QCheckBox*>(widgetAction->defaultWidget()); - if (checkBox != NULL) { - m_EnabledColumns.set(i, checkBox->checkState() == Qt::Checked); - } - } - ++i; - } - emit layoutChanged(); -} - - -void ModListSortProxy::enableAllVisible() -{ - if (m_Profile == NULL) 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(); -} - - -void ModListSortProxy::disableAllVisible() -{ - if (m_Profile == NULL) 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(); -} - - -bool ModListSortProxy::lessThan(const QModelIndex &left, - const QModelIndex &right) const -{ - 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); - - 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); - - lt = leftPrio.toInt() < rightPrio.toInt(); - } - - switch (left.column()) { - case ModList::COL_FLAGS: { - if (leftMod->getFlags().size() != rightMod->getFlags().size()) - lt = leftMod->getFlags().size() < rightMod->getFlags().size(); - } break; - case ModList::COL_NAME: { - int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive); - if (comp != 0) - lt = comp < 0; - } break; - case ModList::COL_CATEGORY: { - if (leftMod->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(); - } break; - case ModList::COL_VERSION: { - 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; - } break; - case ModList::COL_PRIORITY: { - // nop, already compared by priority - } break; - } - return lt; -} - - -void ModListSortProxy::updateFilter(const QString &filter) -{ - m_CurrentFilter = filter; - updateFilterActive(); - invalidateFilter(); -} - - -bool ModListSortProxy::hasConflictFlag(const std::vector<ModInfo::EFlag> &flags) const -{ - foreach (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; -} - - -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; - } - } - 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; - } - } - 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 (m_CategoryFilter.size() == 0) || filterMatchesModOr(info, enabled); - } -} - -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 == NULL) { - return false; - } - - if (row >= static_cast<int>(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; - } - if (idx.isValid() && 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); - } -} - - -bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action, - int row, int column, const QModelIndex &parent) -{ - if ((row == -1) && (column == -1)) { - return this->sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); - } - // in the regular model, when dropping between rows, the row-value passed to - // the sourceModel is inconsistent between ascending and descending ordering. - // This should fix that - if (sortOrder() == Qt::DescendingOrder) { - --row; - } - - QModelIndex proxyIndex = index(row, column, parent); - QModelIndex sourceIndex = mapToSource(proxyIndex); - return this->sourceModel()->dropMimeData(data, action, sourceIndex.row(), sourceIndex.column(), - sourceIndex.parent()); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "modlistsortproxy.h"
+#include "modinfo.h"
+#include "profile.h"
+#include "modlist.h"
+#include <QMenu>
+#include <QCheckBox>
+#include <QWidgetAction>
+#include <QMessageBox>
+
+
+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::updateFilterActive()
+{
+ m_FilterActive = (m_CategoryFilter.size() > 0) || !m_CurrentFilter.isEmpty();
+ emit filterActive(m_FilterActive);
+}
+
+void ModListSortProxy::setCategoryFilter(const std::vector<int> &categories)
+{
+ m_CategoryFilter = categories;
+ updateFilterActive();
+ this->invalidate();
+}
+
+Qt::ItemFlags ModListSortProxy::flags(const QModelIndex &modelIndex) const
+{
+ Qt::ItemFlags flags = sourceModel()->flags(mapToSource(modelIndex));
+ if (sortColumn() != ModList::COL_PRIORITY) {
+ flags &= ~Qt::ItemIsDragEnabled;
+ }
+
+ return flags;
+}
+
+
+void ModListSortProxy::displayColumnSelection(const QPoint &pos)
+{
+ QMenu menu;
+
+ for (int i = 0; i <= ModList::COL_LASTCOLUMN; ++i) {
+ QCheckBox *checkBox = new QCheckBox(&menu);
+ checkBox->setText(ModList::getColumnName(i));
+ checkBox->setChecked(m_EnabledColumns.test(i) ? Qt::Checked : Qt::Unchecked);
+ QWidgetAction *checkableAction = new QWidgetAction(&menu);
+ checkableAction->setDefaultWidget(checkBox);
+ menu.addAction(checkableAction);
+ }
+ menu.exec(pos);
+ int i = 0;
+
+ emit layoutAboutToBeChanged();
+ m_EnabledColumns.reset();
+ foreach (const QAction *action, menu.actions()) {
+ const QWidgetAction *widgetAction = qobject_cast<const QWidgetAction*>(action);
+ if (widgetAction != NULL) {
+ const QCheckBox *checkBox = qobject_cast<const QCheckBox*>(widgetAction->defaultWidget());
+ if (checkBox != NULL) {
+ m_EnabledColumns.set(i, checkBox->checkState() == Qt::Checked);
+ }
+ }
+ ++i;
+ }
+ emit layoutChanged();
+}
+
+
+void ModListSortProxy::enableAllVisible()
+{
+ if (m_Profile == NULL) 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();
+}
+
+
+void ModListSortProxy::disableAllVisible()
+{
+ if (m_Profile == NULL) 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();
+}
+
+
+bool ModListSortProxy::lessThan(const QModelIndex &left,
+ const QModelIndex &right) const
+{
+ 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);
+
+ 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);
+
+ lt = leftPrio.toInt() < rightPrio.toInt();
+ }
+
+ switch (left.column()) {
+ case ModList::COL_FLAGS: {
+ if (leftMod->getFlags().size() != rightMod->getFlags().size())
+ lt = leftMod->getFlags().size() < rightMod->getFlags().size();
+ } break;
+ case ModList::COL_NAME: {
+ int comp = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive);
+ if (comp != 0)
+ lt = comp < 0;
+ } break;
+ case ModList::COL_CATEGORY: {
+ if (leftMod->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();
+ } break;
+ case ModList::COL_VERSION: {
+ 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;
+ } break;
+ case ModList::COL_PRIORITY: {
+ // nop, already compared by priority
+ } break;
+ }
+ return lt;
+}
+
+
+void ModListSortProxy::updateFilter(const QString &filter)
+{
+ m_CurrentFilter = filter;
+ updateFilterActive();
+ invalidateFilter();
+}
+
+
+bool ModListSortProxy::hasConflictFlag(const std::vector<ModInfo::EFlag> &flags) const
+{
+ foreach (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;
+}
+
+
+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;
+ }
+ }
+ 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;
+ }
+ }
+ 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 (m_CategoryFilter.size() == 0) || filterMatchesModOr(info, enabled);
+ }
+}
+
+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 == NULL) {
+ return false;
+ }
+
+ if (row >= static_cast<int>(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;
+ }
+ if (idx.isValid() && 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);
+ }
+}
+
+
+bool ModListSortProxy::dropMimeData(const QMimeData *data, Qt::DropAction action,
+ int row, int column, const QModelIndex &parent)
+{
+ if ((row == -1) && (column == -1)) {
+ return this->sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent));
+ }
+ // in the regular model, when dropping between rows, the row-value passed to
+ // the sourceModel is inconsistent between ascending and descending ordering.
+ // This should fix that
+ if (sortOrder() == Qt::DescendingOrder) {
+ --row;
+ }
+
+ QModelIndex proxyIndex = index(row, column, parent);
+ QModelIndex sourceIndex = mapToSource(proxyIndex);
+ return this->sourceModel()->dropMimeData(data, action, sourceIndex.row(), sourceIndex.column(),
+ sourceIndex.parent());
+}
diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index c04ba28d..fc7edcd5 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -1,121 +1,121 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef MODLISTSORTPROXY_H -#define MODLISTSORTPROXY_H - -#include <QSortFilterProxyModel> -#include <bitset> -#include "modlist.h" - -class Profile; - -class ModListSortProxy : public QSortFilterProxyModel -{ - Q_OBJECT - -public: - - enum FilterMode { - FILTER_AND, - FILTER_OR - }; - -public: - - explicit ModListSortProxy(Profile *profile, QObject *parent = 0); - - void setProfile(Profile *profile); - - void setCategoryFilter(const std::vector<int> &categories); - - virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, - int row, int column, const QModelIndex &parent); - - /** - * @brief enable all mods visible under the current filter - **/ - void enableAllVisible(); - - /** - * @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; - - /** - * @return true if a filter is currently active - */ - bool isFilterActive() const { return m_FilterActive; } - - 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; - } - - -public slots: - - void displayColumnSelection(const QPoint &pos); - void updateFilter(const QString &filter); - -signals: - - void filterActive(bool active); - -protected: - - virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; - virtual bool filterAcceptsRow(int row, const QModelIndex &parent) const; - -private: - - bool hasConflictFlag(const std::vector<ModInfo::EFlag> &flags) const; - void updateFilterActive(); - bool filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const; - bool filterMatchesModOr(ModInfo::Ptr info, bool enabled) const; - -private: - - Profile *m_Profile; - - std::vector<int> m_CategoryFilter; - std::bitset<ModList::COL_LASTCOLUMN + 1> m_EnabledColumns; - QString m_CurrentFilter; - - bool m_FilterActive; - FilterMode m_FilterMode; - -}; - -#endif // MODLISTSORTPROXY_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef MODLISTSORTPROXY_H
+#define MODLISTSORTPROXY_H
+
+#include <QSortFilterProxyModel>
+#include <bitset>
+#include "modlist.h"
+
+class Profile;
+
+class ModListSortProxy : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+
+ enum FilterMode {
+ FILTER_AND,
+ FILTER_OR
+ };
+
+public:
+
+ explicit ModListSortProxy(Profile *profile, QObject *parent = 0);
+
+ void setProfile(Profile *profile);
+
+ void setCategoryFilter(const std::vector<int> &categories);
+
+ virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const;
+ virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action,
+ int row, int column, const QModelIndex &parent);
+
+ /**
+ * @brief enable all mods visible under the current filter
+ **/
+ void enableAllVisible();
+
+ /**
+ * @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;
+
+ /**
+ * @return true if a filter is currently active
+ */
+ bool isFilterActive() const { return m_FilterActive; }
+
+ 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;
+ }
+
+
+public slots:
+
+ void displayColumnSelection(const QPoint &pos);
+ void updateFilter(const QString &filter);
+
+signals:
+
+ void filterActive(bool active);
+
+protected:
+
+ virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
+ virtual bool filterAcceptsRow(int row, const QModelIndex &parent) const;
+
+private:
+
+ bool hasConflictFlag(const std::vector<ModInfo::EFlag> &flags) const;
+ void updateFilterActive();
+ bool filterMatchesModAnd(ModInfo::Ptr info, bool enabled) const;
+ bool filterMatchesModOr(ModInfo::Ptr info, bool enabled) const;
+
+private:
+
+ Profile *m_Profile;
+
+ std::vector<int> m_CategoryFilter;
+ std::bitset<ModList::COL_LASTCOLUMN + 1> m_EnabledColumns;
+ QString m_CurrentFilter;
+
+ bool m_FilterActive;
+ FilterMode m_FilterMode;
+
+};
+
+#endif // MODLISTSORTPROXY_H
diff --git a/src/motddialog.cpp b/src/motddialog.cpp index 84c67aa5..9fa2f005 100644 --- a/src/motddialog.cpp +++ b/src/motddialog.cpp @@ -1,49 +1,49 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "motddialog.h" -#include "bbcode.h" -#include "utility.h" -#include "ui_motddialog.h" -#include <Shlwapi.h> - -MotDDialog::MotDDialog(const QString &message, QWidget *parent) - : QDialog(parent), ui(new Ui::MotDDialog) -{ - ui->setupUi(this); - ui->motdView->setHtml(BBCode::convertToHTML(message)); -// ui->motdView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); -// connect(ui->motdView->page(), SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl))); - connect(ui->motdView, SIGNAL(anchorClicked(QUrl)), this, SLOT(linkClicked(QUrl))); -} - -MotDDialog::~MotDDialog() -{ - delete ui; -} - -void MotDDialog::on_okButton_clicked() -{ - this->close(); -} - -void MotDDialog::linkClicked(const QUrl &url) -{ - ::ShellExecuteW(NULL, L"open", MOBase::ToWString(url.toString()).c_str(), NULL, NULL, SW_SHOWNORMAL); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "motddialog.h"
+#include "bbcode.h"
+#include "utility.h"
+#include "ui_motddialog.h"
+#include <Shlwapi.h>
+
+MotDDialog::MotDDialog(const QString &message, QWidget *parent)
+ : QDialog(parent), ui(new Ui::MotDDialog)
+{
+ ui->setupUi(this);
+ ui->motdView->setHtml(BBCode::convertToHTML(message));
+// ui->motdView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
+// connect(ui->motdView->page(), SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl)));
+ connect(ui->motdView, SIGNAL(anchorClicked(QUrl)), this, SLOT(linkClicked(QUrl)));
+}
+
+MotDDialog::~MotDDialog()
+{
+ delete ui;
+}
+
+void MotDDialog::on_okButton_clicked()
+{
+ this->close();
+}
+
+void MotDDialog::linkClicked(const QUrl &url)
+{
+ ::ShellExecuteW(NULL, L"open", MOBase::ToWString(url.toString()).c_str(), NULL, NULL, SW_SHOWNORMAL);
+}
diff --git a/src/motddialog.h b/src/motddialog.h index 1e9a5078..5791321f 100644 --- a/src/motddialog.h +++ b/src/motddialog.h @@ -1,30 +1,30 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#ifndef MOTDDIALOG_H
-#define MOTDDIALOG_H - -#include <QDialog> -#include <QUrl> - -namespace Ui { -class MotDDialog; +#define MOTDDIALOG_H
+
+#include <QDialog>
+#include <QUrl>
+
+namespace Ui {
+class MotDDialog;
}
class MotDDialog : public QDialog
@@ -34,13 +34,13 @@ class MotDDialog : public QDialog public:
explicit MotDDialog(const QString &message, QWidget *parent = 0);
~MotDDialog();
- -private slots: - void on_okButton_clicked(); - void linkClicked(const QUrl &url); - -private: - Ui::MotDDialog *ui; +
+private slots:
+ void on_okButton_clicked();
+ void linkClicked(const QUrl &url);
+
+private:
+ Ui::MotDDialog *ui;
};
#endif // MOTDDIALOG_H
diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp index 48426834..c7e2df9e 100644 --- a/src/nexusinterface.cpp +++ b/src/nexusinterface.cpp @@ -1,622 +1,622 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "nexusinterface.h" -#include "nxmaccessmanager.h" -#include "utility.h" -#include <json.h> -#include "selectiondialog.h" -#include <QApplication> -#include <utility.h> -#include <regex> -#include <util.h> - - -using namespace MOBase; -using namespace MOShared; - - -NexusBridge::NexusBridge(const QString &subModule) - : m_Interface(NexusInterface::instance()) - , m_Url(MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())) - , m_SubModule(subModule) -{ -} - - -void NexusBridge::requestDescription(int modID, QVariant userData) -{ - m_RequestIDs.insert(m_Interface->requestDescription(modID, this, userData, m_SubModule, m_Url)); -} - -void NexusBridge::requestFiles(int modID, QVariant userData) -{ - m_RequestIDs.insert(m_Interface->requestFiles(modID, this, userData, m_SubModule, m_Url)); -} - -void NexusBridge::requestFileInfo(int modID, int fileID, QVariant userData) -{ - m_RequestIDs.insert(m_Interface->requestFileInfo(modID, fileID, this, userData, m_SubModule, m_Url)); -} - -void NexusBridge::requestDownloadURL(int modID, int fileID, QVariant userData) -{ - m_RequestIDs.insert(m_Interface->requestDownloadURL(modID, fileID, this, userData, m_SubModule, m_Url)); -} - -void NexusBridge::requestToggleEndorsement(int modID, bool endorse, QVariant userData) -{ - m_RequestIDs.insert(m_Interface->requestToggleEndorsement(modID, endorse, this, userData, m_SubModule, m_Url)); -} - -void NexusBridge::nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID) -{ - std::set<int>::iterator iter = m_RequestIDs.find(requestID); - if (iter != m_RequestIDs.end()) { - m_RequestIDs.erase(iter); - - emit descriptionAvailable(modID, userData, resultData); - } -} - -void NexusBridge::nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID) -{ - std::set<int>::iterator iter = m_RequestIDs.find(requestID); - if (iter != m_RequestIDs.end()) { - m_RequestIDs.erase(iter); - - QList<ModRepositoryFileInfo> fileInfoList; - - QVariantList resultList = resultData.toList(); - - foreach(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<int>::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<int>::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<int>::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<int>::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() -{ - atexit(&cleanup); - m_AccessManager = new NXMAccessManager(this); - - m_DiskCache = new QNetworkDiskCache(this); - connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); - - - VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); - - m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, - version.dwFileVersionMS & 0xFFFF, - version.dwFileVersionLS >> 16); -} - - -void NexusInterface::cleanup() -{ -} - - -NXMAccessManager *NexusInterface::getAccessManager() -{ - return m_AccessManager; -} - - -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; -} - -void NexusInterface::loginCompleted() -{ - nextRequest(); -} - - -void NexusInterface::interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query) -{ - static std::tr1::regex exp("^([a-zA-Z0-9_\\- ]*?)([-_ ][VvRr]?[0-9_]+)?-([1-9][0-9]+).*"); - static std::tr1::regex simpleexp("^([a-zA-Z0-9_]+)"); - - QByteArray fileNameUTF8 = fileName.toUtf8(); - std::tr1::cmatch result; - if (std::tr1::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... - unsigned 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) + offset, " *"); - QString r3Highlight(fileName); - r3Highlight.insert(result.position(3) + result.length(3), "* ").insert(result.position(3), " *"); - - selection.addChoice(candidate.c_str(), r3Highlight, strtol(candidate.c_str(), NULL, 10)); - selection.addChoice(candidate2.c_str() + offset, r2Highlight, abs(strtol(candidate2.c_str() + offset, NULL, 10))); - if (selection.exec() == QDialog::Accepted) { - modID = selection.getChoiceData().toInt(); - } else { - modID = -1; - } - } else { - modID = -1; - } - } else { - modID = strtol(candidate.c_str(), NULL, 10); - } - qDebug("mod id guessed: %s -> %d", qPrintable(fileName), modID); - } else if (std::tr1::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; - } -} - - -int NexusInterface::requestDescription(int modID, QObject *receiver, QVariant userData, - const QString &subModule, const QString &url, int nexusGameId) -{ - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_DESCRIPTION, userData, subModule, url, - nexusGameId == -1 ? GameInfo::instance().getNexusGameID() : nexusGameId); - m_RequestQueue.enqueue(requestInfo); - - connect(this, SIGNAL(nxmDescriptionAvailable(int,QVariant,QVariant,int)), - receiver, SLOT(nxmDescriptionAvailable(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; -} - - -int NexusInterface::requestUpdates(const std::vector<int> &modIDs, QObject *receiver, QVariant userData, - const QString &subModule, const QString &url) -{ - NXMRequestInfo requestInfo(modIDs, NXMRequestInfo::TYPE_GETUPDATES, userData, subModule, url, GameInfo::instance().getNexusGameID()); - m_RequestQueue.enqueue(requestInfo); - - connect(this, SIGNAL(nxmUpdatesAvailable(std::vector<int>,QVariant,QVariant,int)), - receiver, SLOT(nxmUpdatesAvailable(std::vector<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; -} - - -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); - - emit nxmFilesAvailable(1234, "fake", result, id++); -} - - -int NexusInterface::requestFiles(int modID, QObject *receiver, QVariant userData, - const QString &subModule, const QString &url) -{ - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_FILES, userData, subModule, url, GameInfo::instance().getNexusGameID()); - 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); - -// QTimer::singleShot(1000, this, SLOT(fakeFiles())); -// static int fID = 42; -// return fID++; - - nextRequest(); - return requestInfo.m_ID; -} - - -int NexusInterface::requestFileInfo(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule, - const QString &url) -{ - NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_FILEINFO, userData, subModule, url, GameInfo::instance().getNexusGameID()); - m_RequestQueue.enqueue(requestInfo); - - connect(this, SIGNAL(nxmFileInfoAvailable(int,int,QVariant,QVariant,int)), - receiver, SLOT(nxmFileInfoAvailable(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); - - nextRequest(); - return requestInfo.m_ID; -} - - -int NexusInterface::requestDownloadURL(int modID, int fileID, QObject *receiver, QVariant userData, - const QString &subModule, const QString &url) -{ - NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_DOWNLOADURL, userData, subModule, url, GameInfo::instance().getNexusGameID()); - 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); - - nextRequest(); - return requestInfo.m_ID; -} - - -int NexusInterface::requestToggleEndorsement(int modID, bool endorse, QObject *receiver, QVariant userData, - const QString &subModule, const QString &url) -{ - NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLEENDORSEMENT, userData, subModule, url, GameInfo::instance().getNexusGameID()); - 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); - - nextRequest(); - return requestInfo.m_ID; -} - -bool NexusInterface::requiresLogin(const NXMRequestInfo &info) -{ - return (info.m_Type == NXMRequestInfo::TYPE_TOGGLEENDORSEMENT) - || (info.m_Type == NXMRequestInfo::TYPE_DOWNLOADURL); -} - -void NexusInterface::nextRequest() -{ - if ((m_ActiveRequest.size() >= MAX_ACTIVE_DOWNLOADS) - || m_RequestQueue.isEmpty()) { - return; - } - - if (!getAccessManager()->loggedIn() - && requiresLogin(m_RequestQueue.head())) { - 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<int>(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; - } - QNetworkRequest request(url); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/xml"); - QStringList comments; - comments << "compatible to Nexus Client v" + m_NMMVersion; - if (!info.m_SubModule.isEmpty()) { - comments << "module: " + info.m_SubModule; - } - - QString userAgent = QString("Mod Organizer v%1 (%2)") - .arg(m_MOVersion.displayString()) - .arg(comments.join("; ")); - request.setRawHeader("User-Agent", userAgent.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<NXMRequestInfo>::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 { - 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<QNetworkReply*>(sender()); - for (std::list<NXMRequestInfo>::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<QNetworkReply*>(sender()); - if (reply == NULL) { - qWarning("invalid sender type"); - return; - } - - qCritical("request (%s) error: %s", - qPrintable(reply->url().toString()), qPrintable(reply->errorString())); -} - - -void NexusInterface::requestTimeout() -{ - QTimer *timer = qobject_cast<QTimer*>(sender()); - if (timer == NULL) { - qWarning("invalid sender type"); - return; - } - for (std::list<NXMRequestInfo>::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; - } - } -} - - -NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID - , NexusInterface::NXMRequestInfo::Type type - , QVariant userData - , const QString &subModule - , const QString &url - , int nexusGameId) - : m_ModID(modID) - , m_FileID(0) - , m_Reply(NULL) - , m_Type(type) - , m_UserData(userData) - , m_Timeout(NULL) - , m_Reroute(false) - , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(url) - , m_SubModule(subModule) - , m_NexusGameID(nexusGameId) -{} - -NexusInterface::NXMRequestInfo::NXMRequestInfo(std::vector<int> modIDList - , NexusInterface::NXMRequestInfo::Type type - , QVariant userData - , const QString &subModule - , const QString &url - , int nexusGameId) - : m_ModID(-1) - , m_ModIDList(modIDList) - , m_FileID(0) - , m_Reply(NULL) - , m_Type(type) - , m_UserData(userData) - , m_Timeout(NULL) - , m_Reroute(false) - , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(url) - , m_SubModule(subModule) - , m_NexusGameID(nexusGameId) -{} - -NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID - , int fileID - , NexusInterface::NXMRequestInfo::Type type - , QVariant userData - , const QString &subModule - , const QString &url - , int nexusGameId) - : m_ModID(modID) - , m_FileID(fileID) - , m_Reply(NULL) - , m_Type(type) - , m_UserData(userData) - , m_Timeout(NULL) - , m_Reroute(false) - , m_ID(s_NextID.fetchAndAddAcquire(1)) - , m_URL(url) - , m_SubModule(subModule) - , m_NexusGameID(nexusGameId) -{} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "nexusinterface.h"
+#include "nxmaccessmanager.h"
+#include "utility.h"
+#include <json.h>
+#include "selectiondialog.h"
+#include <QApplication>
+#include <utility.h>
+#include <regex>
+#include <util.h>
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+NexusBridge::NexusBridge(const QString &subModule)
+ : m_Interface(NexusInterface::instance())
+ , m_Url(MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl()))
+ , m_SubModule(subModule)
+{
+}
+
+
+void NexusBridge::requestDescription(int modID, QVariant userData)
+{
+ m_RequestIDs.insert(m_Interface->requestDescription(modID, this, userData, m_SubModule, m_Url));
+}
+
+void NexusBridge::requestFiles(int modID, QVariant userData)
+{
+ m_RequestIDs.insert(m_Interface->requestFiles(modID, this, userData, m_SubModule, m_Url));
+}
+
+void NexusBridge::requestFileInfo(int modID, int fileID, QVariant userData)
+{
+ m_RequestIDs.insert(m_Interface->requestFileInfo(modID, fileID, this, userData, m_SubModule, m_Url));
+}
+
+void NexusBridge::requestDownloadURL(int modID, int fileID, QVariant userData)
+{
+ m_RequestIDs.insert(m_Interface->requestDownloadURL(modID, fileID, this, userData, m_SubModule, m_Url));
+}
+
+void NexusBridge::requestToggleEndorsement(int modID, bool endorse, QVariant userData)
+{
+ m_RequestIDs.insert(m_Interface->requestToggleEndorsement(modID, endorse, this, userData, m_SubModule, m_Url));
+}
+
+void NexusBridge::nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID)
+{
+ std::set<int>::iterator iter = m_RequestIDs.find(requestID);
+ if (iter != m_RequestIDs.end()) {
+ m_RequestIDs.erase(iter);
+
+ emit descriptionAvailable(modID, userData, resultData);
+ }
+}
+
+void NexusBridge::nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID)
+{
+ std::set<int>::iterator iter = m_RequestIDs.find(requestID);
+ if (iter != m_RequestIDs.end()) {
+ m_RequestIDs.erase(iter);
+
+ QList<ModRepositoryFileInfo> fileInfoList;
+
+ QVariantList resultList = resultData.toList();
+
+ foreach(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<int>::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<int>::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<int>::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<int>::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()
+{
+ atexit(&cleanup);
+ m_AccessManager = new NXMAccessManager(this);
+
+ m_DiskCache = new QNetworkDiskCache(this);
+ connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString)));
+
+
+ VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath()));
+
+ m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16,
+ version.dwFileVersionMS & 0xFFFF,
+ version.dwFileVersionLS >> 16);
+}
+
+
+void NexusInterface::cleanup()
+{
+}
+
+
+NXMAccessManager *NexusInterface::getAccessManager()
+{
+ return m_AccessManager;
+}
+
+
+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;
+}
+
+void NexusInterface::loginCompleted()
+{
+ nextRequest();
+}
+
+
+void NexusInterface::interpretNexusFileName(const QString &fileName, QString &modName, int &modID, bool query)
+{
+ static std::tr1::regex exp("^([a-zA-Z0-9_\\- ]*?)([-_ ][VvRr]?[0-9_]+)?-([1-9][0-9]+).*");
+ static std::tr1::regex simpleexp("^([a-zA-Z0-9_]+)");
+
+ QByteArray fileNameUTF8 = fileName.toUtf8();
+ std::tr1::cmatch result;
+ if (std::tr1::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...
+ unsigned 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) + offset, " *");
+ QString r3Highlight(fileName);
+ r3Highlight.insert(result.position(3) + result.length(3), "* ").insert(result.position(3), " *");
+
+ selection.addChoice(candidate.c_str(), r3Highlight, strtol(candidate.c_str(), NULL, 10));
+ selection.addChoice(candidate2.c_str() + offset, r2Highlight, abs(strtol(candidate2.c_str() + offset, NULL, 10)));
+ if (selection.exec() == QDialog::Accepted) {
+ modID = selection.getChoiceData().toInt();
+ } else {
+ modID = -1;
+ }
+ } else {
+ modID = -1;
+ }
+ } else {
+ modID = strtol(candidate.c_str(), NULL, 10);
+ }
+ qDebug("mod id guessed: %s -> %d", qPrintable(fileName), modID);
+ } else if (std::tr1::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;
+ }
+}
+
+
+int NexusInterface::requestDescription(int modID, QObject *receiver, QVariant userData,
+ const QString &subModule, const QString &url, int nexusGameId)
+{
+ NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_DESCRIPTION, userData, subModule, url,
+ nexusGameId == -1 ? GameInfo::instance().getNexusGameID() : nexusGameId);
+ m_RequestQueue.enqueue(requestInfo);
+
+ connect(this, SIGNAL(nxmDescriptionAvailable(int,QVariant,QVariant,int)),
+ receiver, SLOT(nxmDescriptionAvailable(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;
+}
+
+
+int NexusInterface::requestUpdates(const std::vector<int> &modIDs, QObject *receiver, QVariant userData,
+ const QString &subModule, const QString &url)
+{
+ NXMRequestInfo requestInfo(modIDs, NXMRequestInfo::TYPE_GETUPDATES, userData, subModule, url, GameInfo::instance().getNexusGameID());
+ m_RequestQueue.enqueue(requestInfo);
+
+ connect(this, SIGNAL(nxmUpdatesAvailable(std::vector<int>,QVariant,QVariant,int)),
+ receiver, SLOT(nxmUpdatesAvailable(std::vector<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;
+}
+
+
+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);
+
+ emit nxmFilesAvailable(1234, "fake", result, id++);
+}
+
+
+int NexusInterface::requestFiles(int modID, QObject *receiver, QVariant userData,
+ const QString &subModule, const QString &url)
+{
+ NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_FILES, userData, subModule, url, GameInfo::instance().getNexusGameID());
+ 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);
+
+// QTimer::singleShot(1000, this, SLOT(fakeFiles()));
+// static int fID = 42;
+// return fID++;
+
+ nextRequest();
+ return requestInfo.m_ID;
+}
+
+
+int NexusInterface::requestFileInfo(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule,
+ const QString &url)
+{
+ NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_FILEINFO, userData, subModule, url, GameInfo::instance().getNexusGameID());
+ m_RequestQueue.enqueue(requestInfo);
+
+ connect(this, SIGNAL(nxmFileInfoAvailable(int,int,QVariant,QVariant,int)),
+ receiver, SLOT(nxmFileInfoAvailable(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);
+
+ nextRequest();
+ return requestInfo.m_ID;
+}
+
+
+int NexusInterface::requestDownloadURL(int modID, int fileID, QObject *receiver, QVariant userData,
+ const QString &subModule, const QString &url)
+{
+ NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_DOWNLOADURL, userData, subModule, url, GameInfo::instance().getNexusGameID());
+ 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);
+
+ nextRequest();
+ return requestInfo.m_ID;
+}
+
+
+int NexusInterface::requestToggleEndorsement(int modID, bool endorse, QObject *receiver, QVariant userData,
+ const QString &subModule, const QString &url)
+{
+ NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLEENDORSEMENT, userData, subModule, url, GameInfo::instance().getNexusGameID());
+ 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);
+
+ nextRequest();
+ return requestInfo.m_ID;
+}
+
+bool NexusInterface::requiresLogin(const NXMRequestInfo &info)
+{
+ return (info.m_Type == NXMRequestInfo::TYPE_TOGGLEENDORSEMENT)
+ || (info.m_Type == NXMRequestInfo::TYPE_DOWNLOADURL);
+}
+
+void NexusInterface::nextRequest()
+{
+ if ((m_ActiveRequest.size() >= MAX_ACTIVE_DOWNLOADS)
+ || m_RequestQueue.isEmpty()) {
+ return;
+ }
+
+ if (!getAccessManager()->loggedIn()
+ && requiresLogin(m_RequestQueue.head())) {
+ 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<int>(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;
+ }
+ QNetworkRequest request(url);
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/xml");
+ QStringList comments;
+ comments << "compatible to Nexus Client v" + m_NMMVersion;
+ if (!info.m_SubModule.isEmpty()) {
+ comments << "module: " + info.m_SubModule;
+ }
+
+ QString userAgent = QString("Mod Organizer v%1 (%2)")
+ .arg(m_MOVersion.displayString())
+ .arg(comments.join("; "));
+ request.setRawHeader("User-Agent", userAgent.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<NXMRequestInfo>::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 {
+ 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<QNetworkReply*>(sender());
+ for (std::list<NXMRequestInfo>::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<QNetworkReply*>(sender());
+ if (reply == NULL) {
+ qWarning("invalid sender type");
+ return;
+ }
+
+ qCritical("request (%s) error: %s",
+ qPrintable(reply->url().toString()), qPrintable(reply->errorString()));
+}
+
+
+void NexusInterface::requestTimeout()
+{
+ QTimer *timer = qobject_cast<QTimer*>(sender());
+ if (timer == NULL) {
+ qWarning("invalid sender type");
+ return;
+ }
+ for (std::list<NXMRequestInfo>::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;
+ }
+ }
+}
+
+
+NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID
+ , NexusInterface::NXMRequestInfo::Type type
+ , QVariant userData
+ , const QString &subModule
+ , const QString &url
+ , int nexusGameId)
+ : m_ModID(modID)
+ , m_FileID(0)
+ , m_Reply(NULL)
+ , m_Type(type)
+ , m_UserData(userData)
+ , m_Timeout(NULL)
+ , m_Reroute(false)
+ , m_ID(s_NextID.fetchAndAddAcquire(1))
+ , m_URL(url)
+ , m_SubModule(subModule)
+ , m_NexusGameID(nexusGameId)
+{}
+
+NexusInterface::NXMRequestInfo::NXMRequestInfo(std::vector<int> modIDList
+ , NexusInterface::NXMRequestInfo::Type type
+ , QVariant userData
+ , const QString &subModule
+ , const QString &url
+ , int nexusGameId)
+ : m_ModID(-1)
+ , m_ModIDList(modIDList)
+ , m_FileID(0)
+ , m_Reply(NULL)
+ , m_Type(type)
+ , m_UserData(userData)
+ , m_Timeout(NULL)
+ , m_Reroute(false)
+ , m_ID(s_NextID.fetchAndAddAcquire(1))
+ , m_URL(url)
+ , m_SubModule(subModule)
+ , m_NexusGameID(nexusGameId)
+{}
+
+NexusInterface::NXMRequestInfo::NXMRequestInfo(int modID
+ , int fileID
+ , NexusInterface::NXMRequestInfo::Type type
+ , QVariant userData
+ , const QString &subModule
+ , const QString &url
+ , int nexusGameId)
+ : m_ModID(modID)
+ , m_FileID(fileID)
+ , m_Reply(NULL)
+ , m_Type(type)
+ , m_UserData(userData)
+ , m_Timeout(NULL)
+ , m_Reroute(false)
+ , m_ID(s_NextID.fetchAndAddAcquire(1))
+ , m_URL(url)
+ , m_SubModule(subModule)
+ , m_NexusGameID(nexusGameId)
+{}
diff --git a/src/nexusinterface.h b/src/nexusinterface.h index 5a46eebf..db61b0bf 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -1,322 +1,322 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef NEXUSINTERFACE_H -#define NEXUSINTERFACE_H - - -#include "nxmaccessmanager.h" - -#include <utility.h> -#include <gameinfo.h> -#include <versioninfo.h> -#include <imodrepositorybridge.h> -#include <QNetworkReply> -#include <QNetworkDiskCache> -#include <QQueue> -#include <QVariant> -#include <QTimer> -#include <list> -#include <set> - - -class NexusInterface; - - -/** - * @brief convenience class to make nxm requests easier - * usually, all objects that started a nxm request will be signaled if one finished. - * Therefore, the objects need to store the id of the requests they started and then filter - * the result. - * NexusBridge does this automatically. Users connect to the signals of NexusBridge they intend - * to handle and only receive the signals the caused - **/ -class NexusBridge : public MOBase::IModRepositoryBridge -{ - - 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); - -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); - -private: - - NexusInterface *m_Interface; - QString m_Url; - QString m_SubModule; - std::set<int> m_RequestIDs; - -}; - - -/** - * @brief Makes asynchronous requests to the nexus API - * - * This class can be used to make asynchronous requests to the Nexus API. - * Currently, responses are sent to all receivers that have sent a request of the relevant type, so the - * recipient has to filter the response by the id returned when making the request - **/ -class NexusInterface : public QObject -{ - Q_OBJECT - -public: - - static NexusInterface *instance(); - - /** - * @return the access manager object used to connect to nexus - **/ - NXMAccessManager *getAccessManager(); - - /** - * @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 url the url to request from - * @return int an id to identify the request - **/ - int requestDescription(int modID, QObject *receiver, QVariant userData, const QString &subModule, - const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl()), - int nexusGameId = -1); - - /** - * @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 url the url to request from - * @return int an id to identify the request - */ - int requestUpdates(const std::vector<int> &modIDs, QObject *receiver, QVariant userData, const QString &subModule, - const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); - - /** - * @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 url the url to request from - * @return int an id to identify the request - **/ - int requestFiles(int modID, QObject *receiver, QVariant userData, const QString &subModule, - const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); - - /** - * @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 receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable) - * @param userData user data to be returned with the result - * @param url the url to request from - * @return int an id to identify the request - **/ - int requestFileInfo(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule, - const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); - - /** - * @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 url the url to request from - * @return int an id to identify the request - **/ - int requestDownloadURL(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule, - const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); - - /** - * @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 url the url to request from - * @return int an id to identify the request - */ - int requestToggleEndorsement(int modID, bool endorse, QObject *receiver, QVariant userData, const QString &subModule, - const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl())); - - /** - * @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); - -signals: - - void requestNXMDownload(const QString &url); - - void needLogin(); - - void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmUpdatesAvailable(const std::vector<int> &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); - -private slots: - - void requestFinished(); - void requestError(QNetworkReply::NetworkError error); - void requestTimeout(); - - void downloadRequestedNXM(const QString &url); - - void fakeFiles(); - -private: - - struct NXMRequestInfo { - int m_ModID; - std::vector<int> 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, const QString &url, int nexusGameId); - NXMRequestInfo(std::vector<int> modIDList, Type type, QVariant userData, const QString &subModule, const QString &url, int nexusGameId); - NXMRequestInfo(int modID, int fileID, Type type, QVariant userData, const QString &subModule, const QString &url, int nexusGameId); - - private: - static QAtomicInt s_NextID; - }; - - static const int MAX_ACTIVE_DOWNLOADS = 2; - -private: - - NexusInterface(); - void nextRequest(); - void requestFinished(std::list<NXMRequestInfo>::iterator iter); - bool requiresLogin(const NXMRequestInfo &info); - static void cleanup(); - -private: - -// static NexusInterface *s_Instance; - - QNetworkDiskCache *m_DiskCache; - - NXMAccessManager *m_AccessManager; - - std::list<NXMRequestInfo> m_ActiveRequest; - QQueue<NXMRequestInfo> m_RequestQueue; - - MOBase::VersionInfo m_MOVersion; - QString m_NMMVersion; - -}; - -#endif // NEXUSINTERFACE_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef NEXUSINTERFACE_H
+#define NEXUSINTERFACE_H
+
+
+#include "nxmaccessmanager.h"
+
+#include <utility.h>
+#include <gameinfo.h>
+#include <versioninfo.h>
+#include <imodrepositorybridge.h>
+#include <QNetworkReply>
+#include <QNetworkDiskCache>
+#include <QQueue>
+#include <QVariant>
+#include <QTimer>
+#include <list>
+#include <set>
+
+
+class NexusInterface;
+
+
+/**
+ * @brief convenience class to make nxm requests easier
+ * usually, all objects that started a nxm request will be signaled if one finished.
+ * Therefore, the objects need to store the id of the requests they started and then filter
+ * the result.
+ * NexusBridge does this automatically. Users connect to the signals of NexusBridge they intend
+ * to handle and only receive the signals the caused
+ **/
+class NexusBridge : public MOBase::IModRepositoryBridge
+{
+
+ 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);
+
+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);
+
+private:
+
+ NexusInterface *m_Interface;
+ QString m_Url;
+ QString m_SubModule;
+ std::set<int> m_RequestIDs;
+
+};
+
+
+/**
+ * @brief Makes asynchronous requests to the nexus API
+ *
+ * This class can be used to make asynchronous requests to the Nexus API.
+ * Currently, responses are sent to all receivers that have sent a request of the relevant type, so the
+ * recipient has to filter the response by the id returned when making the request
+ **/
+class NexusInterface : public QObject
+{
+ Q_OBJECT
+
+public:
+
+ static NexusInterface *instance();
+
+ /**
+ * @return the access manager object used to connect to nexus
+ **/
+ NXMAccessManager *getAccessManager();
+
+ /**
+ * @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 url the url to request from
+ * @return int an id to identify the request
+ **/
+ int requestDescription(int modID, QObject *receiver, QVariant userData, const QString &subModule,
+ const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl()),
+ int nexusGameId = -1);
+
+ /**
+ * @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 url the url to request from
+ * @return int an id to identify the request
+ */
+ int requestUpdates(const std::vector<int> &modIDs, QObject *receiver, QVariant userData, const QString &subModule,
+ const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl()));
+
+ /**
+ * @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 url the url to request from
+ * @return int an id to identify the request
+ **/
+ int requestFiles(int modID, QObject *receiver, QVariant userData, const QString &subModule,
+ const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl()));
+
+ /**
+ * @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 receiver the object to receive the result asynchronously via a signal (nxmFilesAvailable)
+ * @param userData user data to be returned with the result
+ * @param url the url to request from
+ * @return int an id to identify the request
+ **/
+ int requestFileInfo(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule,
+ const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl()));
+
+ /**
+ * @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 url the url to request from
+ * @return int an id to identify the request
+ **/
+ int requestDownloadURL(int modID, int fileID, QObject *receiver, QVariant userData, const QString &subModule,
+ const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl()));
+
+ /**
+ * @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 url the url to request from
+ * @return int an id to identify the request
+ */
+ int requestToggleEndorsement(int modID, bool endorse, QObject *receiver, QVariant userData, const QString &subModule,
+ const QString &url = MOBase::ToQString(MOShared::GameInfo::instance().getNexusInfoUrl()));
+
+ /**
+ * @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);
+
+signals:
+
+ void requestNXMDownload(const QString &url);
+
+ void needLogin();
+
+ void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID);
+ void nxmUpdatesAvailable(const std::vector<int> &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);
+
+private slots:
+
+ void requestFinished();
+ void requestError(QNetworkReply::NetworkError error);
+ void requestTimeout();
+
+ void downloadRequestedNXM(const QString &url);
+
+ void fakeFiles();
+
+private:
+
+ struct NXMRequestInfo {
+ int m_ModID;
+ std::vector<int> 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, const QString &url, int nexusGameId);
+ NXMRequestInfo(std::vector<int> modIDList, Type type, QVariant userData, const QString &subModule, const QString &url, int nexusGameId);
+ NXMRequestInfo(int modID, int fileID, Type type, QVariant userData, const QString &subModule, const QString &url, int nexusGameId);
+
+ private:
+ static QAtomicInt s_NextID;
+ };
+
+ static const int MAX_ACTIVE_DOWNLOADS = 2;
+
+private:
+
+ NexusInterface();
+ void nextRequest();
+ void requestFinished(std::list<NXMRequestInfo>::iterator iter);
+ bool requiresLogin(const NXMRequestInfo &info);
+ static void cleanup();
+
+private:
+
+// static NexusInterface *s_Instance;
+
+ QNetworkDiskCache *m_DiskCache;
+
+ NXMAccessManager *m_AccessManager;
+
+ std::list<NXMRequestInfo> m_ActiveRequest;
+ QQueue<NXMRequestInfo> m_RequestQueue;
+
+ MOBase::VersionInfo m_MOVersion;
+ QString m_NMMVersion;
+
+};
+
+#endif // NEXUSINTERFACE_H
diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index ed872870..a238d1c8 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -1,212 +1,212 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "nxmaccessmanager.h" -#include "nxmurl.h" -#include "report.h" -#include "utility.h" -#include "selfupdater.h" -#include "persistentcookiejar.h" -#include <QMessageBox> -#include <QPushButton> -#include <QNetworkProxy> -#include <QNetworkRequest> -#include <QNetworkCookie> -#include <QNetworkCookieJar> -#include <QCoreApplication> -#include <QDir> -#include <gameinfo.h> - -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) -#include <QUrlQuery> -#endif - -using namespace MOBase; -using namespace MOShared; - - -NXMAccessManager::NXMAccessManager(QObject *parent) - : QNetworkAccessManager(parent) - , m_LoginReply(NULL) - , m_ProgressDialog() - , m_LoginAttempted(false) -{ - setCookieJar(new PersistentCookieJar( - QDir::fromNativeSeparators(MOBase::ToQString(MOShared::GameInfo::instance().getCacheDir())) + "/nexus_cookies.dat", this)); -} - -NXMAccessManager::~NXMAccessManager() -{ - if (m_LoginReply != NULL) { - m_LoginReply->deleteLater(); - m_LoginReply = NULL; - } -} - - -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::showCookies() -{ - QList<QNetworkCookie> cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage()) + "/")); - foreach (QNetworkCookie cookie, cookies) { - qDebug("%s - %s", cookie.name().constData(), cookie.value().constData()); - } -} - - -bool NXMAccessManager::loggedIn() const -{ - return hasLoginCookies(); -} - -bool NXMAccessManager::loginWaiting() const -{ - return m_LoginReply != NULL; -} - - -void NXMAccessManager::login(const QString &username, const QString &password) -{ - if (m_LoginReply != NULL) { - return; - } - - if (hasLoginCookies()) { - emit loginSuccessful(false); - return; - } - - m_LoginAttempted = true; - - m_Username = username; - m_Password = password; - pageLogin(); -} - - -void NXMAccessManager::pageLogin() -{ - QString requestString = (ToQString(GameInfo::instance().getNexusPage()) + "/sessions/?Login&uri=%1") - .arg(QString(QUrl::toPercentEncoding(ToQString(GameInfo::instance().getNexusPage())))); - - QNetworkRequest request(requestString); - request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); - - QByteArray postDataQuery; -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - QUrlQuery postData; - postData.addQueryItem("username", m_Username); - postData.addQueryItem("password", m_Password); - postDataQuery = postData.query(QUrl::FullyEncoded).toUtf8(); -#else - QUrl postData; - postData.addQueryItem("username", m_Username); - postData.addQueryItem("password", m_Password); - postDataQuery = postData.encodedQuery(); -#endif - - m_ProgressDialog.setLabelText(tr("Logging into Nexus")); - QList<QPushButton*> buttons = m_ProgressDialog.findChildren<QPushButton*>(); - 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(); - connect(m_LoginReply, SIGNAL(finished()), this, SLOT(loginFinished())); - connect(m_LoginReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(loginError(QNetworkReply::NetworkError))); -} - - -void NXMAccessManager::loginTimeout() -{ - emit loginFailed(tr("timeout")); - m_LoginReply->deleteLater(); - m_LoginReply = NULL; - m_LoginAttempted = false; // this usually means we might have usccess later - m_LoginTimeout.stop(); - m_Username.clear(); - m_Password.clear(); -} - - -void NXMAccessManager::loginError(QNetworkReply::NetworkError) -{ - m_ProgressDialog.hide(); - m_LoginTimeout.stop(); - if (m_LoginReply != NULL) { - emit loginFailed(m_LoginReply->errorString()); - m_LoginReply->deleteLater(); - m_LoginReply = NULL; - } else { - emit loginFailed(tr("Unknown error")); - } - m_Username.clear(); - m_Password.clear(); -} - - -bool NXMAccessManager::hasLoginCookies() const -{ - bool sidCookie = false; - QList<QNetworkCookie> cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage()) + "/")); - foreach (QNetworkCookie cookie, cookies) { - if (cookie.name() == "sid") { - sidCookie = true; - } - } - return sidCookie; -} - - -void NXMAccessManager::loginFinished() -{ - m_ProgressDialog.hide(); - if (hasLoginCookies()) { - emit loginSuccessful(true); - } else { - emit loginFailed(tr("Please check your password")); - } - - m_LoginTimeout.stop(); - m_LoginReply->deleteLater(); - m_LoginReply = NULL; - m_Username.clear(); - m_Password.clear(); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "nxmaccessmanager.h"
+#include "nxmurl.h"
+#include "report.h"
+#include "utility.h"
+#include "selfupdater.h"
+#include "persistentcookiejar.h"
+#include <QMessageBox>
+#include <QPushButton>
+#include <QNetworkProxy>
+#include <QNetworkRequest>
+#include <QNetworkCookie>
+#include <QNetworkCookieJar>
+#include <QCoreApplication>
+#include <QDir>
+#include <gameinfo.h>
+
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+#include <QUrlQuery>
+#endif
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+NXMAccessManager::NXMAccessManager(QObject *parent)
+ : QNetworkAccessManager(parent)
+ , m_LoginReply(NULL)
+ , m_ProgressDialog()
+ , m_LoginAttempted(false)
+{
+ setCookieJar(new PersistentCookieJar(
+ QDir::fromNativeSeparators(MOBase::ToQString(MOShared::GameInfo::instance().getCacheDir())) + "/nexus_cookies.dat", this));
+}
+
+NXMAccessManager::~NXMAccessManager()
+{
+ if (m_LoginReply != NULL) {
+ m_LoginReply->deleteLater();
+ m_LoginReply = NULL;
+ }
+}
+
+
+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::showCookies()
+{
+ QList<QNetworkCookie> cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage()) + "/"));
+ foreach (QNetworkCookie cookie, cookies) {
+ qDebug("%s - %s", cookie.name().constData(), cookie.value().constData());
+ }
+}
+
+
+bool NXMAccessManager::loggedIn() const
+{
+ return hasLoginCookies();
+}
+
+bool NXMAccessManager::loginWaiting() const
+{
+ return m_LoginReply != NULL;
+}
+
+
+void NXMAccessManager::login(const QString &username, const QString &password)
+{
+ if (m_LoginReply != NULL) {
+ return;
+ }
+
+ if (hasLoginCookies()) {
+ emit loginSuccessful(false);
+ return;
+ }
+
+ m_LoginAttempted = true;
+
+ m_Username = username;
+ m_Password = password;
+ pageLogin();
+}
+
+
+void NXMAccessManager::pageLogin()
+{
+ QString requestString = (ToQString(GameInfo::instance().getNexusPage()) + "/sessions/?Login&uri=%1")
+ .arg(QString(QUrl::toPercentEncoding(ToQString(GameInfo::instance().getNexusPage()))));
+
+ QNetworkRequest request(requestString);
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
+
+ QByteArray postDataQuery;
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+ QUrlQuery postData;
+ postData.addQueryItem("username", m_Username);
+ postData.addQueryItem("password", m_Password);
+ postDataQuery = postData.query(QUrl::FullyEncoded).toUtf8();
+#else
+ QUrl postData;
+ postData.addQueryItem("username", m_Username);
+ postData.addQueryItem("password", m_Password);
+ postDataQuery = postData.encodedQuery();
+#endif
+
+ m_ProgressDialog.setLabelText(tr("Logging into Nexus"));
+ QList<QPushButton*> buttons = m_ProgressDialog.findChildren<QPushButton*>();
+ 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();
+ connect(m_LoginReply, SIGNAL(finished()), this, SLOT(loginFinished()));
+ connect(m_LoginReply, SIGNAL(error(QNetworkReply::NetworkError)), this, SLOT(loginError(QNetworkReply::NetworkError)));
+}
+
+
+void NXMAccessManager::loginTimeout()
+{
+ emit loginFailed(tr("timeout"));
+ m_LoginReply->deleteLater();
+ m_LoginReply = NULL;
+ m_LoginAttempted = false; // this usually means we might have usccess later
+ m_LoginTimeout.stop();
+ m_Username.clear();
+ m_Password.clear();
+}
+
+
+void NXMAccessManager::loginError(QNetworkReply::NetworkError)
+{
+ m_ProgressDialog.hide();
+ m_LoginTimeout.stop();
+ if (m_LoginReply != NULL) {
+ emit loginFailed(m_LoginReply->errorString());
+ m_LoginReply->deleteLater();
+ m_LoginReply = NULL;
+ } else {
+ emit loginFailed(tr("Unknown error"));
+ }
+ m_Username.clear();
+ m_Password.clear();
+}
+
+
+bool NXMAccessManager::hasLoginCookies() const
+{
+ bool sidCookie = false;
+ QList<QNetworkCookie> cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage()) + "/"));
+ foreach (QNetworkCookie cookie, cookies) {
+ if (cookie.name() == "sid") {
+ sidCookie = true;
+ }
+ }
+ return sidCookie;
+}
+
+
+void NXMAccessManager::loginFinished()
+{
+ m_ProgressDialog.hide();
+ if (hasLoginCookies()) {
+ emit loginSuccessful(true);
+ } else {
+ emit loginFailed(tr("Please check your password"));
+ }
+
+ m_LoginTimeout.stop();
+ m_LoginReply->deleteLater();
+ m_LoginReply = NULL;
+ m_Username.clear();
+ m_Password.clear();
+}
diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 6c0dedb9..525394c9 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -1,103 +1,103 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef NXMACCESSMANAGER_H -#define NXMACCESSMANAGER_H - - -#include <QNetworkAccessManager> -#include <QTimer> -#include <QNetworkReply> -#include <QProgressDialog> - - -/** - * @brief access manager extended to handle nxm links - **/ -class NXMAccessManager : public QNetworkAccessManager -{ - Q_OBJECT -public: - - explicit NXMAccessManager(QObject *parent); - - ~NXMAccessManager(); - - bool loggedIn() const; - - bool loginAttempted() const { return m_LoginAttempted; } - bool loginWaiting() const; - - void login(const QString &username, const QString &password); - - void showCookies(); - -signals: - - /** - * @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); - - void loginFailed(const QString &message); - - private slots: - - void loginFinished(); - void loginError(QNetworkReply::NetworkError errorCode); - void loginTimeout(); - -public slots: - -protected: - - virtual QNetworkReply *createRequest( - QNetworkAccessManager::Operation operation, const QNetworkRequest &request, - QIODevice *device); - -private: - - void pageLogin(); -// void dlLogin(); - - bool hasLoginCookies() const; - -private: - - QTimer m_LoginTimeout; - QNetworkReply *m_LoginReply; - QProgressDialog m_ProgressDialog; - - QString m_Username; - QString m_Password; - - bool m_LoginAttempted; - -}; - -#endif // NXMACCESSMANAGER_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef NXMACCESSMANAGER_H
+#define NXMACCESSMANAGER_H
+
+
+#include <QNetworkAccessManager>
+#include <QTimer>
+#include <QNetworkReply>
+#include <QProgressDialog>
+
+
+/**
+ * @brief access manager extended to handle nxm links
+ **/
+class NXMAccessManager : public QNetworkAccessManager
+{
+ Q_OBJECT
+public:
+
+ explicit NXMAccessManager(QObject *parent);
+
+ ~NXMAccessManager();
+
+ bool loggedIn() const;
+
+ bool loginAttempted() const { return m_LoginAttempted; }
+ bool loginWaiting() const;
+
+ void login(const QString &username, const QString &password);
+
+ void showCookies();
+
+signals:
+
+ /**
+ * @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);
+
+ void loginFailed(const QString &message);
+
+ private slots:
+
+ void loginFinished();
+ void loginError(QNetworkReply::NetworkError errorCode);
+ void loginTimeout();
+
+public slots:
+
+protected:
+
+ virtual QNetworkReply *createRequest(
+ QNetworkAccessManager::Operation operation, const QNetworkRequest &request,
+ QIODevice *device);
+
+private:
+
+ void pageLogin();
+// void dlLogin();
+
+ bool hasLoginCookies() const;
+
+private:
+
+ QTimer m_LoginTimeout;
+ QNetworkReply *m_LoginReply;
+ QProgressDialog m_ProgressDialog;
+
+ QString m_Username;
+ QString m_Password;
+
+ bool m_LoginAttempted;
+
+};
+
+#endif // NXMACCESSMANAGER_H
diff --git a/src/overwriteinfodialog.cpp b/src/overwriteinfodialog.cpp index 16242506..d06922bc 100644 --- a/src/overwriteinfodialog.cpp +++ b/src/overwriteinfodialog.cpp @@ -1,260 +1,260 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "overwriteinfodialog.h" -#include "ui_overwriteinfodialog.h" -#include "report.h" -#include "utility.h" -#include <QMessageBox> -#include <QMenu> -#include <Shlwapi.h> - - -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); - } - } - - 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; -}; - - -OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent) - : QDialog(parent), ui(new Ui::OverwriteInfoDialog), m_FileSystemModel(NULL), - m_DeleteAction(NULL), m_RenameAction(NULL), m_OpenAction(NULL) -{ - 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; -} - -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()); - 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::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); - } -} - - -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(NULL, L"open", ToWString(fileName).c_str(), NULL, NULL, 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::createDirectoryTriggered() -{ - QModelIndex selection = m_FileSelection.at(0); - - QModelIndex index = m_FileSystemModel->isDir(selection) ? selection - : selection.parent(); - index = index.sibling(index.row(), 0); - - QString name = tr("New Folder"); - QString path = m_FileSystemModel->filePath(index).append("/"); - - QModelIndex existingIndex = m_FileSystemModel->index(path + name); - int suffix = 1; - while (existingIndex.isValid()) { - name = tr("New Folder") + QString::number(suffix++); - existingIndex = m_FileSystemModel->index(path + name); - } - - QModelIndex newIndex = m_FileSystemModel->mkdir(index, name); - if (!newIndex.isValid()) { - reportError(tr("Failed to create \"%1\"").arg(name)); - return; - } - - ui->filesView->setCurrentIndex(newIndex); - ui->filesView->edit(newIndex); -} - - -void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint &pos) -{ - QItemSelectionModel *selectionModel = ui->filesView->selectionModel(); - m_FileSelection = selectionModel->selectedRows(0); - - QMenu menu(ui->filesView); - - menu.addAction(m_NewFolderAction); - - bool hasFiles = false; - - foreach(QModelIndex idx, m_FileSelection) { - if (m_FileSystemModel->fileInfo(idx).isFile()) { - hasFiles = true; - break; - } - } - - if (selectionModel->hasSelection()) { - if (hasFiles) { - menu.addAction(m_OpenAction); - } - menu.addAction(m_RenameAction); - menu.addAction(m_DeleteAction); - } else { - m_FileSelection.clear(); - m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0)); - } - menu.exec(ui->filesView->mapToGlobal(pos)); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "overwriteinfodialog.h"
+#include "ui_overwriteinfodialog.h"
+#include "report.h"
+#include "utility.h"
+#include <QMessageBox>
+#include <QMenu>
+#include <Shlwapi.h>
+
+
+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);
+ }
+ }
+
+ 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;
+};
+
+
+OverwriteInfoDialog::OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent)
+ : QDialog(parent), ui(new Ui::OverwriteInfoDialog), m_FileSystemModel(NULL),
+ m_DeleteAction(NULL), m_RenameAction(NULL), m_OpenAction(NULL)
+{
+ 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;
+}
+
+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());
+ 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::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);
+ }
+}
+
+
+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(NULL, L"open", ToWString(fileName).c_str(), NULL, NULL, 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::createDirectoryTriggered()
+{
+ QModelIndex selection = m_FileSelection.at(0);
+
+ QModelIndex index = m_FileSystemModel->isDir(selection) ? selection
+ : selection.parent();
+ index = index.sibling(index.row(), 0);
+
+ QString name = tr("New Folder");
+ QString path = m_FileSystemModel->filePath(index).append("/");
+
+ QModelIndex existingIndex = m_FileSystemModel->index(path + name);
+ int suffix = 1;
+ while (existingIndex.isValid()) {
+ name = tr("New Folder") + QString::number(suffix++);
+ existingIndex = m_FileSystemModel->index(path + name);
+ }
+
+ QModelIndex newIndex = m_FileSystemModel->mkdir(index, name);
+ if (!newIndex.isValid()) {
+ reportError(tr("Failed to create \"%1\"").arg(name));
+ return;
+ }
+
+ ui->filesView->setCurrentIndex(newIndex);
+ ui->filesView->edit(newIndex);
+}
+
+
+void OverwriteInfoDialog::on_filesView_customContextMenuRequested(const QPoint &pos)
+{
+ QItemSelectionModel *selectionModel = ui->filesView->selectionModel();
+ m_FileSelection = selectionModel->selectedRows(0);
+
+ QMenu menu(ui->filesView);
+
+ menu.addAction(m_NewFolderAction);
+
+ bool hasFiles = false;
+
+ foreach(QModelIndex idx, m_FileSelection) {
+ if (m_FileSystemModel->fileInfo(idx).isFile()) {
+ hasFiles = true;
+ break;
+ }
+ }
+
+ if (selectionModel->hasSelection()) {
+ if (hasFiles) {
+ menu.addAction(m_OpenAction);
+ }
+ menu.addAction(m_RenameAction);
+ menu.addAction(m_DeleteAction);
+ } else {
+ m_FileSelection.clear();
+ m_FileSelection.append(m_FileSystemModel->index(m_FileSystemModel->rootPath(), 0));
+ }
+ menu.exec(ui->filesView->mapToGlobal(pos));
+}
diff --git a/src/overwriteinfodialog.h b/src/overwriteinfodialog.h index 0726f1f9..eb182292 100644 --- a/src/overwriteinfodialog.h +++ b/src/overwriteinfodialog.h @@ -1,73 +1,73 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef OVERWRITEINFODIALOG_H -#define OVERWRITEINFODIALOG_H - -#include "modinfo.h" -#include <QDialog> -#include <QFileSystemModel> - -namespace Ui { -class OverwriteInfoDialog; -} - -class OverwriteInfoDialog : public QDialog -{ - Q_OBJECT - -public: - - explicit OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent = 0); - ~OverwriteInfoDialog(); - - ModInfo::Ptr modInfo() const { return m_ModInfo; } - - void setModInfo(ModInfo::Ptr modInfo); - -private: - - 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 on_filesView_customContextMenuRequested(const QPoint &pos); - -private: - - Ui::OverwriteInfoDialog *ui; - QFileSystemModel *m_FileSystemModel; - QModelIndexList m_FileSelection; - QAction *m_DeleteAction; - QAction *m_RenameAction; - QAction *m_OpenAction; - QAction *m_NewFolderAction; - - ModInfo::Ptr m_ModInfo; - -}; - -#endif // OVERWRITEINFODIALOG_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef OVERWRITEINFODIALOG_H
+#define OVERWRITEINFODIALOG_H
+
+#include "modinfo.h"
+#include <QDialog>
+#include <QFileSystemModel>
+
+namespace Ui {
+class OverwriteInfoDialog;
+}
+
+class OverwriteInfoDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+
+ explicit OverwriteInfoDialog(ModInfo::Ptr modInfo, QWidget *parent = 0);
+ ~OverwriteInfoDialog();
+
+ ModInfo::Ptr modInfo() const { return m_ModInfo; }
+
+ void setModInfo(ModInfo::Ptr modInfo);
+
+private:
+
+ 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 on_filesView_customContextMenuRequested(const QPoint &pos);
+
+private:
+
+ Ui::OverwriteInfoDialog *ui;
+ QFileSystemModel *m_FileSystemModel;
+ QModelIndexList m_FileSelection;
+ QAction *m_DeleteAction;
+ QAction *m_RenameAction;
+ QAction *m_OpenAction;
+ QAction *m_NewFolderAction;
+
+ ModInfo::Ptr m_ModInfo;
+
+};
+
+#endif // OVERWRITEINFODIALOG_H
diff --git a/src/pluginlist.cpp b/src/pluginlist.cpp index 9f0924a2..4794414a 100644 --- a/src/pluginlist.cpp +++ b/src/pluginlist.cpp @@ -1,1159 +1,1159 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "pluginlist.h" -#include "report.h" -#include "inject.h" -#include "settings.h" -#include "safewritefile.h" -#include "scopeguard.h" -#include <utility.h> -#include <gameinfo.h> -#include <espfile.h> -#include <windows_error.h> - -#include <QtDebug> -#include <QMessageBox> -#include <QMimeData> -#include <QCoreApplication> -#include <QDir> -#include <QTextCodec> -#include <QFileInfo> -#include <QListWidgetItem> -#include <QString> -#include <QApplication> -#include <QKeyEvent> -#include <QSortFilterProxyModel> - -#include <ctime> -#include <algorithm> -#include <stdexcept> - - -using namespace MOBase; -using namespace MOShared; - - -bool ByName(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { - return LHS.m_Name.toUpper() < RHS.m_Name.toUpper(); -} - -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; - } -} - -bool ByDate(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) { - QString lhsExtension = LHS.m_Name.right(3).toLower(); - QString rhsExtension = RHS.m_Name.right(3).toLower(); - if (lhsExtension != rhsExtension) { - return lhsExtension == "esm"; - } - - return ::CompareFileTime(&LHS.m_Time, &RHS.m_Time) < 0; -} - -PluginList::PluginList(QObject *parent) - : QAbstractItemModel(parent) - , m_FontMetrics(QFont()) - , m_SaveTimer(this) -{ - m_SaveTimer.setSingleShot(true); - connect(&m_SaveTimer, SIGNAL(timeout()), this, SIGNAL(saveTimer())); - - m_Utf8Codec = QTextCodec::codecForName("utf-8"); - m_LocalCodec = QTextCodec::codecForName("Windows-1252"); - - if (m_LocalCodec == NULL) { - qCritical("required 8-bit string-encoding not supported."); - m_LocalCodec = m_Utf8Codec; - } - -} - -PluginList::~PluginList() -{ -} - - -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 determins the formids of objects originating from this mods."); - default: return tr("unknown"); - } -} - - -void PluginList::refresh(const QString &profileName, const DirectoryEntry &baseDirectory, - const QString &pluginsFile, const QString &loadOrderFile, - const QString &lockedOrderFile) -{ - ChangeBracket<PluginList> layoutChange(this); - - m_ESPsByName.clear(); - m_ESPsByPriority.clear(); - m_ESPs.clear(); - std::vector<std::wstring> primaryPlugins = GameInfo::instance().getPrimaryPlugins(); - - m_CurrentProfile = profileName; - - std::vector<FileEntry::Ptr> files = baseDirectory.getFiles(); - for (auto iter = files.begin(); iter != files.end(); ++iter) { - FileEntry::Ptr current = *iter; - if (current.get() == NULL) { - continue; - } - QString filename = ToQString(current->getName()); - QString extension = filename.right(3).toLower(); - - if ((extension == "esp") || (extension == "esm")) { - bool forceEnabled = Settings::instance().forceEnableCoreFiles() && - std::find(primaryPlugins.begin(), primaryPlugins.end(), ToWString(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() != NULL; - - m_ESPs.push_back(ESPInfo(filename, forceEnabled, current->getFileTime(), ToQString(origin.getName()), ToQString(current->getFullPath()), hasIni)); - } 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())); - } - } - } - - if (readLoadOrder(loadOrderFile)) { - int maxPriority = 0; - // assign known load orders - for (std::vector<ESPInfo>::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) { - std::map<QString, int>::const_iterator priorityIter = m_ESPLoadOrder.find(espIter->m_Name.toLower()); - if (priorityIter != m_ESPLoadOrder.end()) { - if (priorityIter->second > maxPriority) { - maxPriority = priorityIter->second; - } - espIter->m_Priority = priorityIter->second; - } else { - espIter->m_Priority = -1; - } - } - - ++maxPriority; - - // assign maximum priorities for plugins with unknown priority - for (std::vector<ESPInfo>::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) { - if (espIter->m_Priority == -1) { - espIter->m_Priority = maxPriority++; - } - } - } else { - // no load order stored, determine by date - std::sort(m_ESPs.begin(), m_ESPs.end(), ByDate); - - for (size_t i = 0; i < m_ESPs.size(); ++i) { - m_ESPs[i].m_Priority = i; - } - } - - std::sort(m_ESPs.begin(), m_ESPs.end(), ByPriority); // first, sort by priority - // remove gaps from the priorities so we can use them as array indices without overflow - for (int i = 0; i < static_cast<int>(m_ESPs.size()); ++i) { - m_ESPs[i].m_Priority = i; - } - - std::sort(m_ESPs.begin(), m_ESPs.end(), ByName); // sort by name so alphabetical sorting works - - updateIndices(); - - readEnabledFrom(pluginsFile); - - readLockedOrderFrom(lockedOrderFile); - - layoutChange.finish(); - - refreshLoadOrder(); - emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount())); - - m_Refreshed(); -} - - -void PluginList::enableESP(const QString &name, bool enable) -{ - std::map<QString, int>::iterator iter = m_ESPsByName.find(name.toLower()); - - if (iter != m_ESPsByName.end()) { - m_ESPs[iter->second].m_Enabled = enable; - startSaveTime(); - } else { - reportError(tr("esp not found: %1").arg(name)); - } -} - - -void PluginList::enableAll() -{ - if (QMessageBox::question(NULL, tr("Confirm"), tr("Really enable all plugins?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { - iter->m_Enabled = true; - } - startSaveTime(); - } -} - - -void PluginList::disableAll() -{ - if (QMessageBox::question(NULL, tr("Confirm"), tr("Really disable all plugins?"), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { - if (!iter->m_ForceEnabled) { - iter->m_Enabled = false; - } - } - startSaveTime(); - } -} - - -bool PluginList::isEnabled(const QString &name) -{ - std::map<QString, int>::iterator iter = m_ESPsByName.find(name.toLower()); - - if (iter != m_ESPsByName.end()) { - return m_ESPs[iter->second].m_Enabled; - } else { - return false; - } -} - -void PluginList::clearInformation(const QString &name) -{ - std::map<QString, int>::iterator iter = m_ESPsByName.find(name.toLower()); - - if (iter != m_ESPsByName.end()) { - m_AdditionalInfo[name.toLower()].m_Messages.clear(); - } -} - -void PluginList::clearAdditionalInformation() -{ - m_AdditionalInfo.clear(); -} - -void PluginList::addInformation(const QString &name, const QString &message) -{ - std::map<QString, int>::iterator iter = m_ESPsByName.find(name.toLower()); - - if (iter != m_ESPsByName.end()) { - m_AdditionalInfo[name.toLower()].m_Messages.append(message); - } -} - - -bool PluginList::isEnabled(int index) -{ - return m_ESPs.at(index).m_Enabled; -} - - -bool PluginList::readLoadOrder(const QString &fileName) -{ - std::set<QString> availableESPs; - for (std::vector<ESPInfo>::const_iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { - availableESPs.insert(iter->m_Name.toLower()); - } - - m_ESPLoadOrder.clear(); - - int priority = 0; - - std::vector<std::wstring> primaryPlugins = GameInfo::instance().getPrimaryPlugins(); - for (std::vector<std::wstring>::iterator iter = primaryPlugins.begin(); - iter != primaryPlugins.end(); ++iter) { - if (availableESPs.find(ToQString(*iter)) != availableESPs.end()) { - m_ESPLoadOrder[ToQString(*iter)] = priority++; - } - } - - QFile file(fileName); - if (!file.open(QIODevice::ReadOnly)) { - return false; - } - while (!file.atEnd()) { - QByteArray line = file.readLine().trimmed(); - QString modName; - if ((line.size() > 0) && (line.at(0) != '#')) { - modName = QString::fromUtf8(line.constData()).toLower(); - } - - if ((modName.size() > 0) && - (m_ESPLoadOrder.find(modName) == m_ESPLoadOrder.end()) && - (availableESPs.find(modName) != availableESPs.end())) { - m_ESPLoadOrder[modName] = priority++; - } - } - - file.close(); - return true; -} - - -void PluginList::readEnabledFrom(const QString &fileName) -{ - for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { - if (!iter->m_ForceEnabled) { - iter->m_Enabled = false; - } - iter->m_LoadOrder = -1; - } - - QFile file(fileName); - if (!file.exists()) { - throw std::runtime_error(QObject::tr("failed to find \"%1\"").arg(fileName).toUtf8().constData()); - } - - file.open(QIODevice::ReadOnly); - while (!file.atEnd()) { - QByteArray line = file.readLine(); - QString modName; - if ((line.size() > 0) && (line.at(0) != '#')) { - modName = m_LocalCodec->toUnicode(line.trimmed().constData()); - } - if (modName.size() > 0) { - std::map<QString, int>::iterator iter = m_ESPsByName.find(modName.toLower()); - if (iter != m_ESPsByName.end()) { - m_ESPs[iter->second].m_Enabled = true; - } else { - qWarning("plugin %s not found", modName.toUtf8().constData()); - startSaveTime(); - } - } - } - - file.close(); - - testMasters(); -} - - -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<QByteArray> fields = line.split('|'); - if (fields.count() == 2) { - m_LockedOrder[QString::fromUtf8(fields.at(0))] = fields.at(1).trimmed().toInt(); - } else { - reportError(tr("The file containing locked plugin indices is broken")); - break; - } - } - } - file.close(); -} - - - -void PluginList::writePlugins(const QString &fileName, bool writeUnchecked) const -{ - SafeWriteFile file(fileName); - - QTextCodec *textCodec = writeUnchecked ? m_Utf8Codec : m_LocalCodec; - - file->resize(0); - - file->write(textCodec->fromUnicode("# This file was automatically generated by Mod Organizer.\r\n")); - - QStringList saveList; - - bool invalidFileNames = false; - int writtenCount = 0; - for (size_t i = 0; i < m_ESPs.size(); ++i) { - int priority = m_ESPsByPriority[i]; - if (m_ESPs[priority].m_Enabled || writeUnchecked) { - //file.write(m_ESPs[priority].m_Name.toUtf8()); - if (!textCodec->canEncode(m_ESPs[priority].m_Name)) { - invalidFileNames = true; - qCritical("invalid plugin name %s", m_ESPs[priority].m_Name.toUtf8().constData()); - } else { - saveList << m_ESPs[priority].m_Name; - file->write(textCodec->fromUnicode(m_ESPs[priority].m_Name)); - } - file->write("\r\n"); - ++writtenCount; - } - } - - if (invalidFileNames) { - reportError(tr("Some of your plugins have invalid names! These plugins can not be loaded by the game. " - "Please see mo_interface.log for a list of affected plugins and rename them.")); - } - - if (file.commitIfDifferent(m_LastSaveHash[fileName])) { - 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(); -} - - -void PluginList::saveTo(const QString &pluginFileName - , const QString &loadOrderFileName - , const QString &lockedOrderFileName - , const QString& deleterFileName - , bool hideUnchecked) const -{ - writePlugins(pluginFileName, false); - writePlugins(loadOrderFileName, true); - writeLockedOrder(lockedOrderFileName); - - 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 { - shellDelete(QStringList() << deleterFileName); - } - - m_SaveTimer.stop(); -} - - -bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure) -{ - if (GameInfo::instance().getLoadOrderMechanism() != GameInfo::TYPE_FILETIME) { - // nothing to do - return true; - } - - for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { - std::wstring espName = ToWString(iter->m_Name); - const FileEntry::Ptr fileEntry = directoryStructure.findFile(espName); - if (fileEntry.get() != NULL) { - QString fileName; - bool archive = false; - int originid = fileEntry->getOrigin(archive); - fileName = QString("%1\\%2").arg(QDir::toNativeSeparators(ToQString(directoryStructure.getOriginByID(originid).getPath()))).arg(iter->m_Name); - - HANDLE file = ::CreateFile(ToWString(fileName).c_str(), GENERIC_READ | GENERIC_WRITE, - 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); - 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 + iter->m_Priority) * 24 * 60 * 60 * 10000000ULL; - - FILETIME newWriteTime; - - newWriteTime.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF); - newWriteTime.dwHighDateTime = (DWORD)(temp >> 32); - iter->m_Time = newWriteTime; - fileEntry->setFileTime(newWriteTime); - if (!::SetFileTime(file, NULL, NULL, &newWriteTime)) { - throw windows_error(QObject::tr("failed to set file time %1").arg(fileName).toUtf8().constData()); - } - - CloseHandle(file); - } - } - return true; -} - -int PluginList::enabledCount() const -{ - int enabled = 0; - foreach (auto info, m_ESPs) { - if (info.m_Enabled) { - ++enabled; - } - } - return enabled; -} - -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) { - m_LockedOrder[getName(index).toLower()] = m_ESPs.at(index).m_LoadOrder; - } else { - auto iter = m_LockedOrder.find(getName(index).toLower()); - if (iter != m_LockedOrder.end()) { - m_LockedOrder.erase(iter); - } - } - startSaveTime(); -} - - -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; - } - } -} - -void PluginList::refreshLoadOrder() -{ - ChangeBracket<PluginList> layoutChange(this); - syncLoadOrder(); - // set priorities according to locked load order - std::map<int, QString> lockedLoadOrder; - std::for_each(m_LockedOrder.begin(), m_LockedOrder.end(), - [&lockedLoadOrder] (const std::pair<QString, int> &ele) { lockedLoadOrder[ele.second] = ele.first; }); - - int targetPrio = 0; - // this is guaranteed to iterate from lowest key (load order) to highest - for (auto iter = lockedLoadOrder.begin(); iter != lockedLoadOrder.end(); ++iter) { - auto nameIter = m_ESPsByName.find(iter->second); - if (nameIter != m_ESPsByName.end()) { - // locked esp exists - - // find the location to insert at - while ((targetPrio < static_cast<int>(m_ESPs.size() - 1)) && - (m_ESPs[m_ESPsByPriority[targetPrio]].m_LoadOrder < iter->first)) { - ++targetPrio; - } - - if (static_cast<size_t>(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(); - startSaveTime(); - } - } - } -} - - - - -IPluginList::PluginState 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; - } -} - -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; - } -} - -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; - } -} - -QString PluginList::origin(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_OriginName; - } -} - -bool PluginList::onRefreshed(const std::function<void ()> &callback) -{ - auto conn = m_Refreshed.connect(callback); - return conn.connected(); -} - - -bool PluginList::onPluginMoved(const std::function<void (const QString &, int, int)> &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) { - m_ESPsByName[m_ESPs[i].m_Name.toLower()] = i; - m_ESPsByPriority[m_ESPs[i].m_Priority] = i; - } -} - - -int PluginList::rowCount(const QModelIndex &parent) const -{ - if (!parent.isValid()) { - return m_ESPs.size(); - } else { - return 0; - } -} - -int PluginList::columnCount(const QModelIndex &) const -{ - return COL_LASTCOLUMN + 1; -} - - -void PluginList::testMasters() -{ -// emit layoutAboutToBeChanged(); - - std::set<QString> enabledMasters; - for (auto iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { - if (iter->m_Enabled) { - enabledMasters.insert(iter->m_Name.toLower()); - } - } - - for (auto iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) { - iter->m_MasterUnset.clear(); - if (iter->m_Enabled) { - for (auto master = iter->m_Masters.begin(); master != iter->m_Masters.end(); ++master) { - if (enabledMasters.find(master->toLower()) == enabledMasters.end()) { - iter->m_MasterUnset.insert(*master); - } - } - } - } - -#pragma message("emitting this seems to cause a crash!") -// 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 { - return QString("%1").arg(m_ESPs[index].m_LoadOrder, 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::FontRole) { - QFont result; - if (m_ESPs[index].m_IsMaster) { - result.setItalic(true); - result.setWeight(QFont::Bold); - } else if (m_ESPs[index].m_IsDummy) { - result.setItalic(true); - } - 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("<br>") + "<br><hr>"; - } - } - if (m_ESPs[index].m_ForceEnabled) { - toolTip += tr("This plugin can't be disabled (enforced by the game)"); - } else { - QString text = tr("<b>Origin</b>: %1").arg(m_ESPs[index].m_OriginName); - if (m_ESPs[index].m_Author.size() > 0) { - text += "<br><b>" + tr("Author") + "</b>: " + m_ESPs[index].m_Author; - } - if (m_ESPs[index].m_Description.size() > 0) { - text += "<br><b>" + tr("Description") + "</b>: " + m_ESPs[index].m_Description; - } - if (m_ESPs[index].m_MasterUnset.size() > 0) { - text += "<br><b>" + tr("Missing Masters") + "</b>: <b>" + SetJoin(m_ESPs[index].m_MasterUnset, ", ") + "</b>"; - } - std::set<QString> 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.size() > 0) { - text += "<br><b>" + tr("Enabled Masters") + "</b>: " + SetJoin(enabledMasters, ", "); - } - if (m_ESPs[index].m_HasIni) { - text += "<br>There is an ini file connected to this esp. Its settings will be added to your game settings, overwriting " - "in case of conflicts."; - } else if (m_ESPs[index].m_IsDummy) { - text += "<br>This file is a dummy! It exists only so the bsa with the same name gets loaded. If you let MO manage archives you " - "don't need this: Enable the archive with the same name in the \"Archive\" tab and disable this plugin."; - } - 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(QIcon(":/MO/gui/warning")); - } - if (m_LockedOrder.find(nameLower) != m_LockedOrder.end()) { - result.append(QIcon(":/MO/gui/locked")); - } - auto bossInfoIter = m_AdditionalInfo.find(nameLower); - if (bossInfoIter != m_AdditionalInfo.end()) { - if (!bossInfoIter->second.m_Messages.isEmpty()) { - result.append(QIcon(":/MO/gui/information")); - } - /*if (bossInfoIter->second.m_LOOTUnrecognized) { - result.append(QIcon(":/MO/gui/help")); - }*/ - } - if (m_ESPs[index].m_HasIni) { - result.append(QIcon(":/MO/gui/attachment")); - } - if (m_ESPs[index].m_IsDummy && m_ESPs[index].m_Enabled && !m_ESPs[index].m_HasIni) { - result.append(QIcon(":/MO/gui/edit_clear")); - } - return result; - } - return QVariant(); -} - - -bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int role) -{ - bool result = false; - if (role == Qt::CheckStateRole) { - m_ESPs[modIndex.row()].m_Enabled = value.toInt() == Qt::Checked; - emit dataChanged(modIndex, modIndex); - - refreshLoadOrder(); - startSaveTime(); - - 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(); - } - } - 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; - } - } - 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; - } - } else { - result |= Qt::ItemIsDropEnabled; - } - - return result; -} - - -void PluginList::setPluginPriority(int row, int &newPriority) -{ - int newPriorityTemp = newPriority; - - if (!m_ESPs[row].m_IsMaster) { - // don't allow esps to be moved above esms - while ((newPriorityTemp < static_cast<int>(m_ESPsByPriority.size() - 1)) && - m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster) { - ++newPriorityTemp; - } - } else { - // don't allow esms to be moved below esps - while ((newPriorityTemp > 0) && - !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster) { - --newPriorityTemp; - } - // also don't allow "regular" esms to be moved above primary plugins - while ((newPriorityTemp < static_cast<int>(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<int>(m_ESPsByPriority.size())) newPriorityTemp = 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 { - 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(); -} - - -void PluginList::changePluginPriority(std::vector<int> rows, int newPriority) -{ - ChangeBracket<PluginList> layoutChange(this); - // sort rows to insert by their old priority (ascending) and insert them move them in that order - const std::vector<ESPInfo> &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<int>::const_iterator iter = rows.begin(); - iter != rows.end(); ++iter) { - if (m_ESPs[*iter].m_Priority < newPriority) { - --newPriority; - break; - } - } - - for (std::vector<int>::const_iterator iter = rows.begin(); iter != rows.end(); ++iter) { - setPluginPriority(*iter, newPriority); - } - - layoutChange.finish(); - refreshLoadOrder(); - - startSaveTime(); -} - - -void PluginList::startSaveTime() -{ - testMasters(); - - if (!m_SaveTimer.isActive()) { - m_SaveTimer.start(2000); - } -} - - -bool PluginList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent) -{ - if (action == Qt::IgnoreAction) { - return true; - } - - QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist"); - QDataStream stream(&encoded, QIODevice::ReadOnly); - - std::vector<int> sourceRows; - - while (!stream.atEnd()) { - int sourceRow, col; - QMap<int, QVariant> roleDataMap; - stream >> sourceRow >> col >> roleDataMap; - if (col == 0) { // only add each row once - sourceRows.push_back(sourceRow); - } - } - - if (row == -1) { - row = parent.row(); - } - - int newPriority = 0; - - if ((row < 0) || - (row >= static_cast<int>(m_ESPs.size()))) { - newPriority = m_ESPs.size(); - } else { - newPriority = m_ESPs[row].m_Priority; - } - changePluginPriority(sourceRows, newPriority); - - return false; -} - -QModelIndex PluginList::index(int row, int column, const QModelIndex&) const -{ - if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) { - return QModelIndex(); - } - return createIndex(row, column, row); -} - -QModelIndex PluginList::parent(const QModelIndex&) const -{ - return QModelIndex(); -} - - -bool PluginList::eventFilter(QObject *obj, QEvent *event) -{ - if (event->type() == QEvent::KeyPress) { - QAbstractItemView *itemView = qobject_cast<QAbstractItemView*>(obj); - - if (itemView == NULL) { - return QObject::eventFilter(obj, event); - } - - QKeyEvent *keyEvent = static_cast<QKeyEvent*>(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<const QSortFilterProxyModel*>(selectionModel->model()); - 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(); - if (keyEvent->key() == Qt::Key_Down) { - for (int i = 0; i < rows.size() / 2; ++i) { - rows.swap(i, rows.size() - i - 1); - } - } - foreach (QModelIndex idx, rows) { - if (proxyModel != NULL) { - 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<const QSortFilterProxyModel*>(selectionModel->model()); - - QModelIndex minRow, maxRow; - foreach (QModelIndex idx, selectionModel->selectedRows()) { - if (proxyModel != NULL) { - 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 QObject::eventFilter(obj, event); -} - - -PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, FILETIME time, - const QString &originName, const QString &fullPath, - bool hasIni) - : m_Name(name), m_Enabled(enabled), m_ForceEnabled(enabled), - m_Priority(0), m_LoadOrder(-1), m_Time(time), m_OriginName(originName), m_HasIni(hasIni) -{ - try { - ESP::File file(ToWString(fullPath)); - m_IsMaster = file.isMaster(); - m_IsDummy = file.isDummy(); - m_Author = QString::fromLatin1(file.author().c_str()); - m_Description = QString::fromLatin1(file.description().c_str()); - std::set<std::string> 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 esp file %s: %s", qPrintable(fullPath), e.what()); - m_IsMaster = false; - m_IsDummy = false; - } -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "pluginlist.h"
+#include "report.h"
+#include "inject.h"
+#include "settings.h"
+#include "safewritefile.h"
+#include "scopeguard.h"
+#include <utility.h>
+#include <gameinfo.h>
+#include <espfile.h>
+#include <windows_error.h>
+
+#include <QtDebug>
+#include <QMessageBox>
+#include <QMimeData>
+#include <QCoreApplication>
+#include <QDir>
+#include <QTextCodec>
+#include <QFileInfo>
+#include <QListWidgetItem>
+#include <QString>
+#include <QApplication>
+#include <QKeyEvent>
+#include <QSortFilterProxyModel>
+
+#include <ctime>
+#include <algorithm>
+#include <stdexcept>
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+bool ByName(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) {
+ return LHS.m_Name.toUpper() < RHS.m_Name.toUpper();
+}
+
+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;
+ }
+}
+
+bool ByDate(const PluginList::ESPInfo& LHS, const PluginList::ESPInfo& RHS) {
+ QString lhsExtension = LHS.m_Name.right(3).toLower();
+ QString rhsExtension = RHS.m_Name.right(3).toLower();
+ if (lhsExtension != rhsExtension) {
+ return lhsExtension == "esm";
+ }
+
+ return ::CompareFileTime(&LHS.m_Time, &RHS.m_Time) < 0;
+}
+
+PluginList::PluginList(QObject *parent)
+ : QAbstractItemModel(parent)
+ , m_FontMetrics(QFont())
+ , m_SaveTimer(this)
+{
+ m_SaveTimer.setSingleShot(true);
+ connect(&m_SaveTimer, SIGNAL(timeout()), this, SIGNAL(saveTimer()));
+
+ m_Utf8Codec = QTextCodec::codecForName("utf-8");
+ m_LocalCodec = QTextCodec::codecForName("Windows-1252");
+
+ if (m_LocalCodec == NULL) {
+ qCritical("required 8-bit string-encoding not supported.");
+ m_LocalCodec = m_Utf8Codec;
+ }
+
+}
+
+PluginList::~PluginList()
+{
+}
+
+
+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 determins the formids of objects originating from this mods.");
+ default: return tr("unknown");
+ }
+}
+
+
+void PluginList::refresh(const QString &profileName, const DirectoryEntry &baseDirectory,
+ const QString &pluginsFile, const QString &loadOrderFile,
+ const QString &lockedOrderFile)
+{
+ ChangeBracket<PluginList> layoutChange(this);
+
+ m_ESPsByName.clear();
+ m_ESPsByPriority.clear();
+ m_ESPs.clear();
+ std::vector<std::wstring> primaryPlugins = GameInfo::instance().getPrimaryPlugins();
+
+ m_CurrentProfile = profileName;
+
+ std::vector<FileEntry::Ptr> files = baseDirectory.getFiles();
+ for (auto iter = files.begin(); iter != files.end(); ++iter) {
+ FileEntry::Ptr current = *iter;
+ if (current.get() == NULL) {
+ continue;
+ }
+ QString filename = ToQString(current->getName());
+ QString extension = filename.right(3).toLower();
+
+ if ((extension == "esp") || (extension == "esm")) {
+ bool forceEnabled = Settings::instance().forceEnableCoreFiles() &&
+ std::find(primaryPlugins.begin(), primaryPlugins.end(), ToWString(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() != NULL;
+
+ m_ESPs.push_back(ESPInfo(filename, forceEnabled, current->getFileTime(), ToQString(origin.getName()), ToQString(current->getFullPath()), hasIni));
+ } 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()));
+ }
+ }
+ }
+
+ if (readLoadOrder(loadOrderFile)) {
+ int maxPriority = 0;
+ // assign known load orders
+ for (std::vector<ESPInfo>::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) {
+ std::map<QString, int>::const_iterator priorityIter = m_ESPLoadOrder.find(espIter->m_Name.toLower());
+ if (priorityIter != m_ESPLoadOrder.end()) {
+ if (priorityIter->second > maxPriority) {
+ maxPriority = priorityIter->second;
+ }
+ espIter->m_Priority = priorityIter->second;
+ } else {
+ espIter->m_Priority = -1;
+ }
+ }
+
+ ++maxPriority;
+
+ // assign maximum priorities for plugins with unknown priority
+ for (std::vector<ESPInfo>::iterator espIter = m_ESPs.begin(); espIter != m_ESPs.end(); ++espIter) {
+ if (espIter->m_Priority == -1) {
+ espIter->m_Priority = maxPriority++;
+ }
+ }
+ } else {
+ // no load order stored, determine by date
+ std::sort(m_ESPs.begin(), m_ESPs.end(), ByDate);
+
+ for (size_t i = 0; i < m_ESPs.size(); ++i) {
+ m_ESPs[i].m_Priority = i;
+ }
+ }
+
+ std::sort(m_ESPs.begin(), m_ESPs.end(), ByPriority); // first, sort by priority
+ // remove gaps from the priorities so we can use them as array indices without overflow
+ for (int i = 0; i < static_cast<int>(m_ESPs.size()); ++i) {
+ m_ESPs[i].m_Priority = i;
+ }
+
+ std::sort(m_ESPs.begin(), m_ESPs.end(), ByName); // sort by name so alphabetical sorting works
+
+ updateIndices();
+
+ readEnabledFrom(pluginsFile);
+
+ readLockedOrderFrom(lockedOrderFile);
+
+ layoutChange.finish();
+
+ refreshLoadOrder();
+ emit dataChanged(this->index(0, 0), this->index(m_ESPs.size(), columnCount()));
+
+ m_Refreshed();
+}
+
+
+void PluginList::enableESP(const QString &name, bool enable)
+{
+ std::map<QString, int>::iterator iter = m_ESPsByName.find(name.toLower());
+
+ if (iter != m_ESPsByName.end()) {
+ m_ESPs[iter->second].m_Enabled = enable;
+ startSaveTime();
+ } else {
+ reportError(tr("esp not found: %1").arg(name));
+ }
+}
+
+
+void PluginList::enableAll()
+{
+ if (QMessageBox::question(NULL, tr("Confirm"), tr("Really enable all plugins?"),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
+ iter->m_Enabled = true;
+ }
+ startSaveTime();
+ }
+}
+
+
+void PluginList::disableAll()
+{
+ if (QMessageBox::question(NULL, tr("Confirm"), tr("Really disable all plugins?"),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
+ if (!iter->m_ForceEnabled) {
+ iter->m_Enabled = false;
+ }
+ }
+ startSaveTime();
+ }
+}
+
+
+bool PluginList::isEnabled(const QString &name)
+{
+ std::map<QString, int>::iterator iter = m_ESPsByName.find(name.toLower());
+
+ if (iter != m_ESPsByName.end()) {
+ return m_ESPs[iter->second].m_Enabled;
+ } else {
+ return false;
+ }
+}
+
+void PluginList::clearInformation(const QString &name)
+{
+ std::map<QString, int>::iterator iter = m_ESPsByName.find(name.toLower());
+
+ if (iter != m_ESPsByName.end()) {
+ m_AdditionalInfo[name.toLower()].m_Messages.clear();
+ }
+}
+
+void PluginList::clearAdditionalInformation()
+{
+ m_AdditionalInfo.clear();
+}
+
+void PluginList::addInformation(const QString &name, const QString &message)
+{
+ std::map<QString, int>::iterator iter = m_ESPsByName.find(name.toLower());
+
+ if (iter != m_ESPsByName.end()) {
+ m_AdditionalInfo[name.toLower()].m_Messages.append(message);
+ }
+}
+
+
+bool PluginList::isEnabled(int index)
+{
+ return m_ESPs.at(index).m_Enabled;
+}
+
+
+bool PluginList::readLoadOrder(const QString &fileName)
+{
+ std::set<QString> availableESPs;
+ for (std::vector<ESPInfo>::const_iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
+ availableESPs.insert(iter->m_Name.toLower());
+ }
+
+ m_ESPLoadOrder.clear();
+
+ int priority = 0;
+
+ std::vector<std::wstring> primaryPlugins = GameInfo::instance().getPrimaryPlugins();
+ for (std::vector<std::wstring>::iterator iter = primaryPlugins.begin();
+ iter != primaryPlugins.end(); ++iter) {
+ if (availableESPs.find(ToQString(*iter)) != availableESPs.end()) {
+ m_ESPLoadOrder[ToQString(*iter)] = priority++;
+ }
+ }
+
+ QFile file(fileName);
+ if (!file.open(QIODevice::ReadOnly)) {
+ return false;
+ }
+ while (!file.atEnd()) {
+ QByteArray line = file.readLine().trimmed();
+ QString modName;
+ if ((line.size() > 0) && (line.at(0) != '#')) {
+ modName = QString::fromUtf8(line.constData()).toLower();
+ }
+
+ if ((modName.size() > 0) &&
+ (m_ESPLoadOrder.find(modName) == m_ESPLoadOrder.end()) &&
+ (availableESPs.find(modName) != availableESPs.end())) {
+ m_ESPLoadOrder[modName] = priority++;
+ }
+ }
+
+ file.close();
+ return true;
+}
+
+
+void PluginList::readEnabledFrom(const QString &fileName)
+{
+ for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
+ if (!iter->m_ForceEnabled) {
+ iter->m_Enabled = false;
+ }
+ iter->m_LoadOrder = -1;
+ }
+
+ QFile file(fileName);
+ if (!file.exists()) {
+ throw std::runtime_error(QObject::tr("failed to find \"%1\"").arg(fileName).toUtf8().constData());
+ }
+
+ file.open(QIODevice::ReadOnly);
+ while (!file.atEnd()) {
+ QByteArray line = file.readLine();
+ QString modName;
+ if ((line.size() > 0) && (line.at(0) != '#')) {
+ modName = m_LocalCodec->toUnicode(line.trimmed().constData());
+ }
+ if (modName.size() > 0) {
+ std::map<QString, int>::iterator iter = m_ESPsByName.find(modName.toLower());
+ if (iter != m_ESPsByName.end()) {
+ m_ESPs[iter->second].m_Enabled = true;
+ } else {
+ qWarning("plugin %s not found", modName.toUtf8().constData());
+ startSaveTime();
+ }
+ }
+ }
+
+ file.close();
+
+ testMasters();
+}
+
+
+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<QByteArray> fields = line.split('|');
+ if (fields.count() == 2) {
+ m_LockedOrder[QString::fromUtf8(fields.at(0))] = fields.at(1).trimmed().toInt();
+ } else {
+ reportError(tr("The file containing locked plugin indices is broken"));
+ break;
+ }
+ }
+ }
+ file.close();
+}
+
+
+
+void PluginList::writePlugins(const QString &fileName, bool writeUnchecked) const
+{
+ SafeWriteFile file(fileName);
+
+ QTextCodec *textCodec = writeUnchecked ? m_Utf8Codec : m_LocalCodec;
+
+ file->resize(0);
+
+ file->write(textCodec->fromUnicode("# This file was automatically generated by Mod Organizer.\r\n"));
+
+ QStringList saveList;
+
+ bool invalidFileNames = false;
+ int writtenCount = 0;
+ for (size_t i = 0; i < m_ESPs.size(); ++i) {
+ int priority = m_ESPsByPriority[i];
+ if (m_ESPs[priority].m_Enabled || writeUnchecked) {
+ //file.write(m_ESPs[priority].m_Name.toUtf8());
+ if (!textCodec->canEncode(m_ESPs[priority].m_Name)) {
+ invalidFileNames = true;
+ qCritical("invalid plugin name %s", m_ESPs[priority].m_Name.toUtf8().constData());
+ } else {
+ saveList << m_ESPs[priority].m_Name;
+ file->write(textCodec->fromUnicode(m_ESPs[priority].m_Name));
+ }
+ file->write("\r\n");
+ ++writtenCount;
+ }
+ }
+
+ if (invalidFileNames) {
+ reportError(tr("Some of your plugins have invalid names! These plugins can not be loaded by the game. "
+ "Please see mo_interface.log for a list of affected plugins and rename them."));
+ }
+
+ if (file.commitIfDifferent(m_LastSaveHash[fileName])) {
+ 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();
+}
+
+
+void PluginList::saveTo(const QString &pluginFileName
+ , const QString &loadOrderFileName
+ , const QString &lockedOrderFileName
+ , const QString& deleterFileName
+ , bool hideUnchecked) const
+{
+ writePlugins(pluginFileName, false);
+ writePlugins(loadOrderFileName, true);
+ writeLockedOrder(lockedOrderFileName);
+
+ 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 {
+ shellDelete(QStringList() << deleterFileName);
+ }
+
+ m_SaveTimer.stop();
+}
+
+
+bool PluginList::saveLoadOrder(DirectoryEntry &directoryStructure)
+{
+ if (GameInfo::instance().getLoadOrderMechanism() != GameInfo::TYPE_FILETIME) {
+ // nothing to do
+ return true;
+ }
+
+ for (std::vector<ESPInfo>::iterator iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
+ std::wstring espName = ToWString(iter->m_Name);
+ const FileEntry::Ptr fileEntry = directoryStructure.findFile(espName);
+ if (fileEntry.get() != NULL) {
+ QString fileName;
+ bool archive = false;
+ int originid = fileEntry->getOrigin(archive);
+ fileName = QString("%1\\%2").arg(QDir::toNativeSeparators(ToQString(directoryStructure.getOriginByID(originid).getPath()))).arg(iter->m_Name);
+
+ HANDLE file = ::CreateFile(ToWString(fileName).c_str(), GENERIC_READ | GENERIC_WRITE,
+ 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ 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 + iter->m_Priority) * 24 * 60 * 60 * 10000000ULL;
+
+ FILETIME newWriteTime;
+
+ newWriteTime.dwLowDateTime = (DWORD)(temp & 0xFFFFFFFF);
+ newWriteTime.dwHighDateTime = (DWORD)(temp >> 32);
+ iter->m_Time = newWriteTime;
+ fileEntry->setFileTime(newWriteTime);
+ if (!::SetFileTime(file, NULL, NULL, &newWriteTime)) {
+ throw windows_error(QObject::tr("failed to set file time %1").arg(fileName).toUtf8().constData());
+ }
+
+ CloseHandle(file);
+ }
+ }
+ return true;
+}
+
+int PluginList::enabledCount() const
+{
+ int enabled = 0;
+ foreach (auto info, m_ESPs) {
+ if (info.m_Enabled) {
+ ++enabled;
+ }
+ }
+ return enabled;
+}
+
+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) {
+ m_LockedOrder[getName(index).toLower()] = m_ESPs.at(index).m_LoadOrder;
+ } else {
+ auto iter = m_LockedOrder.find(getName(index).toLower());
+ if (iter != m_LockedOrder.end()) {
+ m_LockedOrder.erase(iter);
+ }
+ }
+ startSaveTime();
+}
+
+
+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;
+ }
+ }
+}
+
+void PluginList::refreshLoadOrder()
+{
+ ChangeBracket<PluginList> layoutChange(this);
+ syncLoadOrder();
+ // set priorities according to locked load order
+ std::map<int, QString> lockedLoadOrder;
+ std::for_each(m_LockedOrder.begin(), m_LockedOrder.end(),
+ [&lockedLoadOrder] (const std::pair<QString, int> &ele) { lockedLoadOrder[ele.second] = ele.first; });
+
+ int targetPrio = 0;
+ // this is guaranteed to iterate from lowest key (load order) to highest
+ for (auto iter = lockedLoadOrder.begin(); iter != lockedLoadOrder.end(); ++iter) {
+ auto nameIter = m_ESPsByName.find(iter->second);
+ if (nameIter != m_ESPsByName.end()) {
+ // locked esp exists
+
+ // find the location to insert at
+ while ((targetPrio < static_cast<int>(m_ESPs.size() - 1)) &&
+ (m_ESPs[m_ESPsByPriority[targetPrio]].m_LoadOrder < iter->first)) {
+ ++targetPrio;
+ }
+
+ if (static_cast<size_t>(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();
+ startSaveTime();
+ }
+ }
+ }
+}
+
+
+
+
+IPluginList::PluginState 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;
+ }
+}
+
+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;
+ }
+}
+
+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;
+ }
+}
+
+QString PluginList::origin(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_OriginName;
+ }
+}
+
+bool PluginList::onRefreshed(const std::function<void ()> &callback)
+{
+ auto conn = m_Refreshed.connect(callback);
+ return conn.connected();
+}
+
+
+bool PluginList::onPluginMoved(const std::function<void (const QString &, int, int)> &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) {
+ m_ESPsByName[m_ESPs[i].m_Name.toLower()] = i;
+ m_ESPsByPriority[m_ESPs[i].m_Priority] = i;
+ }
+}
+
+
+int PluginList::rowCount(const QModelIndex &parent) const
+{
+ if (!parent.isValid()) {
+ return m_ESPs.size();
+ } else {
+ return 0;
+ }
+}
+
+int PluginList::columnCount(const QModelIndex &) const
+{
+ return COL_LASTCOLUMN + 1;
+}
+
+
+void PluginList::testMasters()
+{
+// emit layoutAboutToBeChanged();
+
+ std::set<QString> enabledMasters;
+ for (auto iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
+ if (iter->m_Enabled) {
+ enabledMasters.insert(iter->m_Name.toLower());
+ }
+ }
+
+ for (auto iter = m_ESPs.begin(); iter != m_ESPs.end(); ++iter) {
+ iter->m_MasterUnset.clear();
+ if (iter->m_Enabled) {
+ for (auto master = iter->m_Masters.begin(); master != iter->m_Masters.end(); ++master) {
+ if (enabledMasters.find(master->toLower()) == enabledMasters.end()) {
+ iter->m_MasterUnset.insert(*master);
+ }
+ }
+ }
+ }
+
+#pragma message("emitting this seems to cause a crash!")
+// 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 {
+ return QString("%1").arg(m_ESPs[index].m_LoadOrder, 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::FontRole) {
+ QFont result;
+ if (m_ESPs[index].m_IsMaster) {
+ result.setItalic(true);
+ result.setWeight(QFont::Bold);
+ } else if (m_ESPs[index].m_IsDummy) {
+ result.setItalic(true);
+ }
+ 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("<br>") + "<br><hr>";
+ }
+ }
+ if (m_ESPs[index].m_ForceEnabled) {
+ toolTip += tr("This plugin can't be disabled (enforced by the game)");
+ } else {
+ QString text = tr("<b>Origin</b>: %1").arg(m_ESPs[index].m_OriginName);
+ if (m_ESPs[index].m_Author.size() > 0) {
+ text += "<br><b>" + tr("Author") + "</b>: " + m_ESPs[index].m_Author;
+ }
+ if (m_ESPs[index].m_Description.size() > 0) {
+ text += "<br><b>" + tr("Description") + "</b>: " + m_ESPs[index].m_Description;
+ }
+ if (m_ESPs[index].m_MasterUnset.size() > 0) {
+ text += "<br><b>" + tr("Missing Masters") + "</b>: <b>" + SetJoin(m_ESPs[index].m_MasterUnset, ", ") + "</b>";
+ }
+ std::set<QString> 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.size() > 0) {
+ text += "<br><b>" + tr("Enabled Masters") + "</b>: " + SetJoin(enabledMasters, ", ");
+ }
+ if (m_ESPs[index].m_HasIni) {
+ text += "<br>There is an ini file connected to this esp. Its settings will be added to your game settings, overwriting "
+ "in case of conflicts.";
+ } else if (m_ESPs[index].m_IsDummy) {
+ text += "<br>This file is a dummy! It exists only so the bsa with the same name gets loaded. If you let MO manage archives you "
+ "don't need this: Enable the archive with the same name in the \"Archive\" tab and disable this plugin.";
+ }
+ 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(QIcon(":/MO/gui/warning"));
+ }
+ if (m_LockedOrder.find(nameLower) != m_LockedOrder.end()) {
+ result.append(QIcon(":/MO/gui/locked"));
+ }
+ auto bossInfoIter = m_AdditionalInfo.find(nameLower);
+ if (bossInfoIter != m_AdditionalInfo.end()) {
+ if (!bossInfoIter->second.m_Messages.isEmpty()) {
+ result.append(QIcon(":/MO/gui/information"));
+ }
+ /*if (bossInfoIter->second.m_LOOTUnrecognized) {
+ result.append(QIcon(":/MO/gui/help"));
+ }*/
+ }
+ if (m_ESPs[index].m_HasIni) {
+ result.append(QIcon(":/MO/gui/attachment"));
+ }
+ if (m_ESPs[index].m_IsDummy && m_ESPs[index].m_Enabled && !m_ESPs[index].m_HasIni) {
+ result.append(QIcon(":/MO/gui/edit_clear"));
+ }
+ return result;
+ }
+ return QVariant();
+}
+
+
+bool PluginList::setData(const QModelIndex &modIndex, const QVariant &value, int role)
+{
+ bool result = false;
+ if (role == Qt::CheckStateRole) {
+ m_ESPs[modIndex.row()].m_Enabled = value.toInt() == Qt::Checked;
+ emit dataChanged(modIndex, modIndex);
+
+ refreshLoadOrder();
+ startSaveTime();
+
+ 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();
+ }
+ }
+ 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;
+ }
+ }
+ 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;
+ }
+ } else {
+ result |= Qt::ItemIsDropEnabled;
+ }
+
+ return result;
+}
+
+
+void PluginList::setPluginPriority(int row, int &newPriority)
+{
+ int newPriorityTemp = newPriority;
+
+ if (!m_ESPs[row].m_IsMaster) {
+ // don't allow esps to be moved above esms
+ while ((newPriorityTemp < static_cast<int>(m_ESPsByPriority.size() - 1)) &&
+ m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster) {
+ ++newPriorityTemp;
+ }
+ } else {
+ // don't allow esms to be moved below esps
+ while ((newPriorityTemp > 0) &&
+ !m_ESPs.at(m_ESPsByPriority.at(newPriorityTemp)).m_IsMaster) {
+ --newPriorityTemp;
+ }
+ // also don't allow "regular" esms to be moved above primary plugins
+ while ((newPriorityTemp < static_cast<int>(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<int>(m_ESPsByPriority.size())) newPriorityTemp = 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 {
+ 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();
+}
+
+
+void PluginList::changePluginPriority(std::vector<int> rows, int newPriority)
+{
+ ChangeBracket<PluginList> layoutChange(this);
+ // sort rows to insert by their old priority (ascending) and insert them move them in that order
+ const std::vector<ESPInfo> &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<int>::const_iterator iter = rows.begin();
+ iter != rows.end(); ++iter) {
+ if (m_ESPs[*iter].m_Priority < newPriority) {
+ --newPriority;
+ break;
+ }
+ }
+
+ for (std::vector<int>::const_iterator iter = rows.begin(); iter != rows.end(); ++iter) {
+ setPluginPriority(*iter, newPriority);
+ }
+
+ layoutChange.finish();
+ refreshLoadOrder();
+
+ startSaveTime();
+}
+
+
+void PluginList::startSaveTime()
+{
+ testMasters();
+
+ if (!m_SaveTimer.isActive()) {
+ m_SaveTimer.start(2000);
+ }
+}
+
+
+bool PluginList::dropMimeData(const QMimeData *mimeData, Qt::DropAction action, int row, int, const QModelIndex &parent)
+{
+ if (action == Qt::IgnoreAction) {
+ return true;
+ }
+
+ QByteArray encoded = mimeData->data("application/x-qabstractitemmodeldatalist");
+ QDataStream stream(&encoded, QIODevice::ReadOnly);
+
+ std::vector<int> sourceRows;
+
+ while (!stream.atEnd()) {
+ int sourceRow, col;
+ QMap<int, QVariant> roleDataMap;
+ stream >> sourceRow >> col >> roleDataMap;
+ if (col == 0) { // only add each row once
+ sourceRows.push_back(sourceRow);
+ }
+ }
+
+ if (row == -1) {
+ row = parent.row();
+ }
+
+ int newPriority = 0;
+
+ if ((row < 0) ||
+ (row >= static_cast<int>(m_ESPs.size()))) {
+ newPriority = m_ESPs.size();
+ } else {
+ newPriority = m_ESPs[row].m_Priority;
+ }
+ changePluginPriority(sourceRows, newPriority);
+
+ return false;
+}
+
+QModelIndex PluginList::index(int row, int column, const QModelIndex&) const
+{
+ if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) {
+ return QModelIndex();
+ }
+ return createIndex(row, column, row);
+}
+
+QModelIndex PluginList::parent(const QModelIndex&) const
+{
+ return QModelIndex();
+}
+
+
+bool PluginList::eventFilter(QObject *obj, QEvent *event)
+{
+ if (event->type() == QEvent::KeyPress) {
+ QAbstractItemView *itemView = qobject_cast<QAbstractItemView*>(obj);
+
+ if (itemView == NULL) {
+ return QObject::eventFilter(obj, event);
+ }
+
+ QKeyEvent *keyEvent = static_cast<QKeyEvent*>(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<const QSortFilterProxyModel*>(selectionModel->model());
+ 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();
+ if (keyEvent->key() == Qt::Key_Down) {
+ for (int i = 0; i < rows.size() / 2; ++i) {
+ rows.swap(i, rows.size() - i - 1);
+ }
+ }
+ foreach (QModelIndex idx, rows) {
+ if (proxyModel != NULL) {
+ 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<const QSortFilterProxyModel*>(selectionModel->model());
+
+ QModelIndex minRow, maxRow;
+ foreach (QModelIndex idx, selectionModel->selectedRows()) {
+ if (proxyModel != NULL) {
+ 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 QObject::eventFilter(obj, event);
+}
+
+
+PluginList::ESPInfo::ESPInfo(const QString &name, bool enabled, FILETIME time,
+ const QString &originName, const QString &fullPath,
+ bool hasIni)
+ : m_Name(name), m_Enabled(enabled), m_ForceEnabled(enabled),
+ m_Priority(0), m_LoadOrder(-1), m_Time(time), m_OriginName(originName), m_HasIni(hasIni)
+{
+ try {
+ ESP::File file(ToWString(fullPath));
+ m_IsMaster = file.isMaster();
+ m_IsDummy = file.isDummy();
+ m_Author = QString::fromLatin1(file.author().c_str());
+ m_Description = QString::fromLatin1(file.description().c_str());
+ std::set<std::string> 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 esp file %s: %s", qPrintable(fullPath), e.what());
+ m_IsMaster = false;
+ m_IsDummy = false;
+ }
+}
diff --git a/src/pluginlist.h b/src/pluginlist.h index d4a245f0..b8e141fc 100644 --- a/src/pluginlist.h +++ b/src/pluginlist.h @@ -1,344 +1,344 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef PLUGINLIST_H -#define PLUGINLIST_H - -#include <directoryentry.h> -#include <ipluginlist.h> -#include <QString> -#include <QListWidget> -#include <QTimer> -#include <QTemporaryFile> -#include <boost/signals2.hpp> -#include <boost/ptr_container/ptr_vector.hpp> -#include <vector> -#include <map> -#include "pdll.h" -#include <BOSS-API.h> - - - -template <class C> -class ChangeBracket { -public: - ChangeBracket(C *model) - : m_Model(nullptr) - { - QVariant var = model->property("__aboutToChange"); - bool aboutToChange = var.isValid() && var.toBool(); - if (!aboutToChange) { - model->layoutAboutToBeChanged(); - model->setProperty("__aboutToChange", true); - m_Model = model; - } - } - ~ChangeBracket() { - finish(); - } - - void finish() { - if (m_Model != nullptr) { - m_Model->layoutChanged(); - m_Model->setProperty("__aboutToChange", false); - m_Model = nullptr; - } - } - -private: - C *m_Model; -}; - - - -/** - * @brief model representing the plugins (.esp/.esm) in the current virtual data folder - **/ -class PluginList : public QAbstractItemModel, public MOBase::IPluginList -{ - Q_OBJECT - friend class ChangeBracket<PluginList>; -public: - - enum EColumn { - COL_NAME, - COL_FLAGS, - COL_PRIORITY, - COL_MODINDEX, - - COL_LASTCOLUMN = COL_MODINDEX - }; - - typedef boost::signals2::signal<void ()> SignalRefreshed; - typedef boost::signals2::signal<void (const QString &, int, int)> SignalPluginMoved; - -public: - - /** - * @brief constructor - * - * @param parent parent object - **/ - PluginList(QObject *parent = NULL); - - ~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 pluginsFile file that stores the list of enabled plugins - * @param loadOrderFile file that stored the load order (not an official file but used by many tools for skyrim) - * @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 &pluginsFile - , const QString &loadOrderFile - , 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 update the plugin status (enabled/disabled) from the specified file - * - * @param fileName path of the file to load. the filename should be "plugin.txt" - * @todo it would make sense to move this into the Profile-class - **/ - void readEnabledFrom(const QString &fileName); - - /** - * @brief save the plugin status to the specified file - * - * @param pluginFileName path of the plugin.txt to write to - * @param loadOrderFileName path of the loadorder.txt to write to - * @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 &pluginFileName - , const QString &loadOrderFileName - , 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 refreshLoadOrder(); - -public: - - virtual PluginState state(const QString &name) const; - virtual int priority(const QString &name) const; - virtual int loadOrder(const QString &name) const; - virtual bool isMaster(const QString &name) const; - virtual QString origin(const QString &name) const; - virtual bool onRefreshed(const std::function<void()> &callback); - virtual bool onPluginMoved(const std::function<void (const QString &, int, int)> &func); - -public: // implementation of the QAbstractTableModel interface - - virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; - virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; - virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; - virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); - virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; - virtual Qt::ItemFlags flags(const QModelIndex &index) const; - virtual Qt::DropActions supportedDropActions() const { return Qt::MoveAction; } - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const; - virtual QModelIndex parent(const QModelIndex &child) const; - -public slots: - - /** - * @brief enables ALL plugins - **/ - void enableAll(); - - /** - * @brief disables ALL plugins - **/ - void disableAll(); - -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 should be saved - */ - void saveTimer(); - -private: - - struct ESPInfo { - - ESPInfo(const QString &name, bool enabled, FILETIME time, const QString &originName, const QString &fullPath, bool hasIni); - QString m_Name; - bool m_Enabled; - bool m_ForceEnabled; - int m_Priority; - int m_LoadOrder; - FILETIME m_Time; - QString m_OriginName; - bool m_IsMaster; - bool m_IsDummy; - QString m_Author; - QString m_Description; - bool m_HasIni; - std::set<QString> m_Masters; - mutable std::set<QString> m_MasterUnset; - }; - - 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 writePlugins(const QString &fileName, bool writeUnchecked) const; - void writeLockedOrder(const QString &fileName) const; - - bool readLoadOrder(const QString &fileName); - void readLockedOrderFrom(const QString &fileName); - void setPluginPriority(int row, int &newPriority); - void changePluginPriority(std::vector<int> rows, int newPriority); - - void startSaveTime(); - - void testMasters(); - -private: - - std::vector<ESPInfo> m_ESPs; - mutable std::map<QString, QByteArray> m_LastSaveHash; - - std::map<QString, int> m_ESPsByName; - std::vector<int> m_ESPsByPriority; - - // maps esp names to the priority specified in loadorder.txt. The esp names are - // all lowercase!! This is to work around the fact that BOSS for some reason writes some file with - // capitalization that doesn't match the actual name - std::map<QString, int> m_ESPLoadOrder; - std::map<QString, int> m_LockedOrder; - - std::map<QString, AdditionalInfo> m_AdditionalInfo; // maps esp names to boss information - - QString m_CurrentProfile; - QFontMetrics m_FontMetrics; - - QTextCodec *m_Utf8Codec; - QTextCodec *m_LocalCodec; - - mutable QTimer m_SaveTimer; - - SignalRefreshed m_Refreshed; - - QTemporaryFile m_TempFile; - - SignalPluginMoved m_PluginMoved; - -}; - - - -#endif // PLUGINLIST_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef PLUGINLIST_H
+#define PLUGINLIST_H
+
+#include <directoryentry.h>
+#include <ipluginlist.h>
+#include <QString>
+#include <QListWidget>
+#include <QTimer>
+#include <QTemporaryFile>
+#include <boost/signals2.hpp>
+#include <boost/ptr_container/ptr_vector.hpp>
+#include <vector>
+#include <map>
+#include "pdll.h"
+#include <BOSS-API.h>
+
+
+
+template <class C>
+class ChangeBracket {
+public:
+ ChangeBracket(C *model)
+ : m_Model(nullptr)
+ {
+ QVariant var = model->property("__aboutToChange");
+ bool aboutToChange = var.isValid() && var.toBool();
+ if (!aboutToChange) {
+ model->layoutAboutToBeChanged();
+ model->setProperty("__aboutToChange", true);
+ m_Model = model;
+ }
+ }
+ ~ChangeBracket() {
+ finish();
+ }
+
+ void finish() {
+ if (m_Model != nullptr) {
+ m_Model->layoutChanged();
+ m_Model->setProperty("__aboutToChange", false);
+ m_Model = nullptr;
+ }
+ }
+
+private:
+ C *m_Model;
+};
+
+
+
+/**
+ * @brief model representing the plugins (.esp/.esm) in the current virtual data folder
+ **/
+class PluginList : public QAbstractItemModel, public MOBase::IPluginList
+{
+ Q_OBJECT
+ friend class ChangeBracket<PluginList>;
+public:
+
+ enum EColumn {
+ COL_NAME,
+ COL_FLAGS,
+ COL_PRIORITY,
+ COL_MODINDEX,
+
+ COL_LASTCOLUMN = COL_MODINDEX
+ };
+
+ typedef boost::signals2::signal<void ()> SignalRefreshed;
+ typedef boost::signals2::signal<void (const QString &, int, int)> SignalPluginMoved;
+
+public:
+
+ /**
+ * @brief constructor
+ *
+ * @param parent parent object
+ **/
+ PluginList(QObject *parent = NULL);
+
+ ~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 pluginsFile file that stores the list of enabled plugins
+ * @param loadOrderFile file that stored the load order (not an official file but used by many tools for skyrim)
+ * @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 &pluginsFile
+ , const QString &loadOrderFile
+ , 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 update the plugin status (enabled/disabled) from the specified file
+ *
+ * @param fileName path of the file to load. the filename should be "plugin.txt"
+ * @todo it would make sense to move this into the Profile-class
+ **/
+ void readEnabledFrom(const QString &fileName);
+
+ /**
+ * @brief save the plugin status to the specified file
+ *
+ * @param pluginFileName path of the plugin.txt to write to
+ * @param loadOrderFileName path of the loadorder.txt to write to
+ * @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 &pluginFileName
+ , const QString &loadOrderFileName
+ , 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 refreshLoadOrder();
+
+public:
+
+ virtual PluginState state(const QString &name) const;
+ virtual int priority(const QString &name) const;
+ virtual int loadOrder(const QString &name) const;
+ virtual bool isMaster(const QString &name) const;
+ virtual QString origin(const QString &name) const;
+ virtual bool onRefreshed(const std::function<void()> &callback);
+ virtual bool onPluginMoved(const std::function<void (const QString &, int, int)> &func);
+
+public: // implementation of the QAbstractTableModel interface
+
+ virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual int columnCount(const QModelIndex &parent = QModelIndex()) const;
+ virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
+ virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+ virtual QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const;
+ virtual Qt::ItemFlags flags(const QModelIndex &index) const;
+ virtual Qt::DropActions supportedDropActions() const { return Qt::MoveAction; }
+ virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
+ virtual QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
+ virtual QModelIndex parent(const QModelIndex &child) const;
+
+public slots:
+
+ /**
+ * @brief enables ALL plugins
+ **/
+ void enableAll();
+
+ /**
+ * @brief disables ALL plugins
+ **/
+ void disableAll();
+
+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 should be saved
+ */
+ void saveTimer();
+
+private:
+
+ struct ESPInfo {
+
+ ESPInfo(const QString &name, bool enabled, FILETIME time, const QString &originName, const QString &fullPath, bool hasIni);
+ QString m_Name;
+ bool m_Enabled;
+ bool m_ForceEnabled;
+ int m_Priority;
+ int m_LoadOrder;
+ FILETIME m_Time;
+ QString m_OriginName;
+ bool m_IsMaster;
+ bool m_IsDummy;
+ QString m_Author;
+ QString m_Description;
+ bool m_HasIni;
+ std::set<QString> m_Masters;
+ mutable std::set<QString> m_MasterUnset;
+ };
+
+ 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 writePlugins(const QString &fileName, bool writeUnchecked) const;
+ void writeLockedOrder(const QString &fileName) const;
+
+ bool readLoadOrder(const QString &fileName);
+ void readLockedOrderFrom(const QString &fileName);
+ void setPluginPriority(int row, int &newPriority);
+ void changePluginPriority(std::vector<int> rows, int newPriority);
+
+ void startSaveTime();
+
+ void testMasters();
+
+private:
+
+ std::vector<ESPInfo> m_ESPs;
+ mutable std::map<QString, QByteArray> m_LastSaveHash;
+
+ std::map<QString, int> m_ESPsByName;
+ std::vector<int> m_ESPsByPriority;
+
+ // maps esp names to the priority specified in loadorder.txt. The esp names are
+ // all lowercase!! This is to work around the fact that BOSS for some reason writes some file with
+ // capitalization that doesn't match the actual name
+ std::map<QString, int> m_ESPLoadOrder;
+ std::map<QString, int> m_LockedOrder;
+
+ std::map<QString, AdditionalInfo> m_AdditionalInfo; // maps esp names to boss information
+
+ QString m_CurrentProfile;
+ QFontMetrics m_FontMetrics;
+
+ QTextCodec *m_Utf8Codec;
+ QTextCodec *m_LocalCodec;
+
+ mutable QTimer m_SaveTimer;
+
+ SignalRefreshed m_Refreshed;
+
+ QTemporaryFile m_TempFile;
+
+ SignalPluginMoved m_PluginMoved;
+
+};
+
+
+
+#endif // PLUGINLIST_H
diff --git a/src/pluginlistsortproxy.cpp b/src/pluginlistsortproxy.cpp index 8412fa27..02d99b36 100644 --- a/src/pluginlistsortproxy.cpp +++ b/src/pluginlistsortproxy.cpp @@ -1,152 +1,152 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "pluginlistsortproxy.h" -#include "pluginlist.h" -#include <QMenu> -#include <QCheckBox> -#include <QWidgetAction> - - -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); -} - - -unsigned int PluginListSortProxy::getEnabledColumns() const -{ - unsigned int result = 0; - for (int i = 0; i <= PluginList::COL_LASTCOLUMN; ++i) { - if (m_EnabledColumns.test(i)) { - result |= 1 << i; - } - } - return result; -} - - -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 = sourceModel()->flags(mapToSource(modelIndex)); - if (sortColumn() == PluginList::COL_NAME) { - flags &= ~Qt::ItemIsDragEnabled; - } - return flags; -} - - -void PluginListSortProxy::displayColumnSelection(const QPoint &pos) -{ - QMenu menu; - - for (int i = 0; i <= PluginList::COL_LASTCOLUMN; ++i) { - QCheckBox *checkBox = new QCheckBox(&menu); - checkBox->setText(PluginList::getColumnName(i)); - checkBox->setChecked(m_EnabledColumns.test(i) ? Qt::Checked : Qt::Unchecked); - QWidgetAction *checkableAction = new QWidgetAction(&menu); - checkableAction->setDefaultWidget(checkBox); - menu.addAction(checkableAction); - } - menu.exec(pos); - int i = 0; - - emit layoutAboutToBeChanged(); - m_EnabledColumns.reset(); - foreach (const QAction *action, menu.actions()) { - const QWidgetAction *widgetAction = qobject_cast<const QWidgetAction*>(action); - if (widgetAction != NULL) { - const QCheckBox *checkBox = qobject_cast<const QCheckBox*>(widgetAction->defaultWidget()); - if (checkBox != NULL) { - m_EnabledColumns.set(i, checkBox->checkState() == Qt::Checked); - } - } - ++i; - } - emit layoutChanged(); -} - - -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::lessThan(const QModelIndex &left, - const QModelIndex &right) const -{ - PluginList *plugins = qobject_cast<PluginList*>(sourceModel()); - switch (left.column()) { - case PluginList::COL_NAME: { - return QString::compare(plugins->getName(left.row()), plugins->getName(right.row()), Qt::CaseInsensitive) < 0; - } break; - case PluginList::COL_PRIORITY: - case PluginList::COL_MODINDEX: { - 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 ((row == -1) && (column == -1)) { - return this->sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent)); - } - // in the regular model, when dropping between rows, the row-value passed to - // the sourceModel is inconsistent between ascending and descending ordering. - // This should fix that - if (sortOrder() == Qt::DescendingOrder) { - --row; - } - - QModelIndex proxyIndex = index(row, column, parent); - QModelIndex sourceIndex = mapToSource(proxyIndex); - return this->sourceModel()->dropMimeData(data, action, sourceIndex.row(), sourceIndex.column(), - sourceIndex.parent()); -} - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "pluginlistsortproxy.h"
+#include "pluginlist.h"
+#include <QMenu>
+#include <QCheckBox>
+#include <QWidgetAction>
+
+
+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);
+}
+
+
+unsigned int PluginListSortProxy::getEnabledColumns() const
+{
+ unsigned int result = 0;
+ for (int i = 0; i <= PluginList::COL_LASTCOLUMN; ++i) {
+ if (m_EnabledColumns.test(i)) {
+ result |= 1 << i;
+ }
+ }
+ return result;
+}
+
+
+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 = sourceModel()->flags(mapToSource(modelIndex));
+ if (sortColumn() == PluginList::COL_NAME) {
+ flags &= ~Qt::ItemIsDragEnabled;
+ }
+ return flags;
+}
+
+
+void PluginListSortProxy::displayColumnSelection(const QPoint &pos)
+{
+ QMenu menu;
+
+ for (int i = 0; i <= PluginList::COL_LASTCOLUMN; ++i) {
+ QCheckBox *checkBox = new QCheckBox(&menu);
+ checkBox->setText(PluginList::getColumnName(i));
+ checkBox->setChecked(m_EnabledColumns.test(i) ? Qt::Checked : Qt::Unchecked);
+ QWidgetAction *checkableAction = new QWidgetAction(&menu);
+ checkableAction->setDefaultWidget(checkBox);
+ menu.addAction(checkableAction);
+ }
+ menu.exec(pos);
+ int i = 0;
+
+ emit layoutAboutToBeChanged();
+ m_EnabledColumns.reset();
+ foreach (const QAction *action, menu.actions()) {
+ const QWidgetAction *widgetAction = qobject_cast<const QWidgetAction*>(action);
+ if (widgetAction != NULL) {
+ const QCheckBox *checkBox = qobject_cast<const QCheckBox*>(widgetAction->defaultWidget());
+ if (checkBox != NULL) {
+ m_EnabledColumns.set(i, checkBox->checkState() == Qt::Checked);
+ }
+ }
+ ++i;
+ }
+ emit layoutChanged();
+}
+
+
+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::lessThan(const QModelIndex &left,
+ const QModelIndex &right) const
+{
+ PluginList *plugins = qobject_cast<PluginList*>(sourceModel());
+ switch (left.column()) {
+ case PluginList::COL_NAME: {
+ return QString::compare(plugins->getName(left.row()), plugins->getName(right.row()), Qt::CaseInsensitive) < 0;
+ } break;
+ case PluginList::COL_PRIORITY:
+ case PluginList::COL_MODINDEX: {
+ 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 ((row == -1) && (column == -1)) {
+ return this->sourceModel()->dropMimeData(data, action, -1, -1, mapToSource(parent));
+ }
+ // in the regular model, when dropping between rows, the row-value passed to
+ // the sourceModel is inconsistent between ascending and descending ordering.
+ // This should fix that
+ if (sortOrder() == Qt::DescendingOrder) {
+ --row;
+ }
+
+ QModelIndex proxyIndex = index(row, column, parent);
+ QModelIndex sourceIndex = mapToSource(proxyIndex);
+ return this->sourceModel()->dropMimeData(data, action, sourceIndex.row(), sourceIndex.column(),
+ sourceIndex.parent());
+}
+
diff --git a/src/pluginlistsortproxy.h b/src/pluginlistsortproxy.h index d5c71858..bc8a005b 100644 --- a/src/pluginlistsortproxy.h +++ b/src/pluginlistsortproxy.h @@ -1,71 +1,71 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef PLUGINLISTSORTPROXY_H -#define PLUGINLISTSORTPROXY_H - - -#include <bitset> -#include <QSortFilterProxyModel> -#include "pluginlist.h" - - -class PluginListSortProxy : public QSortFilterProxyModel -{ - Q_OBJECT -public: - - enum ESorting { - SORT_ASCENDING, - SORT_DESCENDING - }; - -public: - - explicit PluginListSortProxy(QObject *parent = 0); - - unsigned int getEnabledColumns() const; - - void setEnabledColumns(unsigned int columns); - - virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const; - - virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent); - -public slots: - - void displayColumnSelection(const QPoint &pos); - void updateFilter(const QString &filter); - -protected: - - virtual bool filterAcceptsRow(int row, const QModelIndex &parent) const; - virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const; - -private: - - int m_SortIndex; - Qt::SortOrder m_SortOrder; - - std::bitset<PluginList::COL_LASTCOLUMN + 1> m_EnabledColumns; - QString m_CurrentFilter; - -}; - -#endif // PLUGINLISTSORTPROXY_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef PLUGINLISTSORTPROXY_H
+#define PLUGINLISTSORTPROXY_H
+
+
+#include <bitset>
+#include <QSortFilterProxyModel>
+#include "pluginlist.h"
+
+
+class PluginListSortProxy : public QSortFilterProxyModel
+{
+ Q_OBJECT
+public:
+
+ enum ESorting {
+ SORT_ASCENDING,
+ SORT_DESCENDING
+ };
+
+public:
+
+ explicit PluginListSortProxy(QObject *parent = 0);
+
+ unsigned int getEnabledColumns() const;
+
+ void setEnabledColumns(unsigned int columns);
+
+ virtual Qt::ItemFlags flags(const QModelIndex &modelIndex) const;
+
+ virtual bool dropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent);
+
+public slots:
+
+ void displayColumnSelection(const QPoint &pos);
+ void updateFilter(const QString &filter);
+
+protected:
+
+ virtual bool filterAcceptsRow(int row, const QModelIndex &parent) const;
+ virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
+
+private:
+
+ int m_SortIndex;
+ Qt::SortOrder m_SortOrder;
+
+ std::bitset<PluginList::COL_LASTCOLUMN + 1> m_EnabledColumns;
+ QString m_CurrentFilter;
+
+};
+
+#endif // PLUGINLISTSORTPROXY_H
diff --git a/src/profile.cpp b/src/profile.cpp index 2bbdea66..fbfb3266 100644 --- a/src/profile.cpp +++ b/src/profile.cpp @@ -1,782 +1,782 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "profile.h" -#include "report.h" -#include "gameinfo.h" -#include "windows_error.h" -#include "dummybsa.h" -#include "modinfo.h" -#include "safewritefile.h" -#include <utility.h> -#include <util.h> -#include <error_report.h> -#include <appconfig.h> -#include <QMessageBox> -#include <QApplication> -#include <QSettings> -#include <QTemporaryFile> - -#define WIN32_LEAN_AND_MEAN -#include <windows.h> -#include <shlobj.h> -#include <stdexcept> - -using namespace MOBase; -using namespace MOShared; - -Profile::Profile() - : m_SaveTimer(NULL) -{ - initTimer(); -} - -Profile::Profile(const QString &name, bool useDefaultSettings) - : m_SaveTimer(NULL) -{ - initTimer(); - QString profilesDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())); - QDir profileBase(profilesDir); - - QString fixedName = name; - if (!fixDirectoryName(fixedName)) { - throw MyException(tr("invalid profile name %1").arg(name)); - } - - 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); - QFile modList(m_Directory.filePath("modlist.txt")); - if (!modList.open(QIODevice::ReadWrite)) { - profileBase.rmdir(fixedName); - throw std::runtime_error(QObject::tr("failed to create %1").arg(m_Directory.filePath("modlist.txt")).toUtf8().constData()); - } - modList.close(); - - try { - GameInfo::instance().createProfile(ToWString(fullPath), useDefaultSettings); - } catch (...) { - // clean up in case of an error - shellDelete(QStringList(profileBase.absoluteFilePath(fixedName))); - throw; - } - refreshModStatus(); -} - - -Profile::Profile(const QDir& directory) - : m_Directory(directory), m_SaveTimer(NULL) -{ - initTimer(); - if (!QFile::exists(m_Directory.filePath("modlist.txt"))) { - qWarning("missing modlist.txt in %s", qPrintable(directory.path())); - } - - GameInfo::instance().repairProfile(ToWString(m_Directory.absolutePath())); - - if (!QFile::exists(getIniFileName())) { - reportError(QObject::tr("\"%1\" is missing or inaccessible").arg(getIniFileName())); - } - refreshModStatus(); -} - - -Profile::Profile(const Profile& reference) - : m_Directory(reference.m_Directory), m_SaveTimer(NULL) -{ - initTimer(); - refreshModStatus(); -} - - -Profile::~Profile() -{ - writeModlistNow(); -} - - -void Profile::initTimer() -{ - m_SaveTimer = new QTimer(this); - m_SaveTimer->setSingleShot(true); - connect(m_SaveTimer, SIGNAL(timeout()), this, SLOT(writeModlistNow())); -} - - -bool Profile::exists() const -{ - return m_Directory.exists(); -} - - -void Profile::writeModlist() const -{ - if (!m_SaveTimer->isActive()) { - m_SaveTimer->start(2000); - } -} - - -void Profile::cancelWriteModlist() const -{ - m_SaveTimer->stop(); -} - - -void Profile::writeModlistNow(bool onlyOnTimer) const -{ - if (onlyOnTimer && !m_SaveTimer->isActive()) return; - - m_SaveTimer->stop(); - 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 = 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<ModInfo::EFlag> 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; - } -} - - -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; - } - - for (unsigned int i = 0; i < m_ModStatus.size(); ++i) { - if (m_ModStatus[i].m_Enabled) { - ModInfo::Ptr modInfo = ModInfo::getByIndex(i); - mergeTweaks(modInfo, tweakedIni); - } - } - - mergeTweak(getProfileTweaks(), tweakedIni); - - bool error = false; - if (!::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) { - error = true; - } - - if (localSavesEnabled()) { - if (!::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"1", ToWString(tweakedIni).c_str())) { - error = true; - } - - if (!::WritePrivateProfileStringW(L"General", L"SLocalSavePath", - AppConfig::localSavePlaceholder(), - 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))); -} - - -void Profile::refreshModStatus() -{ - QFile file(getModlistFileName()); - if (!file.exists()) { - throw MyException(tr("\"%1\" is missing or inaccessible").arg(getModlistFileName())); - } - - bool modStatusModified = false; - m_ModStatus.clear(); - m_ModStatus.resize(ModInfo::getNumMods()); - - std::set<QString> namesRead; - - // load mods from file and update enabled state and priority for them - file.open(QIODevice::ReadOnly); - int index = 0; - while (!file.atEnd()) { - QByteArray line = file.readLine(); - 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) { - if (namesRead.find(modName) != namesRead.end()) { - continue; - } else { - namesRead.insert(modName); - } - unsigned int modindex = ModInfo::getIndex(modName); - 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 || info->alwaysEnabled(); - if (m_ModStatus[modindex].m_Priority == -1) { - if (static_cast<size_t>(index) >= m_ModStatus.size()) { - throw MyException(tr("invalid index %1").arg(index)); - } - m_ModStatus[modindex].m_Priority = index++; - } - } else { - qDebug("mod \"%s\" (profile \"%s\") not found", - modName.toUtf8().constData(), m_Directory.path().toUtf8().constData()); - // need to rewrite the modlist to fix this - modStatusModified = true; - } - } - } else { - // line was empty after trimming - } - } - - int numKnownMods = index; - - 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(i); - 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<size_t>(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(i); - if (modInfo->getFixedPriority() == INT_MAX) { - continue; - } - - m_ModStatus[i].m_Priority += offset; - } - } - - file.close(); - updateIndices(); - if (modStatusModified) { - writeModlist(); - } -} - - -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<int>(m_ModIndexByPriority.size())) { - qCritical("invalid priority %d for mod", priority); - continue; - } else { - ++m_NumRegularMods; - m_ModIndexByPriority.at(priority) = i; - } - } -} - - -std::vector<std::tuple<QString, QString, int> > Profile::getActiveMods() -{ - std::vector<std::tuple<QString, QString, int> > result; - for (std::vector<unsigned int>::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->name(), modInfo->absolutePath(), m_ModStatus[*iter].m_Priority)); - } - } - - unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool { - std::vector<ModInfo::EFlag> 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; -} - - -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; - } - - 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)); - } - - 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; -} - - -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<int>)(m_ModStatus.size() - 1, 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; - - updateIndices(); - writeModlist(); -} - - -Profile Profile::createFrom(const QString &name, const Profile &reference) -{ - QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); - reference.copyFilesTo(profileDirectory); - return Profile(QDir(profileDirectory)); -} - - -Profile *Profile::createPtrFrom(const QString &name, const Profile &reference) -{ - QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name); - reference.copyFilesTo(profileDirectory); - return new Profile(QDir(profileDirectory)); -} - - -void Profile::copyFilesTo(QString &target) const -{ - copyDir(m_Directory.absolutePath(), target, false); -} - - -std::vector<std::wstring> Profile::splitDZString(const wchar_t *buffer) const -{ - std::vector<std::wstring> 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<wchar_t> 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<std::wstring> sections = splitDZString(buffer.data()); - - // now iterate over all sections and retrieve a list of keys in each - for (std::vector<std::wstring>::iterator iter = sections.begin(); - iter != sections.end(); ++iter) { - // retrieve the names of all keys - size = ::GetPrivateProfileStringW(iter->c_str(), NULL, NULL, 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<std::wstring> keys = splitDZString(buffer.data()); - - for (std::vector<std::wstring>::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(), - NULL, 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<QString> iniTweaks = modInfo->getIniTweaks(); - for (std::vector<QString>::iterator iter = iniTweaks.begin(); - iter != iniTweaks.end(); ++iter) { - mergeTweak(*iter, tweakedIni); - } -} - - -bool Profile::invalidationActive(bool *supported) const -{ - if (GameInfo::instance().requiresBSAInvalidation()) { - *supported = true; - wchar_t buffer[1024]; - std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); - // epic ms fail: GetPrivateProfileString uses errno (for whatever reason) to signal a fail since the return value - // has a different meaning (number of bytes copied). HOWEVER, it will not set errno to 0 if NO error occured - errno = 0; - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - L"", buffer, 1024, iniFileName.c_str()) == 0) { - if (errno != 0x02) { - if (supported != NULL) { - *supported = false; - } - return false; - } else { - QString errorMessage = tr("failed to parse ini file (%1)").arg(ToQString(iniFileName)); - throw windows_error(errorMessage.toUtf8().constData()); - } - } - QStringList archives = ToQString(buffer).split(','); - - for (int i = 0; i < archives.count(); ++i) { - QString bsaName = archives.at(i).trimmed(); - if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) { - return true; - } - } - } else { - *supported = false; - } - return false; -} - - -void Profile::deactivateInvalidation() const -{ - if (GameInfo::instance().requiresBSAInvalidation()) { - wchar_t buffer[1024]; - std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); - errno = 0; - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - L"", buffer, 1024, iniFileName.c_str()) == 0) { - if (errno == 0x02) { - QString errorMessage = tr("failed to parse ini file (%1): %2").arg(QDir::toNativeSeparators(getIniFileName())).arg(::GetLastError()); - throw windows_error(errorMessage.toUtf8().constData()); - } else { - return; - } - } - QStringList archives = ToQString(buffer).split(", "); - - for (int i = 0; i < archives.count();) { - QString bsaName = archives.at(i).trimmed(); - if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) { - archives.removeAt(i); - } else { - ++i; - } - } - - // just to be safe... - ::SetFileAttributesW(iniFileName.c_str(), FILE_ATTRIBUTE_NORMAL); - - if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) || - !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"0", iniFileName.c_str()) || - !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"ArchiveInvalidation.txt", iniFileName.c_str())) { - QString errorMessage = tr("failed to modify \"%1\"").arg(ToQString(iniFileName)); - throw windows_error(errorMessage.toUtf8().constData()); - } - } -} - - -void Profile::activateInvalidation(const QString& dataDirectory) const -{ - if (GameInfo::instance().requiresBSAInvalidation()) { - wchar_t buffer[1024]; - std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName())); - errno = 0; - if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - L"", buffer, 1024, iniFileName.c_str()) == 0) { - if (errno == 0x02) { - throw windows_error("failed to parse ini file"); - } else { - // ignore. shouldn't have gotten here anyway - return; - } - } - QStringList archives = ToQString(buffer).split(", "); - - QString invalidationBSA = ToQString(GameInfo::instance().getInvalidationBSA()); - - if (!archives.contains(invalidationBSA)) { - archives.insert(0, invalidationBSA); - } - - // just to be safe... - ::SetFileAttributesW(iniFileName.c_str(), FILE_ATTRIBUTE_NORMAL); - - if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(), - ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) || - !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", iniFileName.c_str()) || - !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"", iniFileName.c_str())) { - QString errorMessage = tr("failed to modify \"%1\"").arg(ToQString(iniFileName)); - throw windows_error(errorMessage.toUtf8().constData()); - } - - QString bsaFile = dataDirectory + "/" + invalidationBSA; - if (!QFile::exists(bsaFile)) { - DummyBSA bsa; - bsa.write(bsaFile); - } - } -} - - -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"); - } - } 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; -} - - -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::getLoadOrderFileName() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath("loadorder.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::getDeleterFileName() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath("hide_plugins.txt")); -} - -QString Profile::getIniFileName() const -{ - std::wstring primaryIniFile = *(GameInfo::instance().getIniFileNames().begin()); - return m_Directory.absoluteFilePath(ToQString(primaryIniFile)); -} - -QString Profile::getProfileTweaks() const -{ - return QDir::cleanPath(m_Directory.absoluteFilePath(ToQString(AppConfig::profileTweakIni()))); -} - -QString Profile::getPath() const -{ - return QDir::cleanPath(m_Directory.absolutePath()); -} - -void Profile::rename(const QString &newName) -{ - QDir profileDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))); - profileDir.rename(getName(), newName); - m_Directory = profileDir.absoluteFilePath(newName); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "profile.h"
+#include "report.h"
+#include "gameinfo.h"
+#include "windows_error.h"
+#include "dummybsa.h"
+#include "modinfo.h"
+#include "safewritefile.h"
+#include <utility.h>
+#include <util.h>
+#include <error_report.h>
+#include <appconfig.h>
+#include <QMessageBox>
+#include <QApplication>
+#include <QSettings>
+#include <QTemporaryFile>
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <shlobj.h>
+#include <stdexcept>
+
+using namespace MOBase;
+using namespace MOShared;
+
+Profile::Profile()
+ : m_SaveTimer(NULL)
+{
+ initTimer();
+}
+
+Profile::Profile(const QString &name, bool useDefaultSettings)
+ : m_SaveTimer(NULL)
+{
+ initTimer();
+ QString profilesDir = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()));
+ QDir profileBase(profilesDir);
+
+ QString fixedName = name;
+ if (!fixDirectoryName(fixedName)) {
+ throw MyException(tr("invalid profile name %1").arg(name));
+ }
+
+ 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);
+ QFile modList(m_Directory.filePath("modlist.txt"));
+ if (!modList.open(QIODevice::ReadWrite)) {
+ profileBase.rmdir(fixedName);
+ throw std::runtime_error(QObject::tr("failed to create %1").arg(m_Directory.filePath("modlist.txt")).toUtf8().constData());
+ }
+ modList.close();
+
+ try {
+ GameInfo::instance().createProfile(ToWString(fullPath), useDefaultSettings);
+ } catch (...) {
+ // clean up in case of an error
+ shellDelete(QStringList(profileBase.absoluteFilePath(fixedName)));
+ throw;
+ }
+ refreshModStatus();
+}
+
+
+Profile::Profile(const QDir& directory)
+ : m_Directory(directory), m_SaveTimer(NULL)
+{
+ initTimer();
+ if (!QFile::exists(m_Directory.filePath("modlist.txt"))) {
+ qWarning("missing modlist.txt in %s", qPrintable(directory.path()));
+ }
+
+ GameInfo::instance().repairProfile(ToWString(m_Directory.absolutePath()));
+
+ if (!QFile::exists(getIniFileName())) {
+ reportError(QObject::tr("\"%1\" is missing or inaccessible").arg(getIniFileName()));
+ }
+ refreshModStatus();
+}
+
+
+Profile::Profile(const Profile& reference)
+ : m_Directory(reference.m_Directory), m_SaveTimer(NULL)
+{
+ initTimer();
+ refreshModStatus();
+}
+
+
+Profile::~Profile()
+{
+ writeModlistNow();
+}
+
+
+void Profile::initTimer()
+{
+ m_SaveTimer = new QTimer(this);
+ m_SaveTimer->setSingleShot(true);
+ connect(m_SaveTimer, SIGNAL(timeout()), this, SLOT(writeModlistNow()));
+}
+
+
+bool Profile::exists() const
+{
+ return m_Directory.exists();
+}
+
+
+void Profile::writeModlist() const
+{
+ if (!m_SaveTimer->isActive()) {
+ m_SaveTimer->start(2000);
+ }
+}
+
+
+void Profile::cancelWriteModlist() const
+{
+ m_SaveTimer->stop();
+}
+
+
+void Profile::writeModlistNow(bool onlyOnTimer) const
+{
+ if (onlyOnTimer && !m_SaveTimer->isActive()) return;
+
+ m_SaveTimer->stop();
+ 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 = 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<ModInfo::EFlag> 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;
+ }
+}
+
+
+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;
+ }
+
+ for (unsigned int i = 0; i < m_ModStatus.size(); ++i) {
+ if (m_ModStatus[i].m_Enabled) {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(i);
+ mergeTweaks(modInfo, tweakedIni);
+ }
+ }
+
+ mergeTweak(getProfileTweaks(), tweakedIni);
+
+ bool error = false;
+ if (!::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", ToWString(tweakedIni).c_str())) {
+ error = true;
+ }
+
+ if (localSavesEnabled()) {
+ if (!::WritePrivateProfileStringW(L"General", L"bUseMyGamesDirectory", L"1", ToWString(tweakedIni).c_str())) {
+ error = true;
+ }
+
+ if (!::WritePrivateProfileStringW(L"General", L"SLocalSavePath",
+ AppConfig::localSavePlaceholder(),
+ 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)));
+}
+
+
+void Profile::refreshModStatus()
+{
+ QFile file(getModlistFileName());
+ if (!file.exists()) {
+ throw MyException(tr("\"%1\" is missing or inaccessible").arg(getModlistFileName()));
+ }
+
+ bool modStatusModified = false;
+ m_ModStatus.clear();
+ m_ModStatus.resize(ModInfo::getNumMods());
+
+ std::set<QString> namesRead;
+
+ // load mods from file and update enabled state and priority for them
+ file.open(QIODevice::ReadOnly);
+ int index = 0;
+ while (!file.atEnd()) {
+ QByteArray line = file.readLine();
+ 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) {
+ if (namesRead.find(modName) != namesRead.end()) {
+ continue;
+ } else {
+ namesRead.insert(modName);
+ }
+ unsigned int modindex = ModInfo::getIndex(modName);
+ 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 || info->alwaysEnabled();
+ if (m_ModStatus[modindex].m_Priority == -1) {
+ if (static_cast<size_t>(index) >= m_ModStatus.size()) {
+ throw MyException(tr("invalid index %1").arg(index));
+ }
+ m_ModStatus[modindex].m_Priority = index++;
+ }
+ } else {
+ qDebug("mod \"%s\" (profile \"%s\") not found",
+ modName.toUtf8().constData(), m_Directory.path().toUtf8().constData());
+ // need to rewrite the modlist to fix this
+ modStatusModified = true;
+ }
+ }
+ } else {
+ // line was empty after trimming
+ }
+ }
+
+ int numKnownMods = index;
+
+ 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(i);
+ 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<size_t>(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(i);
+ if (modInfo->getFixedPriority() == INT_MAX) {
+ continue;
+ }
+
+ m_ModStatus[i].m_Priority += offset;
+ }
+ }
+
+ file.close();
+ updateIndices();
+ if (modStatusModified) {
+ writeModlist();
+ }
+}
+
+
+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<int>(m_ModIndexByPriority.size())) {
+ qCritical("invalid priority %d for mod", priority);
+ continue;
+ } else {
+ ++m_NumRegularMods;
+ m_ModIndexByPriority.at(priority) = i;
+ }
+ }
+}
+
+
+std::vector<std::tuple<QString, QString, int> > Profile::getActiveMods()
+{
+ std::vector<std::tuple<QString, QString, int> > result;
+ for (std::vector<unsigned int>::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->name(), modInfo->absolutePath(), m_ModStatus[*iter].m_Priority));
+ }
+ }
+
+ unsigned int overwriteIndex = ModInfo::findMod([](ModInfo::Ptr mod) -> bool {
+ std::vector<ModInfo::EFlag> 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;
+}
+
+
+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;
+ }
+
+ 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));
+ }
+
+ 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;
+}
+
+
+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<int>)(m_ModStatus.size() - 1, 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;
+
+ updateIndices();
+ writeModlist();
+}
+
+
+Profile Profile::createFrom(const QString &name, const Profile &reference)
+{
+ QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name);
+ reference.copyFilesTo(profileDirectory);
+ return Profile(QDir(profileDirectory));
+}
+
+
+Profile *Profile::createPtrFrom(const QString &name, const Profile &reference)
+{
+ QString profileDirectory = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())).append("/").append(name);
+ reference.copyFilesTo(profileDirectory);
+ return new Profile(QDir(profileDirectory));
+}
+
+
+void Profile::copyFilesTo(QString &target) const
+{
+ copyDir(m_Directory.absolutePath(), target, false);
+}
+
+
+std::vector<std::wstring> Profile::splitDZString(const wchar_t *buffer) const
+{
+ std::vector<std::wstring> 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<wchar_t> 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<std::wstring> sections = splitDZString(buffer.data());
+
+ // now iterate over all sections and retrieve a list of keys in each
+ for (std::vector<std::wstring>::iterator iter = sections.begin();
+ iter != sections.end(); ++iter) {
+ // retrieve the names of all keys
+ size = ::GetPrivateProfileStringW(iter->c_str(), NULL, NULL, 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<std::wstring> keys = splitDZString(buffer.data());
+
+ for (std::vector<std::wstring>::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(),
+ NULL, 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<QString> iniTweaks = modInfo->getIniTweaks();
+ for (std::vector<QString>::iterator iter = iniTweaks.begin();
+ iter != iniTweaks.end(); ++iter) {
+ mergeTweak(*iter, tweakedIni);
+ }
+}
+
+
+bool Profile::invalidationActive(bool *supported) const
+{
+ if (GameInfo::instance().requiresBSAInvalidation()) {
+ *supported = true;
+ wchar_t buffer[1024];
+ std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName()));
+ // epic ms fail: GetPrivateProfileString uses errno (for whatever reason) to signal a fail since the return value
+ // has a different meaning (number of bytes copied). HOWEVER, it will not set errno to 0 if NO error occured
+ errno = 0;
+ if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(),
+ L"", buffer, 1024, iniFileName.c_str()) == 0) {
+ if (errno != 0x02) {
+ if (supported != NULL) {
+ *supported = false;
+ }
+ return false;
+ } else {
+ QString errorMessage = tr("failed to parse ini file (%1)").arg(ToQString(iniFileName));
+ throw windows_error(errorMessage.toUtf8().constData());
+ }
+ }
+ QStringList archives = ToQString(buffer).split(',');
+
+ for (int i = 0; i < archives.count(); ++i) {
+ QString bsaName = archives.at(i).trimmed();
+ if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) {
+ return true;
+ }
+ }
+ } else {
+ *supported = false;
+ }
+ return false;
+}
+
+
+void Profile::deactivateInvalidation() const
+{
+ if (GameInfo::instance().requiresBSAInvalidation()) {
+ wchar_t buffer[1024];
+ std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName()));
+ errno = 0;
+ if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(),
+ L"", buffer, 1024, iniFileName.c_str()) == 0) {
+ if (errno == 0x02) {
+ QString errorMessage = tr("failed to parse ini file (%1): %2").arg(QDir::toNativeSeparators(getIniFileName())).arg(::GetLastError());
+ throw windows_error(errorMessage.toUtf8().constData());
+ } else {
+ return;
+ }
+ }
+ QStringList archives = ToQString(buffer).split(", ");
+
+ for (int i = 0; i < archives.count();) {
+ QString bsaName = archives.at(i).trimmed();
+ if (GameInfo::instance().isInvalidationBSA(ToWString(bsaName))) {
+ archives.removeAt(i);
+ } else {
+ ++i;
+ }
+ }
+
+ // just to be safe...
+ ::SetFileAttributesW(iniFileName.c_str(), FILE_ATTRIBUTE_NORMAL);
+
+ if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(),
+ ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) ||
+ !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"0", iniFileName.c_str()) ||
+ !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"ArchiveInvalidation.txt", iniFileName.c_str())) {
+ QString errorMessage = tr("failed to modify \"%1\"").arg(ToQString(iniFileName));
+ throw windows_error(errorMessage.toUtf8().constData());
+ }
+ }
+}
+
+
+void Profile::activateInvalidation(const QString& dataDirectory) const
+{
+ if (GameInfo::instance().requiresBSAInvalidation()) {
+ wchar_t buffer[1024];
+ std::wstring iniFileName = ToWString(QDir::toNativeSeparators(getIniFileName()));
+ errno = 0;
+ if (::GetPrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(),
+ L"", buffer, 1024, iniFileName.c_str()) == 0) {
+ if (errno == 0x02) {
+ throw windows_error("failed to parse ini file");
+ } else {
+ // ignore. shouldn't have gotten here anyway
+ return;
+ }
+ }
+ QStringList archives = ToQString(buffer).split(", ");
+
+ QString invalidationBSA = ToQString(GameInfo::instance().getInvalidationBSA());
+
+ if (!archives.contains(invalidationBSA)) {
+ archives.insert(0, invalidationBSA);
+ }
+
+ // just to be safe...
+ ::SetFileAttributesW(iniFileName.c_str(), FILE_ATTRIBUTE_NORMAL);
+
+ if (!::WritePrivateProfileStringW(L"Archive", GameInfo::instance().archiveListKey().c_str(),
+ ToWString(archives.join(", ").toUtf8()).c_str(), iniFileName.c_str()) ||
+ !::WritePrivateProfileStringW(L"Archive", L"bInvalidateOlderFiles", L"1", iniFileName.c_str()) ||
+ !::WritePrivateProfileStringW(L"Archive", L"SInvalidationFile", L"", iniFileName.c_str())) {
+ QString errorMessage = tr("failed to modify \"%1\"").arg(ToQString(iniFileName));
+ throw windows_error(errorMessage.toUtf8().constData());
+ }
+
+ QString bsaFile = dataDirectory + "/" + invalidationBSA;
+ if (!QFile::exists(bsaFile)) {
+ DummyBSA bsa;
+ bsa.write(bsaFile);
+ }
+ }
+}
+
+
+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");
+ }
+ } 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;
+}
+
+
+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::getLoadOrderFileName() const
+{
+ return QDir::cleanPath(m_Directory.absoluteFilePath("loadorder.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::getDeleterFileName() const
+{
+ return QDir::cleanPath(m_Directory.absoluteFilePath("hide_plugins.txt"));
+}
+
+QString Profile::getIniFileName() const
+{
+ std::wstring primaryIniFile = *(GameInfo::instance().getIniFileNames().begin());
+ return m_Directory.absoluteFilePath(ToQString(primaryIniFile));
+}
+
+QString Profile::getProfileTweaks() const
+{
+ return QDir::cleanPath(m_Directory.absoluteFilePath(ToQString(AppConfig::profileTweakIni())));
+}
+
+QString Profile::getPath() const
+{
+ return QDir::cleanPath(m_Directory.absolutePath());
+}
+
+void Profile::rename(const QString &newName)
+{
+ QDir profileDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())));
+ profileDir.rename(getName(), newName);
+ m_Directory = profileDir.absoluteFilePath(newName);
+}
diff --git a/src/profile.h b/src/profile.h index d2f66ca0..fa34ab9e 100644 --- a/src/profile.h +++ b/src/profile.h @@ -1,330 +1,330 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef PROFILE_H -#define PROFILE_H - - -#include "modinfo.h" - -#include <QString> -#include <QDir> -#include <QMetaType> -#include <QSettings> -#include <vector> -#include <tuple> - - -/** - * @brief represents a profile - **/ -class Profile : public QObject -{ - - Q_OBJECT - -public: - - typedef boost::shared_ptr<Profile> Ptr; - -public: - - /** - * @brief default constructor - * @todo This constructor initialised nothing, the resulting object is not usable - **/ - Profile(); - - /** - * @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, 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); - - 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 createFrom(const QString &name, const Profile &reference); - - /** - * @param name of the new profile - * @param reference profile to copy from - **/ - static Profile *createPtrFrom(const QString &name, const Profile &reference); - - /** - * @brief write out the modlist.txt - **/ - void writeModlist() const; - - /** - * cancel any potential request to write modlist.txt. This is used to ensure no invalid modlist is - * saved while it's being modified - */ - void cancelWriteModlist() const; - - /** - * @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() const; - - /** - * @brief activate archive invalidation - * - * @param dataDirectory data directory of the game - * @todo passing the data directory as a parameter is useless, the function should - * be able to query it from GameInfo - **/ - void activateInvalidation(const QString &dataDirectory) const; - - /** - * @return true if this profile uses local save games - */ - bool localSavesEnabled() const; - - /** - * @brief enables or disables the use of local save games for this profile - * disabling this does not delete exising local saves but they will not be visible - * in the game - * @param enable if true, local saves are enabled, otherewise they are disabled - */ - bool enableLocalSaves(bool enable); - - /** - * @return name of the profile (this is identical to its directory name) - **/ - QString getName() const { 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 - **/ - QString getPath() const; - - 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<std::tuple<QString, QString, int> > 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 - **/ - unsigned int 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; - -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); - -public slots: - - void writeModlistNow(bool onlyOnTimer = false) const; - -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; - }; - -private: - Profile& operator=(const Profile &reference); // not implemented - - void initTimer(); - - void updateIndices(); - - void copyFilesTo(QString &target) const; - - std::vector<std::wstring> splitDZString(const wchar_t *buffer) const; - void mergeTweak(const QString &tweakName, const QString &tweakedIni) const; - void mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const; - -private: - - QDir m_Directory; - - mutable QByteArray m_LastModlistHash; - std::vector<ModStatus> m_ModStatus; - std::vector<unsigned int> m_ModIndexByPriority; - unsigned int m_NumRegularMods; - - QTimer *m_SaveTimer; -}; - -Q_DECLARE_METATYPE(Profile) - - -#endif // PROFILE_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef PROFILE_H
+#define PROFILE_H
+
+
+#include "modinfo.h"
+
+#include <QString>
+#include <QDir>
+#include <QMetaType>
+#include <QSettings>
+#include <vector>
+#include <tuple>
+
+
+/**
+ * @brief represents a profile
+ **/
+class Profile : public QObject
+{
+
+ Q_OBJECT
+
+public:
+
+ typedef boost::shared_ptr<Profile> Ptr;
+
+public:
+
+ /**
+ * @brief default constructor
+ * @todo This constructor initialised nothing, the resulting object is not usable
+ **/
+ Profile();
+
+ /**
+ * @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, 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);
+
+ 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 createFrom(const QString &name, const Profile &reference);
+
+ /**
+ * @param name of the new profile
+ * @param reference profile to copy from
+ **/
+ static Profile *createPtrFrom(const QString &name, const Profile &reference);
+
+ /**
+ * @brief write out the modlist.txt
+ **/
+ void writeModlist() const;
+
+ /**
+ * cancel any potential request to write modlist.txt. This is used to ensure no invalid modlist is
+ * saved while it's being modified
+ */
+ void cancelWriteModlist() const;
+
+ /**
+ * @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() const;
+
+ /**
+ * @brief activate archive invalidation
+ *
+ * @param dataDirectory data directory of the game
+ * @todo passing the data directory as a parameter is useless, the function should
+ * be able to query it from GameInfo
+ **/
+ void activateInvalidation(const QString &dataDirectory) const;
+
+ /**
+ * @return true if this profile uses local save games
+ */
+ bool localSavesEnabled() const;
+
+ /**
+ * @brief enables or disables the use of local save games for this profile
+ * disabling this does not delete exising local saves but they will not be visible
+ * in the game
+ * @param enable if true, local saves are enabled, otherewise they are disabled
+ */
+ bool enableLocalSaves(bool enable);
+
+ /**
+ * @return name of the profile (this is identical to its directory name)
+ **/
+ QString getName() const { 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
+ **/
+ QString getPath() const;
+
+ 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<std::tuple<QString, QString, int> > 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
+ **/
+ unsigned int 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;
+
+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);
+
+public slots:
+
+ void writeModlistNow(bool onlyOnTimer = false) const;
+
+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;
+ };
+
+private:
+ Profile& operator=(const Profile &reference); // not implemented
+
+ void initTimer();
+
+ void updateIndices();
+
+ void copyFilesTo(QString &target) const;
+
+ std::vector<std::wstring> splitDZString(const wchar_t *buffer) const;
+ void mergeTweak(const QString &tweakName, const QString &tweakedIni) const;
+ void mergeTweaks(ModInfo::Ptr modInfo, const QString &tweakedIni) const;
+
+private:
+
+ QDir m_Directory;
+
+ mutable QByteArray m_LastModlistHash;
+ std::vector<ModStatus> m_ModStatus;
+ std::vector<unsigned int> m_ModIndexByPriority;
+ unsigned int m_NumRegularMods;
+
+ QTimer *m_SaveTimer;
+};
+
+Q_DECLARE_METATYPE(Profile)
+
+
+#endif // PROFILE_H
diff --git a/src/profileinputdialog.cpp b/src/profileinputdialog.cpp index af33dcdd..5a1fd03c 100644 --- a/src/profileinputdialog.cpp +++ b/src/profileinputdialog.cpp @@ -1,48 +1,48 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "profileinputdialog.h" -#include "ui_profileinputdialog.h" -#include <utility.h> - -ProfileInputDialog::ProfileInputDialog(QWidget *parent) : - QDialog(parent), - ui(new Ui::ProfileInputDialog) -{ - ui->setupUi(this); -} - -ProfileInputDialog::~ProfileInputDialog() -{ - delete ui; -} - -QString ProfileInputDialog::getName() const -{ - QString result = ui->nameEdit->text(); - MOBase::fixDirectoryName(result); - return result; -} - - -bool ProfileInputDialog::getPreferDefaultSettings() const -{ - return ui->defaultSettingsBox->checkState() == Qt::Checked; -} - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "profileinputdialog.h"
+#include "ui_profileinputdialog.h"
+#include <utility.h>
+
+ProfileInputDialog::ProfileInputDialog(QWidget *parent) :
+ QDialog(parent),
+ ui(new Ui::ProfileInputDialog)
+{
+ ui->setupUi(this);
+}
+
+ProfileInputDialog::~ProfileInputDialog()
+{
+ delete ui;
+}
+
+QString ProfileInputDialog::getName() const
+{
+ QString result = ui->nameEdit->text();
+ MOBase::fixDirectoryName(result);
+ return result;
+}
+
+
+bool ProfileInputDialog::getPreferDefaultSettings() const
+{
+ return ui->defaultSettingsBox->checkState() == Qt::Checked;
+}
+
diff --git a/src/profileinputdialog.h b/src/profileinputdialog.h index 5ee1d600..8a132666 100644 --- a/src/profileinputdialog.h +++ b/src/profileinputdialog.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#ifndef PROFILEINPUTDIALOG_H
#define PROFILEINPUTDIALOG_H
diff --git a/src/profilesdialog.cpp b/src/profilesdialog.cpp index 69872a24..3fdad880 100644 --- a/src/profilesdialog.cpp +++ b/src/profilesdialog.cpp @@ -1,320 +1,320 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "profilesdialog.h" -#include "ui_profilesdialog.h" -#include "profile.h" -#include "report.h" -#include "utility.h" -#include "transfersavesdialog.h" -#include "profileinputdialog.h" -#include "mainwindow.h" -#include <gameinfo.h> -#include <QListWidgetItem> -#include <QInputDialog> -#include <QLineEdit> -#include <QDirIterator> -#include <QMessageBox> -#include <QWhatsThis> - - -using namespace MOBase; -using namespace MOShared; - -Q_DECLARE_METATYPE(Profile::Ptr) - - -ProfilesDialog::ProfilesDialog(const QString &gamePath, QWidget *parent) - : TutorableDialog("Profiles", parent), ui(new Ui::ProfilesDialog), m_GamePath(gamePath), m_FailState(false) -{ - ui->setupUi(this); - - QDir profilesDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir()))); - profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot); - m_ProfilesList = findChild<QListWidget*>("profilesList"); - - QDirIterator profileIter(profilesDir); - - while (profileIter.hasNext()) { - profileIter.next(); - addItem(profileIter.filePath()); - } - - QCheckBox *invalidationBox = findChild<QCheckBox*>("invalidationBox"); - if (!GameInfo::instance().requiresBSAInvalidation()) { - invalidationBox->setToolTip(tr("Archive invalidation isn't required for this game.")); - invalidationBox->setEnabled(false); - } -} - -ProfilesDialog::~ProfilesDialog() -{ - delete ui; -} - -void ProfilesDialog::showEvent(QShowEvent *event) -{ - TutorableDialog::showEvent(event); - - if (m_ProfilesList->count() == 0) { - QPoint pos = m_ProfilesList->mapToGlobal(QPoint(0, 0)); - pos.rx() += m_ProfilesList->width() / 2; - pos.ry() += (m_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!"), m_ProfilesList); - } -} - -void ProfilesDialog::on_closeButton_clicked() -{ - close(); -} - - -void ProfilesDialog::addItem(const QString &name) -{ - QDir profileDir(name); - QListWidgetItem *newItem = new QListWidgetItem(profileDir.dirName(), m_ProfilesList); - try { - newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(profileDir)))); - m_FailState = false; - } catch (const std::exception& e) { - reportError(tr("failed to create profile: %1").arg(e.what())); - } -} - - -void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings) -{ - try { - QListWidget *profilesList = findChild<QListWidget*>("profilesList"); - QListWidgetItem *newItem = new QListWidgetItem(name, profilesList); - newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(name, useDefaultSettings)))); - profilesList->addItem(newItem); - m_FailState = false; - } catch (const std::exception&) { - m_FailState = true; - throw; - } -} - - -void ProfilesDialog::createProfile(const QString &name, const Profile &reference) -{ - try { - QListWidget *profilesList = findChild<QListWidget*>("profilesList"); - QListWidgetItem *newItem = new QListWidgetItem(name, profilesList); - newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(Profile::createPtrFrom(name, reference)))); - 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_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) { - QListWidget *profilesList = findChild<QListWidget*>("profilesList"); - - try { - const Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>(); - 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) { - QListWidget *profilesList = findChild<QListWidget*>("profilesList"); - - Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>(); - QString profilePath; - if (currentProfile.get() == NULL) { - profilePath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())) + "/" + profilesList->currentItem()->text(); - if (QMessageBox::question(this, tr("Profile broken"), - tr("This profile you're about to delete seems to be broken or the path is invalid. " - "I'm about to delete the following folder: \"%1\". Proceed?").arg(profilePath), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) { - return; - } - } else { - // on destruction, the profile object would write the profile.ini file again, so - // we have to get rid of the it before deleting the directory - profilePath = currentProfile->getPath(); - } - QListWidgetItem* item = profilesList->takeItem(profilesList->currentRow()); - if (item != NULL) { - delete item; - } - 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<Profile::Ptr>(); - - bool valid = false; - QString name; - - while (!valid) { - bool ok = false; - name = QInputDialog::getText(this, tr("Rename Profile"), tr("New Name"), - QLineEdit::Normal, currentProfile->getName(), - &ok); - valid = fixDirectoryName(name); - if (!ok) { - return; - } - } - - ui->profilesList->currentItem()->setText(name); - currentProfile->rename(name); -} - - -void ProfilesDialog::on_invalidationBox_stateChanged(int state) -{ - QListWidget *profilesList = findChild<QListWidget*>("profilesList"); - - QListWidgetItem *currentItem = profilesList->currentItem(); - if (currentItem == NULL) { - return; - } - if (!ui->invalidationBox->isEnabled()) { - return; - } - try { - QVariant currentProfileVariant = currentItem->data(Qt::UserRole); - if (!currentProfileVariant.isValid() || currentProfileVariant.isNull()) { - return; - } - const Profile::Ptr currentProfile = currentItem->data(Qt::UserRole).value<Profile::Ptr>(); - if (state == Qt::Unchecked) { - currentProfile->deactivateInvalidation(); - } else { - currentProfile->activateInvalidation(m_GamePath + "/data"); - } - } 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*) -{ - QCheckBox *invalidationBox = findChild<QCheckBox*>("invalidationBox"); - QCheckBox *localSavesBox = findChild<QCheckBox*>("localSavesBox"); - QPushButton *copyButton = findChild<QPushButton*>("copyProfileButton"); - QPushButton *removeButton = findChild<QPushButton*>("removeProfileButton"); - QPushButton *transferButton = findChild<QPushButton*>("transferButton"); - QPushButton *renameButton = findChild<QPushButton*>("renameButton"); - - if (current != NULL) { - if (!current->data(Qt::UserRole).isValid()) return; - const Profile::Ptr currentProfile = current->data(Qt::UserRole).value<Profile::Ptr>(); - - try { - bool invalidationSupported = false; - invalidationBox->blockSignals(true); - invalidationBox->setChecked(currentProfile->invalidationActive(&invalidationSupported)); - invalidationBox->setEnabled(invalidationSupported); - invalidationBox->blockSignals(false); - - bool localSaves = currentProfile->localSavesEnabled(); - 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 - localSavesBox->blockSignals(true); - localSavesBox->setChecked(localSaves); - localSavesBox->blockSignals(false); - - copyButton->setEnabled(true); - removeButton->setEnabled(true); - renameButton->setEnabled(true); - } catch (const std::exception& E) { - reportError(tr("failed to determine if invalidation is active: %1").arg(E.what())); - copyButton->setEnabled(false); - removeButton->setEnabled(false); - renameButton->setEnabled(false); - invalidationBox->setChecked(false); - } - } else { - invalidationBox->setChecked(false); - copyButton->setEnabled(false); - removeButton->setEnabled(false); - renameButton->setEnabled(false); - } -} - -void ProfilesDialog::on_localSavesBox_stateChanged(int state) -{ - Profile::Ptr currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>(); - - 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 = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>(); - TransferSavesDialog transferDialog(*currentProfile, this); - transferDialog.exec(); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "profilesdialog.h"
+#include "ui_profilesdialog.h"
+#include "profile.h"
+#include "report.h"
+#include "utility.h"
+#include "transfersavesdialog.h"
+#include "profileinputdialog.h"
+#include "mainwindow.h"
+#include <gameinfo.h>
+#include <QListWidgetItem>
+#include <QInputDialog>
+#include <QLineEdit>
+#include <QDirIterator>
+#include <QMessageBox>
+#include <QWhatsThis>
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+Q_DECLARE_METATYPE(Profile::Ptr)
+
+
+ProfilesDialog::ProfilesDialog(const QString &gamePath, QWidget *parent)
+ : TutorableDialog("Profiles", parent), ui(new Ui::ProfilesDialog), m_GamePath(gamePath), m_FailState(false)
+{
+ ui->setupUi(this);
+
+ QDir profilesDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())));
+ profilesDir.setFilter(QDir::AllDirs | QDir::NoDotAndDotDot);
+ m_ProfilesList = findChild<QListWidget*>("profilesList");
+
+ QDirIterator profileIter(profilesDir);
+
+ while (profileIter.hasNext()) {
+ profileIter.next();
+ addItem(profileIter.filePath());
+ }
+
+ QCheckBox *invalidationBox = findChild<QCheckBox*>("invalidationBox");
+ if (!GameInfo::instance().requiresBSAInvalidation()) {
+ invalidationBox->setToolTip(tr("Archive invalidation isn't required for this game."));
+ invalidationBox->setEnabled(false);
+ }
+}
+
+ProfilesDialog::~ProfilesDialog()
+{
+ delete ui;
+}
+
+void ProfilesDialog::showEvent(QShowEvent *event)
+{
+ TutorableDialog::showEvent(event);
+
+ if (m_ProfilesList->count() == 0) {
+ QPoint pos = m_ProfilesList->mapToGlobal(QPoint(0, 0));
+ pos.rx() += m_ProfilesList->width() / 2;
+ pos.ry() += (m_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!"), m_ProfilesList);
+ }
+}
+
+void ProfilesDialog::on_closeButton_clicked()
+{
+ close();
+}
+
+
+void ProfilesDialog::addItem(const QString &name)
+{
+ QDir profileDir(name);
+ QListWidgetItem *newItem = new QListWidgetItem(profileDir.dirName(), m_ProfilesList);
+ try {
+ newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(profileDir))));
+ m_FailState = false;
+ } catch (const std::exception& e) {
+ reportError(tr("failed to create profile: %1").arg(e.what()));
+ }
+}
+
+
+void ProfilesDialog::createProfile(const QString &name, bool useDefaultSettings)
+{
+ try {
+ QListWidget *profilesList = findChild<QListWidget*>("profilesList");
+ QListWidgetItem *newItem = new QListWidgetItem(name, profilesList);
+ newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(new Profile(name, useDefaultSettings))));
+ profilesList->addItem(newItem);
+ m_FailState = false;
+ } catch (const std::exception&) {
+ m_FailState = true;
+ throw;
+ }
+}
+
+
+void ProfilesDialog::createProfile(const QString &name, const Profile &reference)
+{
+ try {
+ QListWidget *profilesList = findChild<QListWidget*>("profilesList");
+ QListWidgetItem *newItem = new QListWidgetItem(name, profilesList);
+ newItem->setData(Qt::UserRole, QVariant::fromValue(Profile::Ptr(Profile::createPtrFrom(name, reference))));
+ 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_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) {
+ QListWidget *profilesList = findChild<QListWidget*>("profilesList");
+
+ try {
+ const Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
+ 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) {
+ QListWidget *profilesList = findChild<QListWidget*>("profilesList");
+
+ Profile::Ptr currentProfile = profilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
+ QString profilePath;
+ if (currentProfile.get() == NULL) {
+ profilePath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getProfilesDir())) + "/" + profilesList->currentItem()->text();
+ if (QMessageBox::question(this, tr("Profile broken"),
+ tr("This profile you're about to delete seems to be broken or the path is invalid. "
+ "I'm about to delete the following folder: \"%1\". Proceed?").arg(profilePath), QMessageBox::Yes | QMessageBox::No) == QMessageBox::No) {
+ return;
+ }
+ } else {
+ // on destruction, the profile object would write the profile.ini file again, so
+ // we have to get rid of the it before deleting the directory
+ profilePath = currentProfile->getPath();
+ }
+ QListWidgetItem* item = profilesList->takeItem(profilesList->currentRow());
+ if (item != NULL) {
+ delete item;
+ }
+ 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<Profile::Ptr>();
+
+ bool valid = false;
+ QString name;
+
+ while (!valid) {
+ bool ok = false;
+ name = QInputDialog::getText(this, tr("Rename Profile"), tr("New Name"),
+ QLineEdit::Normal, currentProfile->getName(),
+ &ok);
+ valid = fixDirectoryName(name);
+ if (!ok) {
+ return;
+ }
+ }
+
+ ui->profilesList->currentItem()->setText(name);
+ currentProfile->rename(name);
+}
+
+
+void ProfilesDialog::on_invalidationBox_stateChanged(int state)
+{
+ QListWidget *profilesList = findChild<QListWidget*>("profilesList");
+
+ QListWidgetItem *currentItem = profilesList->currentItem();
+ if (currentItem == NULL) {
+ return;
+ }
+ if (!ui->invalidationBox->isEnabled()) {
+ return;
+ }
+ try {
+ QVariant currentProfileVariant = currentItem->data(Qt::UserRole);
+ if (!currentProfileVariant.isValid() || currentProfileVariant.isNull()) {
+ return;
+ }
+ const Profile::Ptr currentProfile = currentItem->data(Qt::UserRole).value<Profile::Ptr>();
+ if (state == Qt::Unchecked) {
+ currentProfile->deactivateInvalidation();
+ } else {
+ currentProfile->activateInvalidation(m_GamePath + "/data");
+ }
+ } 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*)
+{
+ QCheckBox *invalidationBox = findChild<QCheckBox*>("invalidationBox");
+ QCheckBox *localSavesBox = findChild<QCheckBox*>("localSavesBox");
+ QPushButton *copyButton = findChild<QPushButton*>("copyProfileButton");
+ QPushButton *removeButton = findChild<QPushButton*>("removeProfileButton");
+ QPushButton *transferButton = findChild<QPushButton*>("transferButton");
+ QPushButton *renameButton = findChild<QPushButton*>("renameButton");
+
+ if (current != NULL) {
+ if (!current->data(Qt::UserRole).isValid()) return;
+ const Profile::Ptr currentProfile = current->data(Qt::UserRole).value<Profile::Ptr>();
+
+ try {
+ bool invalidationSupported = false;
+ invalidationBox->blockSignals(true);
+ invalidationBox->setChecked(currentProfile->invalidationActive(&invalidationSupported));
+ invalidationBox->setEnabled(invalidationSupported);
+ invalidationBox->blockSignals(false);
+
+ bool localSaves = currentProfile->localSavesEnabled();
+ 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
+ localSavesBox->blockSignals(true);
+ localSavesBox->setChecked(localSaves);
+ localSavesBox->blockSignals(false);
+
+ copyButton->setEnabled(true);
+ removeButton->setEnabled(true);
+ renameButton->setEnabled(true);
+ } catch (const std::exception& E) {
+ reportError(tr("failed to determine if invalidation is active: %1").arg(E.what()));
+ copyButton->setEnabled(false);
+ removeButton->setEnabled(false);
+ renameButton->setEnabled(false);
+ invalidationBox->setChecked(false);
+ }
+ } else {
+ invalidationBox->setChecked(false);
+ copyButton->setEnabled(false);
+ removeButton->setEnabled(false);
+ renameButton->setEnabled(false);
+ }
+}
+
+void ProfilesDialog::on_localSavesBox_stateChanged(int state)
+{
+ Profile::Ptr currentProfile = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
+
+ 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 = m_ProfilesList->currentItem()->data(Qt::UserRole).value<Profile::Ptr>();
+ TransferSavesDialog transferDialog(*currentProfile, this);
+ transferDialog.exec();
+}
diff --git a/src/profilesdialog.h b/src/profilesdialog.h index f864a7e0..905d49b1 100644 --- a/src/profilesdialog.h +++ b/src/profilesdialog.h @@ -1,96 +1,96 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef PROFILESDIALOG_H -#define PROFILESDIALOG_H - -#include "tutorabledialog.h" -#include <QListWidgetItem> -#include "profile.h" - - -namespace Ui { - class ProfilesDialog; -} - - -/** - * @brief Dialog that can be used to create/delete/modify profiles - **/ -class ProfilesDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - - /** - * @brief constructor - * - * @param gamePath the path to the game directory - * @param parent parent widget - * @todo the game path could be retrieved from GameInfo just as easily - **/ - explicit ProfilesDialog(const QString &gamePath, 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); - -private: - - void 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_addProfileButton_clicked(); - - void on_invalidationBox_stateChanged(int arg1); - - void on_copyProfileButton_clicked(); - - void on_profilesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - - void on_removeProfileButton_clicked(); - - void on_localSavesBox_stateChanged(int arg1); - - void on_transferButton_clicked(); - - void on_renameButton_clicked(); - -private: - Ui::ProfilesDialog *ui; - QString m_GamePath; - QListWidget *m_ProfilesList; - bool m_FailState; - -}; - -#endif // PROFILESDIALOG_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef PROFILESDIALOG_H
+#define PROFILESDIALOG_H
+
+#include "tutorabledialog.h"
+#include <QListWidgetItem>
+#include "profile.h"
+
+
+namespace Ui {
+ class ProfilesDialog;
+}
+
+
+/**
+ * @brief Dialog that can be used to create/delete/modify profiles
+ **/
+class ProfilesDialog : public MOBase::TutorableDialog
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * @brief constructor
+ *
+ * @param gamePath the path to the game directory
+ * @param parent parent widget
+ * @todo the game path could be retrieved from GameInfo just as easily
+ **/
+ explicit ProfilesDialog(const QString &gamePath, 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);
+
+private:
+
+ void 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_addProfileButton_clicked();
+
+ void on_invalidationBox_stateChanged(int arg1);
+
+ void on_copyProfileButton_clicked();
+
+ void on_profilesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
+
+ void on_removeProfileButton_clicked();
+
+ void on_localSavesBox_stateChanged(int arg1);
+
+ void on_transferButton_clicked();
+
+ void on_renameButton_clicked();
+
+private:
+ Ui::ProfilesDialog *ui;
+ QString m_GamePath;
+ QListWidget *m_ProfilesList;
+ bool m_FailState;
+
+};
+
+#endif // PROFILESDIALOG_H
diff --git a/src/queryoverwritedialog.cpp b/src/queryoverwritedialog.cpp index df604f5e..1d7aea0a 100644 --- a/src/queryoverwritedialog.cpp +++ b/src/queryoverwritedialog.cpp @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#include "queryoverwritedialog.h"
#include "ui_queryoverwritedialog.h"
diff --git a/src/queryoverwritedialog.h b/src/queryoverwritedialog.h index e6821c38..1a22a60e 100644 --- a/src/queryoverwritedialog.h +++ b/src/queryoverwritedialog.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#ifndef QUERYOVERWRITEDIALOG_H
#define QUERYOVERWRITEDIALOG_H
diff --git a/src/report.cpp b/src/report.cpp index ca7e1dc9..5cf36078 100644 --- a/src/report.cpp +++ b/src/report.cpp @@ -1,53 +1,53 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "report.h" -#include "utility.h" -#include <QMessageBox> -#include <QApplication> -#include <Windows.h> - - -using namespace MOBase; - - -void reportError(QString message) -{ - if (QApplication::topLevelWidgets().count() != 0) { - QMessageBox messageBox(QMessageBox::Warning, QObject::tr("Error"), message, QMessageBox::Ok); - messageBox.exec(); - } else { - ::MessageBoxW(NULL, ToWString(message).c_str(), ToWString(QObject::tr("Error")).c_str(), MB_ICONERROR | MB_OK); - } -} - - -std::tstring toTString(const QString& source) -{ -#ifdef UNICODE - wchar_t* temp = new wchar_t[source.size() + 1]; - source.toWCharArray(temp); - temp[source.size()] = '\0'; - std::tstring result(temp); - delete[] temp; - return result; -#else // UNICODE - return source.toAscii(); -#endif // UNICODE -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "report.h"
+#include "utility.h"
+#include <QMessageBox>
+#include <QApplication>
+#include <Windows.h>
+
+
+using namespace MOBase;
+
+
+void reportError(QString message)
+{
+ if (QApplication::topLevelWidgets().count() != 0) {
+ QMessageBox messageBox(QMessageBox::Warning, QObject::tr("Error"), message, QMessageBox::Ok);
+ messageBox.exec();
+ } else {
+ ::MessageBoxW(NULL, ToWString(message).c_str(), ToWString(QObject::tr("Error")).c_str(), MB_ICONERROR | MB_OK);
+ }
+}
+
+
+std::tstring toTString(const QString& source)
+{
+#ifdef UNICODE
+ wchar_t* temp = new wchar_t[source.size() + 1];
+ source.toWCharArray(temp);
+ temp[source.size()] = '\0';
+ std::tstring result(temp);
+ delete[] temp;
+ return result;
+#else // UNICODE
+ return source.toAscii();
+#endif // UNICODE
+}
diff --git a/src/report.h b/src/report.h index 97b8b607..b319f9a0 100644 --- a/src/report.h +++ b/src/report.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#ifndef REPORT_H
#define REPORT_H
diff --git a/src/savegame.cpp b/src/savegame.cpp index 06e028e2..1cdabb2d 100644 --- a/src/savegame.cpp +++ b/src/savegame.cpp @@ -1,106 +1,106 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "savegame.h" -#include <QFile> -#include <QBuffer> -#include <set> -#include <QFileInfo> -#include <QDateTime> -#include <utility.h> -#include <limits> -#include "gameinfo.h" - - -SaveGame::SaveGame(QObject *parent) - : QObject(parent), m_FileName(), m_PCName(), m_PCLevel(0), m_PCLocation(), m_SaveNumber(0), m_Screenshot() -{ -} - - -SaveGame::SaveGame(QObject *parent, const QString &filename) - : QObject(parent), m_FileName(filename), m_PCName(), m_PCLevel(0), m_PCLocation(), m_SaveNumber(0), m_Screenshot() -{ -} - - -SaveGame::SaveGame(const SaveGame& reference) - : m_FileName(reference.m_FileName), m_PCName(reference.m_PCName), m_PCLevel(reference.m_PCLevel), - m_PCLocation(reference.m_PCLocation), m_SaveNumber(reference.m_SaveNumber), - m_Screenshot(reference.m_Screenshot) -{ -} - - -SaveGame& SaveGame::operator=(const SaveGame &reference) -{ - if (&reference != this) { - m_FileName = reference.m_FileName; - m_PCName = reference.m_PCName; - m_PCLevel = reference.m_PCLevel; - m_PCLocation = reference.m_PCLocation; - m_SaveNumber = reference.m_SaveNumber; - m_Screenshot = reference.m_Screenshot; - } - return *this; -} - - -SaveGame::~SaveGame() -{ -} - -QStringList SaveGame::attachedFiles() const -{ - QStringList result; - foreach (const std::wstring &ext, MOShared::GameInfo::instance().getSavegameAttachmentExtensions()) { - QFileInfo fi(fileName()); - fi.setFile(fi.canonicalPath() + "/" + fi.completeBaseName() + "." + MOBase::ToQString(ext)); - if (fi.exists()) { - result.append(fi.filePath()); - } - } - - return result; -} - -QStringList SaveGame::saveFiles() const -{ - QStringList result = attachedFiles(); - result.append(fileName()); - return result; -} - - -void SaveGame::setCreationTime(const QString &fileName) -{ - QFileInfo creationTime(fileName); - QDateTime modified = creationTime.lastModified(); - memset(&m_CreationTime, 0, sizeof(SYSTEMTIME)); - - m_CreationTime.wDay = static_cast<WORD>(modified.date().day()); - m_CreationTime.wDayOfWeek = static_cast<WORD>(modified.date().dayOfWeek()); - m_CreationTime.wMonth = static_cast<WORD>(modified.date().month()); - m_CreationTime.wYear =static_cast<WORD>( modified.date().year()); - - m_CreationTime.wHour = static_cast<WORD>(modified.time().hour()); - m_CreationTime.wMinute = static_cast<WORD>(modified.time().minute()); - m_CreationTime.wSecond = static_cast<WORD>(modified.time().second()); - m_CreationTime.wMilliseconds = static_cast<WORD>(modified.time().msec()); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "savegame.h"
+#include <QFile>
+#include <QBuffer>
+#include <set>
+#include <QFileInfo>
+#include <QDateTime>
+#include <utility.h>
+#include <limits>
+#include "gameinfo.h"
+
+
+SaveGame::SaveGame(QObject *parent)
+ : QObject(parent), m_FileName(), m_PCName(), m_PCLevel(0), m_PCLocation(), m_SaveNumber(0), m_Screenshot()
+{
+}
+
+
+SaveGame::SaveGame(QObject *parent, const QString &filename)
+ : QObject(parent), m_FileName(filename), m_PCName(), m_PCLevel(0), m_PCLocation(), m_SaveNumber(0), m_Screenshot()
+{
+}
+
+
+SaveGame::SaveGame(const SaveGame& reference)
+ : m_FileName(reference.m_FileName), m_PCName(reference.m_PCName), m_PCLevel(reference.m_PCLevel),
+ m_PCLocation(reference.m_PCLocation), m_SaveNumber(reference.m_SaveNumber),
+ m_Screenshot(reference.m_Screenshot)
+{
+}
+
+
+SaveGame& SaveGame::operator=(const SaveGame &reference)
+{
+ if (&reference != this) {
+ m_FileName = reference.m_FileName;
+ m_PCName = reference.m_PCName;
+ m_PCLevel = reference.m_PCLevel;
+ m_PCLocation = reference.m_PCLocation;
+ m_SaveNumber = reference.m_SaveNumber;
+ m_Screenshot = reference.m_Screenshot;
+ }
+ return *this;
+}
+
+
+SaveGame::~SaveGame()
+{
+}
+
+QStringList SaveGame::attachedFiles() const
+{
+ QStringList result;
+ foreach (const std::wstring &ext, MOShared::GameInfo::instance().getSavegameAttachmentExtensions()) {
+ QFileInfo fi(fileName());
+ fi.setFile(fi.canonicalPath() + "/" + fi.completeBaseName() + "." + MOBase::ToQString(ext));
+ if (fi.exists()) {
+ result.append(fi.filePath());
+ }
+ }
+
+ return result;
+}
+
+QStringList SaveGame::saveFiles() const
+{
+ QStringList result = attachedFiles();
+ result.append(fileName());
+ return result;
+}
+
+
+void SaveGame::setCreationTime(const QString &fileName)
+{
+ QFileInfo creationTime(fileName);
+ QDateTime modified = creationTime.lastModified();
+ memset(&m_CreationTime, 0, sizeof(SYSTEMTIME));
+
+ m_CreationTime.wDay = static_cast<WORD>(modified.date().day());
+ m_CreationTime.wDayOfWeek = static_cast<WORD>(modified.date().dayOfWeek());
+ m_CreationTime.wMonth = static_cast<WORD>(modified.date().month());
+ m_CreationTime.wYear =static_cast<WORD>( modified.date().year());
+
+ m_CreationTime.wHour = static_cast<WORD>(modified.time().hour());
+ m_CreationTime.wMinute = static_cast<WORD>(modified.time().minute());
+ m_CreationTime.wSecond = static_cast<WORD>(modified.time().second());
+ m_CreationTime.wMilliseconds = static_cast<WORD>(modified.time().msec());
+}
diff --git a/src/savegame.h b/src/savegame.h index 9a5f7036..1fd2f7ab 100644 --- a/src/savegame.h +++ b/src/savegame.h @@ -1,131 +1,131 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef SAVEGAME_H -#define SAVEGAME_H - - -#include <QString> -#include <QObject> -#include <QImage> -#include <QMetaType> -#include <QFile> -#include <QStringList> - -#define WIN32_LEAN_AND_MEAN -#include <Windows.h> - - -/** - * @brief represents a single save game - **/ -class SaveGame : public QObject { - -Q_OBJECT - -public: - - /** - * @brief construct an empty object - **/ - SaveGame(QObject *parent = 0); - - /** - * @brief construct a save game and immediately read out information from the file - * - * @param filename absolute path of the save game file - **/ - SaveGame(QObject *parent, const QString &filename); - - SaveGame(const SaveGame& reference); - - SaveGame& operator=(const SaveGame &reference); - - ~SaveGame(); - - /** - * @brief read out information from a savegame - * - * @param fileName absolute path of the save game file - **/ - virtual void readFile(const QString) { } - - /** - * @return filename of this savegame - */ - const QString &fileName() const { return m_FileName; } - - /** - * @return a list of additional files that belong to this savegame - */ - virtual QStringList attachedFiles() const; - - /** - * @return a list of all files that belong to this savegame - */ - virtual QStringList saveFiles() const; - - /** - * @return name of the player character - **/ - const QString &pcName() const { return m_PCName; } - - /** - * @return level of the player character - **/ - unsigned short pcLevel() const { return m_PCLevel; } - - /** - * @return location of the player character - **/ - const QString &pcLocation() const { return m_PCLocation; } - - /** - * @return index of the save game - **/ - unsigned long saveNumber() const { return m_SaveNumber; } - - /** - * @return creation time of the save game - **/ - SYSTEMTIME creationTime() const { return m_CreationTime; } - - /** - * @return screenshot in the savegame - **/ - const QImage &screenshot() const { return m_Screenshot; } - -private: - - void setCreationTime(const QString &fileName); - -protected: - - QString m_FileName; - QString m_PCName; - unsigned short m_PCLevel; - QString m_PCLocation; - unsigned long m_SaveNumber; - SYSTEMTIME m_CreationTime; - QImage m_Screenshot; - -}; - - -#endif // SAVEGAME_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef SAVEGAME_H
+#define SAVEGAME_H
+
+
+#include <QString>
+#include <QObject>
+#include <QImage>
+#include <QMetaType>
+#include <QFile>
+#include <QStringList>
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+
+/**
+ * @brief represents a single save game
+ **/
+class SaveGame : public QObject {
+
+Q_OBJECT
+
+public:
+
+ /**
+ * @brief construct an empty object
+ **/
+ SaveGame(QObject *parent = 0);
+
+ /**
+ * @brief construct a save game and immediately read out information from the file
+ *
+ * @param filename absolute path of the save game file
+ **/
+ SaveGame(QObject *parent, const QString &filename);
+
+ SaveGame(const SaveGame& reference);
+
+ SaveGame& operator=(const SaveGame &reference);
+
+ ~SaveGame();
+
+ /**
+ * @brief read out information from a savegame
+ *
+ * @param fileName absolute path of the save game file
+ **/
+ virtual void readFile(const QString) { }
+
+ /**
+ * @return filename of this savegame
+ */
+ const QString &fileName() const { return m_FileName; }
+
+ /**
+ * @return a list of additional files that belong to this savegame
+ */
+ virtual QStringList attachedFiles() const;
+
+ /**
+ * @return a list of all files that belong to this savegame
+ */
+ virtual QStringList saveFiles() const;
+
+ /**
+ * @return name of the player character
+ **/
+ const QString &pcName() const { return m_PCName; }
+
+ /**
+ * @return level of the player character
+ **/
+ unsigned short pcLevel() const { return m_PCLevel; }
+
+ /**
+ * @return location of the player character
+ **/
+ const QString &pcLocation() const { return m_PCLocation; }
+
+ /**
+ * @return index of the save game
+ **/
+ unsigned long saveNumber() const { return m_SaveNumber; }
+
+ /**
+ * @return creation time of the save game
+ **/
+ SYSTEMTIME creationTime() const { return m_CreationTime; }
+
+ /**
+ * @return screenshot in the savegame
+ **/
+ const QImage &screenshot() const { return m_Screenshot; }
+
+private:
+
+ void setCreationTime(const QString &fileName);
+
+protected:
+
+ QString m_FileName;
+ QString m_PCName;
+ unsigned short m_PCLevel;
+ QString m_PCLocation;
+ unsigned long m_SaveNumber;
+ SYSTEMTIME m_CreationTime;
+ QImage m_Screenshot;
+
+};
+
+
+#endif // SAVEGAME_H
diff --git a/src/savegamegamebryo.cpp b/src/savegamegamebryo.cpp index bf113254..f593ef5a 100644 --- a/src/savegamegamebryo.cpp +++ b/src/savegamegamebryo.cpp @@ -1,338 +1,338 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "savegamegamebyro.h" -#include <QFile> -#include <QBuffer> -#include <set> -#include "gameinfo.h" -#include <QFileInfo> -#include <QDateTime> -#include <limits> - - -using namespace MOShared; - - -template <typename T> -void FileRead(QFile &file, T &value) -{ - int read = file.read(reinterpret_cast<char*>(&value), sizeof(T)); - if (read != sizeof(T)) { - throw std::runtime_error("unexpected end of file"); - } -} - - -template <typename T> -void FileSkip(QFile &file, int count = 1) -{ - char ignore[sizeof(T)]; - for (int i = 0; i < count; ++i) { - if (file.read(ignore, sizeof(T)) != sizeof(T)) { - throw std::runtime_error("unexpected end of file"); - } - } -} - - -QString ReadBString(QFile &file) -{ - char buffer[256]; - file.read(buffer, 1); // size including zero termination - unsigned char size = buffer[0]; - file.read(buffer, size); - return QString::fromLatin1(buffer, size); -} - - -QString ReadFOSString(QFile &file, bool delimiter) -{ - union { - char lengthBuffer[2]; - unsigned short length; - }; - - file.read(lengthBuffer, 2); - if (delimiter) { - FileSkip<char>(file); // 0x7c - } - char *buffer = new char[length]; - file.read(buffer, length); - - QString result = QString::fromLatin1(buffer, length); - delete [] buffer; - - return result; -} - - -SaveGameGamebryo::SaveGameGamebryo(QObject *parent) - : SaveGame(parent), m_Plugins() -{ -} - - -SaveGameGamebryo::SaveGameGamebryo(QObject *parent, const QString &fileName) - : SaveGame(parent, fileName), m_Plugins() -{ - readFile(fileName); -} - - -SaveGameGamebryo::SaveGameGamebryo(const SaveGameGamebryo& reference) - : SaveGame(reference), m_Plugins(reference.m_Plugins) -{ -} - - -SaveGameGamebryo& SaveGameGamebryo::operator=(const SaveGameGamebryo &reference) -{ - if (&reference != this) { - SaveGame::operator =(reference); - m_Plugins = reference.m_Plugins; - } - return *this; -} - - -SaveGameGamebryo::~SaveGameGamebryo() -{ -} - - - - - -void SaveGameGamebryo::readSkyrimFile(QFile &saveFile) -{ - char fileID[14]; - - saveFile.read(fileID, 13); - fileID[13] = '\0'; - if (strncmp(fileID, "TESV_SAVEGAME", 13) != 0) { - throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData()); - } - - FileSkip<unsigned long>(saveFile); // header size - FileSkip<unsigned long>(saveFile); // header version, -> 8 - FileRead(saveFile, m_SaveNumber); - - m_PCName = ReadFOSString(saveFile, false); - - unsigned long temp; - FileRead(saveFile, temp); // player level - m_PCLevel = static_cast<unsigned short>(temp); - - m_PCLocation = ReadFOSString(saveFile, false); - ReadFOSString(saveFile, false); // playtime as ascii hhh.mm.ss - ReadFOSString(saveFile, false); // race name (i.e. BretonRace) - - - FileSkip<unsigned short>(saveFile); // ??? - FileSkip<float>(saveFile, 2); // ??? - FileSkip<unsigned char>(saveFile, 8); // filetime - -// FileSkip<unsigned char>(saveFile, 18); // ??? 18 bytes of data. not completely random, maybe a time stamp? maybe - - unsigned long width, height; - FileRead(saveFile, width); // 320 - FileRead(saveFile, height); // 192 - - QScopedArrayPointer<unsigned char> buffer(new unsigned char[width * height * 3]); - saveFile.read(reinterpret_cast<char*>(buffer.data()), width * height * 3); - // why do I have to copy here? without the copy, the buffer seems to get deleted after the - // temporary vanishes, but Qts implicit sharing should handle that? - m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).copy(); - - FileSkip<unsigned char>(saveFile); // form version - FileSkip<unsigned long>(saveFile); // plugin info size - - unsigned char pluginCount; - FileRead(saveFile, pluginCount); - - for (int i = 0; i < pluginCount; ++i) { - m_Plugins.push_back(ReadFOSString(saveFile, false)); - } -} - - -void SaveGameGamebryo::readESSFile(QFile &saveFile) -{ - char fileID[13]; - unsigned char versionMinor; - unsigned long headerVersion, saveHeaderSize; -// *** format is different for fallout! - saveFile.read(fileID, 12); - fileID[12] = '\0'; - FileSkip<unsigned char>(saveFile); FileRead(saveFile, versionMinor); - FileSkip<SYSTEMTIME>(saveFile); // modified time - FileRead(saveFile, headerVersion); FileRead(saveFile, saveHeaderSize); - - if (strncmp(fileID, "TES4SAVEGAME", 12) != 0) { - throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData()); - } - - FileRead(saveFile, m_SaveNumber); - - m_PCName = ReadBString(saveFile); - - FileRead(saveFile, m_PCLevel); - m_PCLocation = ReadBString(saveFile); - FileSkip<float>(saveFile); // game days - FileSkip<unsigned long>(saveFile); // game ticks - FileRead(saveFile, m_CreationTime); - - unsigned long size; - FileRead(saveFile, size); // screenshot size - - unsigned long width, height; - FileRead(saveFile, width); FileRead(saveFile, height); - QScopedArrayPointer<unsigned char> buffer(new unsigned char[width * height * 3]); - saveFile.read(reinterpret_cast<char*>(buffer.data()), width * height * 3); - // why do I have to copy here? without the copy, the buffer seems to get deleted after the - // temporary vanishes, but Qts implicit sharing should handle that? - m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).copy(); - - unsigned char pluginCount; - FileRead(saveFile, pluginCount); - - for (int i = 0; i < pluginCount; ++i) { - QString name = ReadBString(saveFile); - m_Plugins.push_back(name); - } -} - - -void SaveGameGamebryo::readFOSFile(QFile &saveFile, bool newVegas) -{ - char fileID[13]; - saveFile.read(fileID, 12); - // the signature is only 11 characters, the 12th is random? - fileID[11] = '\0'; - - if (strncmp(fileID, "FO3SAVEGAME", 11) != 0) { - throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData()); - } - - char ignore = 0x00; - while (ignore != 0x7c) { - FileRead<char>(saveFile, ignore); // unknown - } - if (newVegas) { - ignore = 0x00; - // in new vegas there is another block of uninteresting (?) information - FileSkip<char>(saveFile); // 0x7c - while (ignore != 0x7c) { - FileRead<char>(saveFile, ignore); // unknown - } - } - - unsigned long width, height; - FileRead(saveFile, width); - FileSkip<char>(saveFile); // 0x7c - FileRead(saveFile, height); - FileSkip<char>(saveFile); // 0x7c - - FileRead(saveFile, m_SaveNumber); - FileSkip<char>(saveFile); // 0x7c - - m_PCName = ReadFOSString(saveFile, true); - FileSkip<char>(saveFile); // 0x7c - - ReadFOSString(saveFile, true); - FileSkip<char>(saveFile); // 0x7c - - long Level; - FileRead(saveFile, Level); - m_PCLevel = Level; - FileSkip<char>(saveFile); // 0x7c - - m_PCLocation = ReadFOSString(saveFile, true); - FileSkip<char>(saveFile); // 0x7c - - ReadFOSString(saveFile, true); // playtime - - FileSkip<char>(saveFile); - - QScopedArrayPointer<unsigned char> buffer(new unsigned char[width * height * 3]); - saveFile.read(reinterpret_cast<char*>(buffer.data()), width * height * 3); - // why do I have to copy here? without the copy, the buffer seems to get deleted after the - // temporary vanishes, but Qts implicit sharing should handle that? - m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).scaledToWidth(256); - - FileSkip<char>(saveFile, 5); // unknown - - unsigned char pluginCount = 0; - FileRead(saveFile, pluginCount); - FileSkip<char>(saveFile); // 0x7c - - for (int i = 0; i < pluginCount; ++i) { - QString name = ReadFOSString(saveFile, true); - m_Plugins.push_back(name); - FileSkip<char>(saveFile); // 0x7c - } -} - - -void SaveGameGamebryo::setCreationTime(const QString &fileName) -{ - QFileInfo creationTime(fileName); - QDateTime modified = creationTime.lastModified(); - memset(&m_CreationTime, 0, sizeof(SYSTEMTIME)); - - m_CreationTime.wDay = static_cast<WORD>(modified.date().day()); - m_CreationTime.wDayOfWeek = static_cast<WORD>(modified.date().dayOfWeek()); - m_CreationTime.wMonth = static_cast<WORD>(modified.date().month()); - m_CreationTime.wYear =static_cast<WORD>( modified.date().year()); - - m_CreationTime.wHour = static_cast<WORD>(modified.time().hour()); - m_CreationTime.wMinute = static_cast<WORD>(modified.time().minute()); - m_CreationTime.wSecond = static_cast<WORD>(modified.time().second()); - m_CreationTime.wMilliseconds = static_cast<WORD>(modified.time().msec()); -} - - -void SaveGameGamebryo::readFile(const QString &fileName) -{ - m_FileName = fileName; - QFile saveFile(fileName); - if (!saveFile.open(QIODevice::ReadOnly)) { - throw std::runtime_error(QObject::tr("failed to open %1").arg(fileName).toUtf8().constData()); - } - switch (GameInfo::instance().getType()) { - case GameInfo::TYPE_FALLOUT3: { - setCreationTime(fileName); - readFOSFile(saveFile, false); - } break; - case GameInfo::TYPE_FALLOUTNV: { - setCreationTime(fileName); - readFOSFile(saveFile, true); - } break; - case GameInfo::TYPE_OBLIVION: { - readESSFile(saveFile); - } break; - case GameInfo::TYPE_SKYRIM: { - setCreationTime(fileName); - readSkyrimFile(saveFile); - } break; - } - - saveFile.close(); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "savegamegamebyro.h"
+#include <QFile>
+#include <QBuffer>
+#include <set>
+#include "gameinfo.h"
+#include <QFileInfo>
+#include <QDateTime>
+#include <limits>
+
+
+using namespace MOShared;
+
+
+template <typename T>
+void FileRead(QFile &file, T &value)
+{
+ int read = file.read(reinterpret_cast<char*>(&value), sizeof(T));
+ if (read != sizeof(T)) {
+ throw std::runtime_error("unexpected end of file");
+ }
+}
+
+
+template <typename T>
+void FileSkip(QFile &file, int count = 1)
+{
+ char ignore[sizeof(T)];
+ for (int i = 0; i < count; ++i) {
+ if (file.read(ignore, sizeof(T)) != sizeof(T)) {
+ throw std::runtime_error("unexpected end of file");
+ }
+ }
+}
+
+
+QString ReadBString(QFile &file)
+{
+ char buffer[256];
+ file.read(buffer, 1); // size including zero termination
+ unsigned char size = buffer[0];
+ file.read(buffer, size);
+ return QString::fromLatin1(buffer, size);
+}
+
+
+QString ReadFOSString(QFile &file, bool delimiter)
+{
+ union {
+ char lengthBuffer[2];
+ unsigned short length;
+ };
+
+ file.read(lengthBuffer, 2);
+ if (delimiter) {
+ FileSkip<char>(file); // 0x7c
+ }
+ char *buffer = new char[length];
+ file.read(buffer, length);
+
+ QString result = QString::fromLatin1(buffer, length);
+ delete [] buffer;
+
+ return result;
+}
+
+
+SaveGameGamebryo::SaveGameGamebryo(QObject *parent)
+ : SaveGame(parent), m_Plugins()
+{
+}
+
+
+SaveGameGamebryo::SaveGameGamebryo(QObject *parent, const QString &fileName)
+ : SaveGame(parent, fileName), m_Plugins()
+{
+ readFile(fileName);
+}
+
+
+SaveGameGamebryo::SaveGameGamebryo(const SaveGameGamebryo& reference)
+ : SaveGame(reference), m_Plugins(reference.m_Plugins)
+{
+}
+
+
+SaveGameGamebryo& SaveGameGamebryo::operator=(const SaveGameGamebryo &reference)
+{
+ if (&reference != this) {
+ SaveGame::operator =(reference);
+ m_Plugins = reference.m_Plugins;
+ }
+ return *this;
+}
+
+
+SaveGameGamebryo::~SaveGameGamebryo()
+{
+}
+
+
+
+
+
+void SaveGameGamebryo::readSkyrimFile(QFile &saveFile)
+{
+ char fileID[14];
+
+ saveFile.read(fileID, 13);
+ fileID[13] = '\0';
+ if (strncmp(fileID, "TESV_SAVEGAME", 13) != 0) {
+ throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData());
+ }
+
+ FileSkip<unsigned long>(saveFile); // header size
+ FileSkip<unsigned long>(saveFile); // header version, -> 8
+ FileRead(saveFile, m_SaveNumber);
+
+ m_PCName = ReadFOSString(saveFile, false);
+
+ unsigned long temp;
+ FileRead(saveFile, temp); // player level
+ m_PCLevel = static_cast<unsigned short>(temp);
+
+ m_PCLocation = ReadFOSString(saveFile, false);
+ ReadFOSString(saveFile, false); // playtime as ascii hhh.mm.ss
+ ReadFOSString(saveFile, false); // race name (i.e. BretonRace)
+
+
+ FileSkip<unsigned short>(saveFile); // ???
+ FileSkip<float>(saveFile, 2); // ???
+ FileSkip<unsigned char>(saveFile, 8); // filetime
+
+// FileSkip<unsigned char>(saveFile, 18); // ??? 18 bytes of data. not completely random, maybe a time stamp? maybe
+
+ unsigned long width, height;
+ FileRead(saveFile, width); // 320
+ FileRead(saveFile, height); // 192
+
+ QScopedArrayPointer<unsigned char> buffer(new unsigned char[width * height * 3]);
+ saveFile.read(reinterpret_cast<char*>(buffer.data()), width * height * 3);
+ // why do I have to copy here? without the copy, the buffer seems to get deleted after the
+ // temporary vanishes, but Qts implicit sharing should handle that?
+ m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).copy();
+
+ FileSkip<unsigned char>(saveFile); // form version
+ FileSkip<unsigned long>(saveFile); // plugin info size
+
+ unsigned char pluginCount;
+ FileRead(saveFile, pluginCount);
+
+ for (int i = 0; i < pluginCount; ++i) {
+ m_Plugins.push_back(ReadFOSString(saveFile, false));
+ }
+}
+
+
+void SaveGameGamebryo::readESSFile(QFile &saveFile)
+{
+ char fileID[13];
+ unsigned char versionMinor;
+ unsigned long headerVersion, saveHeaderSize;
+// *** format is different for fallout!
+ saveFile.read(fileID, 12);
+ fileID[12] = '\0';
+ FileSkip<unsigned char>(saveFile); FileRead(saveFile, versionMinor);
+ FileSkip<SYSTEMTIME>(saveFile); // modified time
+ FileRead(saveFile, headerVersion); FileRead(saveFile, saveHeaderSize);
+
+ if (strncmp(fileID, "TES4SAVEGAME", 12) != 0) {
+ throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData());
+ }
+
+ FileRead(saveFile, m_SaveNumber);
+
+ m_PCName = ReadBString(saveFile);
+
+ FileRead(saveFile, m_PCLevel);
+ m_PCLocation = ReadBString(saveFile);
+ FileSkip<float>(saveFile); // game days
+ FileSkip<unsigned long>(saveFile); // game ticks
+ FileRead(saveFile, m_CreationTime);
+
+ unsigned long size;
+ FileRead(saveFile, size); // screenshot size
+
+ unsigned long width, height;
+ FileRead(saveFile, width); FileRead(saveFile, height);
+ QScopedArrayPointer<unsigned char> buffer(new unsigned char[width * height * 3]);
+ saveFile.read(reinterpret_cast<char*>(buffer.data()), width * height * 3);
+ // why do I have to copy here? without the copy, the buffer seems to get deleted after the
+ // temporary vanishes, but Qts implicit sharing should handle that?
+ m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).copy();
+
+ unsigned char pluginCount;
+ FileRead(saveFile, pluginCount);
+
+ for (int i = 0; i < pluginCount; ++i) {
+ QString name = ReadBString(saveFile);
+ m_Plugins.push_back(name);
+ }
+}
+
+
+void SaveGameGamebryo::readFOSFile(QFile &saveFile, bool newVegas)
+{
+ char fileID[13];
+ saveFile.read(fileID, 12);
+ // the signature is only 11 characters, the 12th is random?
+ fileID[11] = '\0';
+
+ if (strncmp(fileID, "FO3SAVEGAME", 11) != 0) {
+ throw std::runtime_error(QObject::tr("wrong file format").toUtf8().constData());
+ }
+
+ char ignore = 0x00;
+ while (ignore != 0x7c) {
+ FileRead<char>(saveFile, ignore); // unknown
+ }
+ if (newVegas) {
+ ignore = 0x00;
+ // in new vegas there is another block of uninteresting (?) information
+ FileSkip<char>(saveFile); // 0x7c
+ while (ignore != 0x7c) {
+ FileRead<char>(saveFile, ignore); // unknown
+ }
+ }
+
+ unsigned long width, height;
+ FileRead(saveFile, width);
+ FileSkip<char>(saveFile); // 0x7c
+ FileRead(saveFile, height);
+ FileSkip<char>(saveFile); // 0x7c
+
+ FileRead(saveFile, m_SaveNumber);
+ FileSkip<char>(saveFile); // 0x7c
+
+ m_PCName = ReadFOSString(saveFile, true);
+ FileSkip<char>(saveFile); // 0x7c
+
+ ReadFOSString(saveFile, true);
+ FileSkip<char>(saveFile); // 0x7c
+
+ long Level;
+ FileRead(saveFile, Level);
+ m_PCLevel = Level;
+ FileSkip<char>(saveFile); // 0x7c
+
+ m_PCLocation = ReadFOSString(saveFile, true);
+ FileSkip<char>(saveFile); // 0x7c
+
+ ReadFOSString(saveFile, true); // playtime
+
+ FileSkip<char>(saveFile);
+
+ QScopedArrayPointer<unsigned char> buffer(new unsigned char[width * height * 3]);
+ saveFile.read(reinterpret_cast<char*>(buffer.data()), width * height * 3);
+ // why do I have to copy here? without the copy, the buffer seems to get deleted after the
+ // temporary vanishes, but Qts implicit sharing should handle that?
+ m_Screenshot = QImage(buffer.data(), width, height, QImage::Format_RGB888).scaledToWidth(256);
+
+ FileSkip<char>(saveFile, 5); // unknown
+
+ unsigned char pluginCount = 0;
+ FileRead(saveFile, pluginCount);
+ FileSkip<char>(saveFile); // 0x7c
+
+ for (int i = 0; i < pluginCount; ++i) {
+ QString name = ReadFOSString(saveFile, true);
+ m_Plugins.push_back(name);
+ FileSkip<char>(saveFile); // 0x7c
+ }
+}
+
+
+void SaveGameGamebryo::setCreationTime(const QString &fileName)
+{
+ QFileInfo creationTime(fileName);
+ QDateTime modified = creationTime.lastModified();
+ memset(&m_CreationTime, 0, sizeof(SYSTEMTIME));
+
+ m_CreationTime.wDay = static_cast<WORD>(modified.date().day());
+ m_CreationTime.wDayOfWeek = static_cast<WORD>(modified.date().dayOfWeek());
+ m_CreationTime.wMonth = static_cast<WORD>(modified.date().month());
+ m_CreationTime.wYear =static_cast<WORD>( modified.date().year());
+
+ m_CreationTime.wHour = static_cast<WORD>(modified.time().hour());
+ m_CreationTime.wMinute = static_cast<WORD>(modified.time().minute());
+ m_CreationTime.wSecond = static_cast<WORD>(modified.time().second());
+ m_CreationTime.wMilliseconds = static_cast<WORD>(modified.time().msec());
+}
+
+
+void SaveGameGamebryo::readFile(const QString &fileName)
+{
+ m_FileName = fileName;
+ QFile saveFile(fileName);
+ if (!saveFile.open(QIODevice::ReadOnly)) {
+ throw std::runtime_error(QObject::tr("failed to open %1").arg(fileName).toUtf8().constData());
+ }
+ switch (GameInfo::instance().getType()) {
+ case GameInfo::TYPE_FALLOUT3: {
+ setCreationTime(fileName);
+ readFOSFile(saveFile, false);
+ } break;
+ case GameInfo::TYPE_FALLOUTNV: {
+ setCreationTime(fileName);
+ readFOSFile(saveFile, true);
+ } break;
+ case GameInfo::TYPE_OBLIVION: {
+ readESSFile(saveFile);
+ } break;
+ case GameInfo::TYPE_SKYRIM: {
+ setCreationTime(fileName);
+ readSkyrimFile(saveFile);
+ } break;
+ }
+
+ saveFile.close();
+}
diff --git a/src/savegamegamebyro.h b/src/savegamegamebyro.h index 70400636..eedda6c2 100644 --- a/src/savegamegamebyro.h +++ b/src/savegamegamebyro.h @@ -1,102 +1,102 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef SAVEGAMEGAMEBRYO_H -#define SAVEGAMEGAMEBRYO_H - - -#include "savegame.h" -#include <gameinfo.h> -#include <QString> -#include <QObject> -#include <QImage> -#include <QMetaType> -#include <QFile> - -#define WIN32_LEAN_AND_MEAN -#include <Windows.h> - - -/** - * @brief represents a single save game - **/ -class SaveGameGamebryo : public SaveGame { - -Q_OBJECT - -public: - - /** - * @brief construct an empty object - **/ - SaveGameGamebryo(QObject *parent = 0); - - /** - * @brief construct a save game and immediately read out information from the file - * - * @param filename absolute path of the save game file - **/ - SaveGameGamebryo(QObject *parent, const QString &filename); - - - SaveGameGamebryo(const SaveGameGamebryo &reference); - - SaveGameGamebryo &operator=(const SaveGameGamebryo &reference); - - ~SaveGameGamebryo(); - - /** - * @brief read out information from a savegame - * - * @param fileName absolute path of the save game file - **/ - virtual void readFile(const QString &fileName); - - /** - * @return number of plugins that were enabled when the save game was created - **/ - int numPlugins() const { return m_Plugins.size(); } - - /** - * retrieve the name of one of the plugins that were enabled when the save game - * was created. valid indices are in the range between [0, numPlugins()[ - * @param index plugin index - * @return name of the plugin - **/ - const QString &plugin(int index) const { return m_Plugins.at(index); } - - -private: - - void readESSFile(QFile &saveFile); - void readFOSFile(QFile &saveFile, bool newVegas); - void readSkyrimFile(QFile &saveFile); - - void setCreationTime(const QString &fileName); - -private: - - std::vector<QString> m_Plugins; - -}; - -Q_DECLARE_METATYPE(SaveGameGamebryo) -Q_DECLARE_METATYPE(SaveGameGamebryo*) - -#endif // SAVEGAMEGAMEBRYO_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef SAVEGAMEGAMEBRYO_H
+#define SAVEGAMEGAMEBRYO_H
+
+
+#include "savegame.h"
+#include <gameinfo.h>
+#include <QString>
+#include <QObject>
+#include <QImage>
+#include <QMetaType>
+#include <QFile>
+
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+
+
+/**
+ * @brief represents a single save game
+ **/
+class SaveGameGamebryo : public SaveGame {
+
+Q_OBJECT
+
+public:
+
+ /**
+ * @brief construct an empty object
+ **/
+ SaveGameGamebryo(QObject *parent = 0);
+
+ /**
+ * @brief construct a save game and immediately read out information from the file
+ *
+ * @param filename absolute path of the save game file
+ **/
+ SaveGameGamebryo(QObject *parent, const QString &filename);
+
+
+ SaveGameGamebryo(const SaveGameGamebryo &reference);
+
+ SaveGameGamebryo &operator=(const SaveGameGamebryo &reference);
+
+ ~SaveGameGamebryo();
+
+ /**
+ * @brief read out information from a savegame
+ *
+ * @param fileName absolute path of the save game file
+ **/
+ virtual void readFile(const QString &fileName);
+
+ /**
+ * @return number of plugins that were enabled when the save game was created
+ **/
+ int numPlugins() const { return m_Plugins.size(); }
+
+ /**
+ * retrieve the name of one of the plugins that were enabled when the save game
+ * was created. valid indices are in the range between [0, numPlugins()[
+ * @param index plugin index
+ * @return name of the plugin
+ **/
+ const QString &plugin(int index) const { return m_Plugins.at(index); }
+
+
+private:
+
+ void readESSFile(QFile &saveFile);
+ void readFOSFile(QFile &saveFile, bool newVegas);
+ void readSkyrimFile(QFile &saveFile);
+
+ void setCreationTime(const QString &fileName);
+
+private:
+
+ std::vector<QString> m_Plugins;
+
+};
+
+Q_DECLARE_METATYPE(SaveGameGamebryo)
+Q_DECLARE_METATYPE(SaveGameGamebryo*)
+
+#endif // SAVEGAMEGAMEBRYO_H
diff --git a/src/savegameinfowidget.cpp b/src/savegameinfowidget.cpp index f07a9694..093b794c 100644 --- a/src/savegameinfowidget.cpp +++ b/src/savegameinfowidget.cpp @@ -1,62 +1,62 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "savegameinfowidget.h" -#include "ui_savegameinfowidget.h" -#include "utility.h" -#include <QGraphicsDropShadowEffect> - - -SaveGameInfoWidget::SaveGameInfoWidget(QWidget *parent) - : QWidget(parent), ui(new Ui::SaveGameInfoWidget) -{ - ui->setupUi(this); - this->setWindowFlags(Qt::ToolTip | Qt::BypassGraphicsProxyWidget); - setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, 0, this) / qreal(255.0)); -// installEventFilter(this); -} - -SaveGameInfoWidget::~SaveGameInfoWidget() -{ - delete ui; -} - - -void SaveGameInfoWidget::setSave(const SaveGame *saveGame) -{ - ui->saveNumLabel->setText(QString("%1").arg(saveGame->saveNumber())); - ui->characterLabel->setText(saveGame->pcName()); - ui->locationLabel->setText(saveGame->pcLocation()); - ui->levelLabel->setText(QString("%1").arg(saveGame->pcLevel())); - ui->dateLabel->setText(MOBase::ToString(saveGame->creationTime())); - ui->screenshotLabel->setPixmap(QPixmap::fromImage(saveGame->screenshot())); - if (ui->gameFrame->layout() != NULL) { - QLayoutItem *item = NULL; - while ((item = ui->gameFrame->layout()->takeAt(0)) != NULL) { - delete item->widget(); - delete item; - } - ui->gameFrame->layout()->setSizeConstraint(QLayout::SetFixedSize); - } -} - -QFrame *SaveGameInfoWidget::getGameFrame() -{ - return ui->gameFrame; -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "savegameinfowidget.h"
+#include "ui_savegameinfowidget.h"
+#include "utility.h"
+#include <QGraphicsDropShadowEffect>
+
+
+SaveGameInfoWidget::SaveGameInfoWidget(QWidget *parent)
+ : QWidget(parent), ui(new Ui::SaveGameInfoWidget)
+{
+ ui->setupUi(this);
+ this->setWindowFlags(Qt::ToolTip | Qt::BypassGraphicsProxyWidget);
+ setWindowOpacity(style()->styleHint(QStyle::SH_ToolTipLabel_Opacity, 0, this) / qreal(255.0));
+// installEventFilter(this);
+}
+
+SaveGameInfoWidget::~SaveGameInfoWidget()
+{
+ delete ui;
+}
+
+
+void SaveGameInfoWidget::setSave(const SaveGame *saveGame)
+{
+ ui->saveNumLabel->setText(QString("%1").arg(saveGame->saveNumber()));
+ ui->characterLabel->setText(saveGame->pcName());
+ ui->locationLabel->setText(saveGame->pcLocation());
+ ui->levelLabel->setText(QString("%1").arg(saveGame->pcLevel()));
+ ui->dateLabel->setText(MOBase::ToString(saveGame->creationTime()));
+ ui->screenshotLabel->setPixmap(QPixmap::fromImage(saveGame->screenshot()));
+ if (ui->gameFrame->layout() != NULL) {
+ QLayoutItem *item = NULL;
+ while ((item = ui->gameFrame->layout()->takeAt(0)) != NULL) {
+ delete item->widget();
+ delete item;
+ }
+ ui->gameFrame->layout()->setSizeConstraint(QLayout::SetFixedSize);
+ }
+}
+
+QFrame *SaveGameInfoWidget::getGameFrame()
+{
+ return ui->gameFrame;
+}
diff --git a/src/savegameinfowidget.h b/src/savegameinfowidget.h index 318108c4..d1cd2b98 100644 --- a/src/savegameinfowidget.h +++ b/src/savegameinfowidget.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#ifndef SAVEGAMEINFOWIDGET_H
#define SAVEGAMEINFOWIDGET_H
diff --git a/src/savegameinfowidgetgamebryo.cpp b/src/savegameinfowidgetgamebryo.cpp index deaa3e01..8a7cf1ba 100644 --- a/src/savegameinfowidgetgamebryo.cpp +++ b/src/savegameinfowidgetgamebryo.cpp @@ -1,73 +1,73 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "savegameinfowidgetgamebryo.h" -#include <QLabel> -#include <QVBoxLayout> - - -SaveGameInfoWidgetGamebryo::SaveGameInfoWidgetGamebryo(const SaveGame *saveGame, PluginList *pluginList, QWidget *parent) - : SaveGameInfoWidget(parent), m_PluginList(pluginList) -{ - QVBoxLayout *gameLayout = new QVBoxLayout(); - gameLayout->setMargin(0); - gameLayout->setSpacing(2); - getGameFrame()->setLayout(gameLayout); - setSave(saveGame); -} - - -void SaveGameInfoWidgetGamebryo::setSave(const SaveGame *saveGame) -{ - SaveGameInfoWidget::setSave(saveGame); - const SaveGameGamebryo *gamebryoSave = qobject_cast<const SaveGameGamebryo*>(saveGame); - QLayout *layout = getGameFrame()->layout(); - QLabel *header = new QLabel(tr("Missing ESPs")); - QFont headerFont = header->font(); - QFont contentFont = headerFont; - headerFont.setItalic(true); - contentFont.setBold(true); - contentFont.setPointSize(7); - header->setFont(headerFont); - layout->addWidget(header); - int count = 0; - for (int i = 0; i < gamebryoSave->numPlugins(); ++i) { - const QString &pluginName = gamebryoSave->plugin(i); - if (m_PluginList->isEnabled(pluginName)) { - continue; - } else { - ++count; - } - - if (count > 10) { - break; - } - - QLabel *pluginLabel = new QLabel(gamebryoSave->plugin(i)); - pluginLabel->setIndent(10); - pluginLabel->setFont(contentFont); - layout->addWidget(pluginLabel); - } - if (count > 10) { - QLabel *dotDotLabel = new QLabel("..."); - dotDotLabel->setIndent(10); - dotDotLabel->setFont(contentFont); - layout->addWidget(dotDotLabel); - } -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "savegameinfowidgetgamebryo.h"
+#include <QLabel>
+#include <QVBoxLayout>
+
+
+SaveGameInfoWidgetGamebryo::SaveGameInfoWidgetGamebryo(const SaveGame *saveGame, PluginList *pluginList, QWidget *parent)
+ : SaveGameInfoWidget(parent), m_PluginList(pluginList)
+{
+ QVBoxLayout *gameLayout = new QVBoxLayout();
+ gameLayout->setMargin(0);
+ gameLayout->setSpacing(2);
+ getGameFrame()->setLayout(gameLayout);
+ setSave(saveGame);
+}
+
+
+void SaveGameInfoWidgetGamebryo::setSave(const SaveGame *saveGame)
+{
+ SaveGameInfoWidget::setSave(saveGame);
+ const SaveGameGamebryo *gamebryoSave = qobject_cast<const SaveGameGamebryo*>(saveGame);
+ QLayout *layout = getGameFrame()->layout();
+ QLabel *header = new QLabel(tr("Missing ESPs"));
+ QFont headerFont = header->font();
+ QFont contentFont = headerFont;
+ headerFont.setItalic(true);
+ contentFont.setBold(true);
+ contentFont.setPointSize(7);
+ header->setFont(headerFont);
+ layout->addWidget(header);
+ int count = 0;
+ for (int i = 0; i < gamebryoSave->numPlugins(); ++i) {
+ const QString &pluginName = gamebryoSave->plugin(i);
+ if (m_PluginList->isEnabled(pluginName)) {
+ continue;
+ } else {
+ ++count;
+ }
+
+ if (count > 10) {
+ break;
+ }
+
+ QLabel *pluginLabel = new QLabel(gamebryoSave->plugin(i));
+ pluginLabel->setIndent(10);
+ pluginLabel->setFont(contentFont);
+ layout->addWidget(pluginLabel);
+ }
+ if (count > 10) {
+ QLabel *dotDotLabel = new QLabel("...");
+ dotDotLabel->setIndent(10);
+ dotDotLabel->setFont(contentFont);
+ layout->addWidget(dotDotLabel);
+ }
+}
diff --git a/src/savegameinfowidgetgamebryo.h b/src/savegameinfowidgetgamebryo.h index b60de8ea..1f5fd7ee 100644 --- a/src/savegameinfowidgetgamebryo.h +++ b/src/savegameinfowidgetgamebryo.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#ifndef SAVEGAMEINFOWIDGETGAMEBRYO_H
#define SAVEGAMEINFOWIDGETGAMEBRYO_H
diff --git a/src/selectiondialog.cpp b/src/selectiondialog.cpp index 902b4d9c..b4df8a1f 100644 --- a/src/selectiondialog.cpp +++ b/src/selectiondialog.cpp @@ -1,83 +1,83 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "selectiondialog.h" -#include "ui_selectiondialog.h" - -#include <QCommandLinkButton> - -SelectionDialog::SelectionDialog(const QString &description, QWidget *parent) - : QDialog(parent), ui(new Ui::SelectionDialog), m_Choice(NULL), m_ValidateByData(false) -{ - ui->setupUi(this); - - ui->descriptionLabel->setText(description); -} - -SelectionDialog::~SelectionDialog() -{ - delete ui; -} - - -void SelectionDialog::addChoice(const QString &buttonText, const QString &description, const QVariant &data) -{ - QCommandLinkButton *button = new QCommandLinkButton(buttonText, description, ui->buttonBox); - button->setProperty("data", data); - ui->buttonBox->addButton(button, QDialogButtonBox::AcceptRole); - if (data.isValid()) m_ValidateByData = true; -} - -int SelectionDialog::numChoices() const -{ - return ui->buttonBox->findChildren<QCommandLinkButton*>(QString()).count(); -} - - -QVariant SelectionDialog::getChoiceData() -{ - return m_Choice->property("data"); -} - - -QString SelectionDialog::getChoiceString() -{ - if ((m_Choice == NULL) || - (m_ValidateByData && !m_Choice->property("data").isValid())) { - return QString(); - } else { - return m_Choice->text(); - } -} - - -void SelectionDialog::on_buttonBox_clicked(QAbstractButton *button) -{ - m_Choice = button; - if (!m_ValidateByData || m_Choice->property("data").isValid()) { - this->accept(); - } else { - this->reject(); - } -} - -void SelectionDialog::on_cancelButton_clicked() -{ - this->reject(); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "selectiondialog.h"
+#include "ui_selectiondialog.h"
+
+#include <QCommandLinkButton>
+
+SelectionDialog::SelectionDialog(const QString &description, QWidget *parent)
+ : QDialog(parent), ui(new Ui::SelectionDialog), m_Choice(NULL), m_ValidateByData(false)
+{
+ ui->setupUi(this);
+
+ ui->descriptionLabel->setText(description);
+}
+
+SelectionDialog::~SelectionDialog()
+{
+ delete ui;
+}
+
+
+void SelectionDialog::addChoice(const QString &buttonText, const QString &description, const QVariant &data)
+{
+ QCommandLinkButton *button = new QCommandLinkButton(buttonText, description, ui->buttonBox);
+ button->setProperty("data", data);
+ ui->buttonBox->addButton(button, QDialogButtonBox::AcceptRole);
+ if (data.isValid()) m_ValidateByData = true;
+}
+
+int SelectionDialog::numChoices() const
+{
+ return ui->buttonBox->findChildren<QCommandLinkButton*>(QString()).count();
+}
+
+
+QVariant SelectionDialog::getChoiceData()
+{
+ return m_Choice->property("data");
+}
+
+
+QString SelectionDialog::getChoiceString()
+{
+ if ((m_Choice == NULL) ||
+ (m_ValidateByData && !m_Choice->property("data").isValid())) {
+ return QString();
+ } else {
+ return m_Choice->text();
+ }
+}
+
+
+void SelectionDialog::on_buttonBox_clicked(QAbstractButton *button)
+{
+ m_Choice = button;
+ if (!m_ValidateByData || m_Choice->property("data").isValid()) {
+ this->accept();
+ } else {
+ this->reject();
+ }
+}
+
+void SelectionDialog::on_cancelButton_clicked()
+{
+ this->reject();
+}
diff --git a/src/selectiondialog.h b/src/selectiondialog.h index fc04291e..fe148859 100644 --- a/src/selectiondialog.h +++ b/src/selectiondialog.h @@ -1,68 +1,68 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef SELECTIONDIALOG_H -#define SELECTIONDIALOG_H - -#include <QDialog> -#include <QAbstractButton> - -namespace Ui { -class SelectionDialog; -} - -class SelectionDialog : public QDialog -{ - Q_OBJECT - -public: - - explicit SelectionDialog(const QString &description, QWidget *parent = 0); - - ~SelectionDialog(); - - /** - * @brief add a choice to the dialog - * @param buttonText the text to be displayed on the button - * @param description the description that shows up under in small letters inside the button - * @param data data to be stored with the button. Please note that as soon as one choice has data associated with it (non-invalid QVariant) - * all buttons that contain no data will be treated as "cancel" buttons - */ - void addChoice(const QString &buttonText, const QString &description, const QVariant &data); - - int numChoices() const; - - QVariant getChoiceData(); - QString getChoiceString(); - -private slots: - - void on_buttonBox_clicked(QAbstractButton *button); - - void on_cancelButton_clicked(); - -private: - - Ui::SelectionDialog *ui; - QAbstractButton *m_Choice; - bool m_ValidateByData; - -}; - -#endif // SELECTIONDIALOG_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef SELECTIONDIALOG_H
+#define SELECTIONDIALOG_H
+
+#include <QDialog>
+#include <QAbstractButton>
+
+namespace Ui {
+class SelectionDialog;
+}
+
+class SelectionDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+
+ explicit SelectionDialog(const QString &description, QWidget *parent = 0);
+
+ ~SelectionDialog();
+
+ /**
+ * @brief add a choice to the dialog
+ * @param buttonText the text to be displayed on the button
+ * @param description the description that shows up under in small letters inside the button
+ * @param data data to be stored with the button. Please note that as soon as one choice has data associated with it (non-invalid QVariant)
+ * all buttons that contain no data will be treated as "cancel" buttons
+ */
+ void addChoice(const QString &buttonText, const QString &description, const QVariant &data);
+
+ int numChoices() const;
+
+ QVariant getChoiceData();
+ QString getChoiceString();
+
+private slots:
+
+ void on_buttonBox_clicked(QAbstractButton *button);
+
+ void on_cancelButton_clicked();
+
+private:
+
+ Ui::SelectionDialog *ui;
+ QAbstractButton *m_Choice;
+ bool m_ValidateByData;
+
+};
+
+#endif // SELECTIONDIALOG_H
diff --git a/src/selfupdater.cpp b/src/selfupdater.cpp index 650e650c..6037422c 100644 --- a/src/selfupdater.cpp +++ b/src/selfupdater.cpp @@ -1,466 +1,466 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "selfupdater.h" -#include "utility.h" -#include "installationmanager.h" -#include "report.h" -#include "messagedialog.h" -#include "downloadmanager.h" - -#include <versioninfo.h> -#include <gameinfo.h> -#include <skyriminfo.h> -#include <QMessageBox> -#include <QNetworkAccessManager> -#include <QNetworkRequest> -#include <QDir> -#include <QLibrary> -#include <QProcess> -#include <QApplication> -#include <util.h> -#include <boost/bind.hpp> - - -using namespace MOBase; -using namespace MOShared; - - -typedef Archive* (*CreateArchiveType)(); - - -template <typename T> T resolveFunction(QLibrary &lib, const char *name) -{ - T temp = reinterpret_cast<T>(lib.resolve(name)); - if (temp == NULL) { - throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData()); - } - return temp; -} - - -SelfUpdater::SelfUpdater(NexusInterface *nexusInterface, QWidget *parent) - : QObject(parent), m_Parent(parent), m_Interface(nexusInterface), m_UpdateRequestID(-1), - m_Reply(NULL), m_Progress(parent), m_Attempts(3) -{ - m_Progress.setMaximum(100); - - QLibrary archiveLib("dlls\\archive.dll"); - if (!archiveLib.load()) { - throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString())); - } - - CreateArchiveType CreateArchiveFunc = resolveFunction<CreateArchiveType>(archiveLib, "CreateArchive"); - - m_CurrentArchive = CreateArchiveFunc(); - if (!m_CurrentArchive->isValid()) { - throw MyException(InstallationManager::getErrorString(m_CurrentArchive->getLastError())); - } - - connect(&m_Progress, SIGNAL(canceled()), this, SLOT(downloadCancel())); - - VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath())); - - m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16, - version.dwFileVersionMS & 0xFFFF, - version.dwFileVersionLS >> 16); -} - - -SelfUpdater::~SelfUpdater() -{ - delete m_CurrentArchive; -} - - -void SelfUpdater::testForUpdate() -{ - if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) { - emit updateAvailable(); - return; - } - - if (m_UpdateRequestID == -1) { - m_UpdateRequestID = m_Interface->requestDescription( - SkyrimInfo::getNexusModIDStatic(), this, QVariant(), - QString(), ToQString(SkyrimInfo::getNexusInfoUrlStatic()), - SkyrimInfo::getNexusGameIDStatic()); - } -} - -void SelfUpdater::startUpdate() -{ - if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) { - m_UpdateFile.setFileName(QCoreApplication::applicationDirPath() + "/mo_test_update.7z"); - installUpdate(); - return; - } - - if ((m_UpdateRequestID == -1) && - (!m_NewestVersion.isEmpty())) { - if (QMessageBox::question(m_Parent, tr("Update"), - tr("An update is available (newest version: %1), do you want to install it?").arg(m_NewestVersion), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - m_UpdateRequestID = m_Interface->requestFiles(SkyrimInfo::getNexusModIDStatic(), - this, m_NewestVersion, - ToQString(SkyrimInfo::getNexusInfoUrlStatic())); - } - } -} - - -void SelfUpdater::download(const QString &downloadLink, const QString &fileName) -{ - QNetworkAccessManager *accessManager = m_Interface->getAccessManager(); - QUrl dlUrl(downloadLink); - QNetworkRequest request(dlUrl); - m_Canceled = false; - m_Reply = accessManager->get(request); - m_UpdateFile.setFileName(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory()).append("\\").append(fileName))); - m_UpdateFile.open(QIODevice::WriteOnly); - m_Progress.setModal(true); - m_Progress.show(); - m_Progress.setValue(0); - m_Progress.setWindowTitle(tr("Update")); - m_Progress.setLabelText(tr("Download in progress")); - - connect(m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64))); - connect(m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished())); - connect(m_Reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead())); -} - - -void SelfUpdater::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) -{ - if (m_Reply != NULL) { - if (m_Canceled) { - m_Reply->abort(); - } else { - if (bytesTotal != 0) { - m_Progress.setValue((bytesReceived * 100) / bytesTotal); - } - } - } -} - - -void SelfUpdater::downloadReadyRead() -{ - if (m_Reply != NULL) { - m_UpdateFile.write(m_Reply->readAll()); - } -} - - -void SelfUpdater::downloadFinished() -{ - int error = QNetworkReply::NoError; - - if (m_Reply != NULL) { - m_UpdateFile.write(m_Reply->readAll()); - - error = m_Reply->error(); - - if (m_Reply->header(QNetworkRequest::ContentTypeHeader).toString().startsWith("text", Qt::CaseInsensitive)) { - m_Canceled = true; - } - - m_Progress.hide(); - m_Reply->close(); - m_Reply->deleteLater(); - m_Reply = NULL; - } - - m_UpdateFile.close(); - - if ((m_UpdateFile.size() == 0) || - (error != QNetworkReply::NoError) || - m_Canceled) { - if (!m_Canceled) { - reportError(tr("Download failed: %1").arg(error)); - } - m_UpdateFile.remove(); - return; - } - - 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(ToQString(GameInfo::instance().getOrganizerDirectory())); - - QString backupPath = mopath.mid(0).append("/update_backup"); - QDir().mkdir(backupPath); - - // rename files that are currently open so we can unpack the update - if (!m_CurrentArchive->open(ToWString(QDir::toNativeSeparators(m_UpdateFile.fileName())).c_str(), - new MethodCallback<SelfUpdater, void, LPSTR>(this, &SelfUpdater::queryPassword))) { - throw MyException(tr("failed to open archive \"%1\": %2") - .arg(m_UpdateFile.fileName()) - .arg(InstallationManager::getErrorString(m_CurrentArchive->getLastError()))); - } - - // move all files contained in the archive out of the way, - // otherwise we can't overwrite everything - FileData* const *data; - size_t size; - m_CurrentArchive->getFileList(data, size); - - for (size_t i = 0; i < size; ++i) { - QString outputName = ToQString(data[i]->getFileName()); - if (outputName.startsWith("ModOrganizer\\", Qt::CaseInsensitive)) { - outputName = outputName.mid(13); - data[i]->setOutputFileName(ToWString(outputName).c_str()); - } else if (outputName == "ModOrganizer") { - data[i]->setSkip(true); - } - QFileInfo file(mopath.mid(0).append("/").append(outputName)); - if (file.exists() && file.isFile()) { - if (!shellMove(QStringList(mopath.mid(0).append("/").append(outputName)), - QStringList(backupPath.mid(0).append("/").append(outputName)))) { - reportError(tr("failed to move outdated files: %1. Please update manually.").arg(windowsErrorString(::GetLastError()))); - return; - } - } - } - - // now unpack the archive into the mo directory - if (!m_CurrentArchive->extract(GameInfo::instance().getOrganizerDirectory().c_str(), - new MethodCallback<SelfUpdater, void, float>(this, &SelfUpdater::updateProgress), - new MethodCallback<SelfUpdater, void, LPCWSTR>(this, &SelfUpdater::updateProgressFile), - new MethodCallback<SelfUpdater, void, LPCWSTR>(this, &SelfUpdater::report7ZipError))) { - throw std::runtime_error("extracting failed"); - } - - m_CurrentArchive->close(); - - m_UpdateFile.remove(); - - QMessageBox::information(m_Parent, tr("Update"), tr("Update installed, Mod Organizer will now be restarted.")); - - QProcess newProcess; - if (QFile::exists(mopath.mid(0).append("/ModOrganizer.exe"))) { - newProcess.startDetached(mopath.mid(0).append("/ModOrganizer.exe"), QStringList("update")); - } else { - newProcess.startDetached(mopath.mid(0).append("/ModOrganiser.exe"), QStringList("update")); - } - emit restart(); -} - -void SelfUpdater::queryPassword(LPSTR) -{ - // nop -} - -void SelfUpdater::updateProgress(float) -{ - // nop -} - -void SelfUpdater::updateProgressFile(LPCWSTR) -{ - // nop -} - -void SelfUpdater::report7ZipError(LPCWSTR errorMessage) -{ - QMessageBox::critical(m_Parent, tr("Error"), ToQString(errorMessage)); -} - - -QString SelfUpdater::retrieveNews(const QString &description) -{ - QStringList temp = description.split("[s][/s]"); - if (temp.length() < 2) { - return QString(); - } else { - return temp.at(1); - } -} - - -void SelfUpdater::nxmDescriptionAvailable(int, QVariant, QVariant resultData, int requestID) -{ - if (requestID == m_UpdateRequestID) { - m_UpdateRequestID = -1; - - QVariantMap result = resultData.toMap(); - QString motd = retrieveNews(result["description"].toString()).trimmed(); - if (motd.length() != 0) { - emit motdAvailable(motd); - } - - m_NewestVersion = result["version"].toString(); - if (m_NewestVersion.isEmpty()) { - QTimer::singleShot(5000, this, SLOT(testForUpdate())); - } - VersionInfo currentVersion(m_MOVersion); - VersionInfo newestVersion(m_NewestVersion); - - if (!m_NewestVersion.isEmpty() && (currentVersion < newestVersion)) { - emit updateAvailable(); - } else if (newestVersion < currentVersion) { - qDebug("this version is newer than the current version on nexus (%s vs %s)", - currentVersion.canonicalString().toUtf8().constData(), - newestVersion.canonicalString().toUtf8().constData()); - } - } -} - - -void SelfUpdater::nxmFilesAvailable(int, QVariant userData, QVariant resultData, int requestID) -{ - if (requestID != m_UpdateRequestID) { - return; - } - QString version = userData.toString(); - - m_UpdateRequestID = -1; - - if (!resultData.canConvert<QVariantList>()) { - qCritical("invalid files result: %s", resultData.toString().toUtf8().constData()); - reportError(tr("Failed to parse response. Please report this as a bug and include the file mo_interface.log.")); - return; - } - - QVariantList result = resultData.toList(); - - QRegExp updateExpList(QString("updates version ([0-9., ]*) to %1").arg(version)); - QRegExp updateExpRange(QString("updates version ([0-9.]*) - ([0-9.]*) to %1").arg(version)); - int updateFileID = -1; - QString updateFileName; - int mainFileID = -1; - QString mainFileName; - int mainFileSize = 0; - - foreach(QVariant file, result) { - QVariantMap fileInfo = file.toMap(); - if (!fileInfo["uri"].toString().endsWith(".7z")) { - continue; - } - - if (fileInfo["version"].toString() == version) { - if (fileInfo["category_id"].toInt() == 2) { - QString description = fileInfo["description"].toString(); - // update - if (updateExpList.indexIn(description) != -1) { - // there is an update for the newest version of MO, but does - // it apply to the current version? - QStringList supportedVersions = updateExpList.cap(1).split(QRegExp(",[ ]*"), QString::SkipEmptyParts); - if (supportedVersions.contains(m_MOVersion.canonicalString())) { - updateFileID = fileInfo["id"].toInt(); - updateFileName = fileInfo["uri"].toString(); - } else { - qDebug("update not supported from %s", m_MOVersion.canonicalString().toUtf8().constData()); - } - } else if (updateExpRange.indexIn(description) != -1) { - VersionInfo rangeLowEnd(updateExpRange.cap(1)); - VersionInfo rangeHighEnd(updateExpRange.cap(2)); - if ((rangeLowEnd <= m_MOVersion) && - (m_MOVersion <= rangeHighEnd)) { - updateFileID = fileInfo["id"].toInt(); - updateFileName = fileInfo["uri"].toString(); - break; - } else { - qDebug("update not supported from %s", m_MOVersion.canonicalString().toUtf8().constData()); - } - } else { - qWarning("invalid update description: %s", - description.toUtf8().constData()); - } - } else if (fileInfo["category_id"].toInt() == 1) { - mainFileID = fileInfo["id"].toInt(); - mainFileName = fileInfo["uri"].toString(); - mainFileSize = fileInfo["size"].toInt(); - } - } - } - - if (updateFileID != -1) { - qDebug("update available: %d", updateFileID); - m_UpdateRequestID = m_Interface->requestDownloadURL(SkyrimInfo::getNexusModIDStatic(), - updateFileID, this, updateFileName, - ToQString(SkyrimInfo::getNexusInfoUrlStatic())); - } else if (mainFileID != -1) { - qDebug("full download required: %d", mainFileID); - if (QMessageBox::question(m_Parent, tr("Update"), - tr("No incremental update available for this version, " - "the complete package needs to be downloaded (%1 kB)").arg(mainFileSize), - QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) { - m_UpdateRequestID = m_Interface->requestDownloadURL(SkyrimInfo::getNexusModIDStatic(), - mainFileID, this, mainFileName, - ToQString(SkyrimInfo::getNexusInfoUrlStatic())); - } - } else { - qCritical("no file for update found"); - MessageDialog::showMessage(tr("no file for update found. Please update manually."), m_Parent); - m_Progress.hide(); - } -} - - -void SelfUpdater::nxmRequestFailed(int, int, QVariant, int requestID, const QString &errorMessage) -{ - if (requestID == m_UpdateRequestID) { - m_UpdateRequestID = -1; - if (m_Attempts > 0) { - QTimer::singleShot(60000, this, SLOT(testForUpdate())); - --m_Attempts; - } else { - qWarning("Failed to retrieve update information: %s", qPrintable(errorMessage)); - MessageDialog::showMessage(tr("Failed to retrieve update information: %1").arg(errorMessage), m_Parent, false); - } - } -} - - -void SelfUpdater::nxmDownloadURLsAvailable(int, int, QVariant userData, QVariant resultData, int requestID) -{ - if (requestID == m_UpdateRequestID) { - m_UpdateRequestID = -1; - QVariantList serverList = resultData.toList(); - if (serverList.count() != 0) { - std::map<QString, int> dummy; - qSort(serverList.begin(), serverList.end(), boost::bind(&DownloadManager::ServerByPreference, dummy, _1, _2)); - - - QVariantMap dlServer = serverList.first().toMap(); - - download(dlServer["URI"].toString(), userData.toString()); - } else { - MessageDialog::showMessage(tr("No download server available. Please try again later."), m_Parent); - m_Progress.hide(); - } - } -} - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "selfupdater.h"
+#include "utility.h"
+#include "installationmanager.h"
+#include "report.h"
+#include "messagedialog.h"
+#include "downloadmanager.h"
+
+#include <versioninfo.h>
+#include <gameinfo.h>
+#include <skyriminfo.h>
+#include <QMessageBox>
+#include <QNetworkAccessManager>
+#include <QNetworkRequest>
+#include <QDir>
+#include <QLibrary>
+#include <QProcess>
+#include <QApplication>
+#include <util.h>
+#include <boost/bind.hpp>
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+typedef Archive* (*CreateArchiveType)();
+
+
+template <typename T> T resolveFunction(QLibrary &lib, const char *name)
+{
+ T temp = reinterpret_cast<T>(lib.resolve(name));
+ if (temp == NULL) {
+ throw std::runtime_error(QObject::tr("invalid 7-zip32.dll: %1").arg(lib.errorString()).toLatin1().constData());
+ }
+ return temp;
+}
+
+
+SelfUpdater::SelfUpdater(NexusInterface *nexusInterface, QWidget *parent)
+ : QObject(parent), m_Parent(parent), m_Interface(nexusInterface), m_UpdateRequestID(-1),
+ m_Reply(NULL), m_Progress(parent), m_Attempts(3)
+{
+ m_Progress.setMaximum(100);
+
+ QLibrary archiveLib("dlls\\archive.dll");
+ if (!archiveLib.load()) {
+ throw MyException(tr("archive.dll not loaded: \"%1\"").arg(archiveLib.errorString()));
+ }
+
+ CreateArchiveType CreateArchiveFunc = resolveFunction<CreateArchiveType>(archiveLib, "CreateArchive");
+
+ m_CurrentArchive = CreateArchiveFunc();
+ if (!m_CurrentArchive->isValid()) {
+ throw MyException(InstallationManager::getErrorString(m_CurrentArchive->getLastError()));
+ }
+
+ connect(&m_Progress, SIGNAL(canceled()), this, SLOT(downloadCancel()));
+
+ VS_FIXEDFILEINFO version = GetFileVersion(ToWString(QApplication::applicationFilePath()));
+
+ m_MOVersion = VersionInfo(version.dwFileVersionMS >> 16,
+ version.dwFileVersionMS & 0xFFFF,
+ version.dwFileVersionLS >> 16);
+}
+
+
+SelfUpdater::~SelfUpdater()
+{
+ delete m_CurrentArchive;
+}
+
+
+void SelfUpdater::testForUpdate()
+{
+ if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) {
+ emit updateAvailable();
+ return;
+ }
+
+ if (m_UpdateRequestID == -1) {
+ m_UpdateRequestID = m_Interface->requestDescription(
+ SkyrimInfo::getNexusModIDStatic(), this, QVariant(),
+ QString(), ToQString(SkyrimInfo::getNexusInfoUrlStatic()),
+ SkyrimInfo::getNexusGameIDStatic());
+ }
+}
+
+void SelfUpdater::startUpdate()
+{
+ if (QFile::exists(QCoreApplication::applicationDirPath() + "/mo_test_update.7z")) {
+ m_UpdateFile.setFileName(QCoreApplication::applicationDirPath() + "/mo_test_update.7z");
+ installUpdate();
+ return;
+ }
+
+ if ((m_UpdateRequestID == -1) &&
+ (!m_NewestVersion.isEmpty())) {
+ if (QMessageBox::question(m_Parent, tr("Update"),
+ tr("An update is available (newest version: %1), do you want to install it?").arg(m_NewestVersion),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ m_UpdateRequestID = m_Interface->requestFiles(SkyrimInfo::getNexusModIDStatic(),
+ this, m_NewestVersion,
+ ToQString(SkyrimInfo::getNexusInfoUrlStatic()));
+ }
+ }
+}
+
+
+void SelfUpdater::download(const QString &downloadLink, const QString &fileName)
+{
+ QNetworkAccessManager *accessManager = m_Interface->getAccessManager();
+ QUrl dlUrl(downloadLink);
+ QNetworkRequest request(dlUrl);
+ m_Canceled = false;
+ m_Reply = accessManager->get(request);
+ m_UpdateFile.setFileName(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory()).append("\\").append(fileName)));
+ m_UpdateFile.open(QIODevice::WriteOnly);
+ m_Progress.setModal(true);
+ m_Progress.show();
+ m_Progress.setValue(0);
+ m_Progress.setWindowTitle(tr("Update"));
+ m_Progress.setLabelText(tr("Download in progress"));
+
+ connect(m_Reply, SIGNAL(downloadProgress(qint64, qint64)), this, SLOT(downloadProgress(qint64, qint64)));
+ connect(m_Reply, SIGNAL(finished()), this, SLOT(downloadFinished()));
+ connect(m_Reply, SIGNAL(readyRead()), this, SLOT(downloadReadyRead()));
+}
+
+
+void SelfUpdater::downloadProgress(qint64 bytesReceived, qint64 bytesTotal)
+{
+ if (m_Reply != NULL) {
+ if (m_Canceled) {
+ m_Reply->abort();
+ } else {
+ if (bytesTotal != 0) {
+ m_Progress.setValue((bytesReceived * 100) / bytesTotal);
+ }
+ }
+ }
+}
+
+
+void SelfUpdater::downloadReadyRead()
+{
+ if (m_Reply != NULL) {
+ m_UpdateFile.write(m_Reply->readAll());
+ }
+}
+
+
+void SelfUpdater::downloadFinished()
+{
+ int error = QNetworkReply::NoError;
+
+ if (m_Reply != NULL) {
+ m_UpdateFile.write(m_Reply->readAll());
+
+ error = m_Reply->error();
+
+ if (m_Reply->header(QNetworkRequest::ContentTypeHeader).toString().startsWith("text", Qt::CaseInsensitive)) {
+ m_Canceled = true;
+ }
+
+ m_Progress.hide();
+ m_Reply->close();
+ m_Reply->deleteLater();
+ m_Reply = NULL;
+ }
+
+ m_UpdateFile.close();
+
+ if ((m_UpdateFile.size() == 0) ||
+ (error != QNetworkReply::NoError) ||
+ m_Canceled) {
+ if (!m_Canceled) {
+ reportError(tr("Download failed: %1").arg(error));
+ }
+ m_UpdateFile.remove();
+ return;
+ }
+
+ 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(ToQString(GameInfo::instance().getOrganizerDirectory()));
+
+ QString backupPath = mopath.mid(0).append("/update_backup");
+ QDir().mkdir(backupPath);
+
+ // rename files that are currently open so we can unpack the update
+ if (!m_CurrentArchive->open(ToWString(QDir::toNativeSeparators(m_UpdateFile.fileName())).c_str(),
+ new MethodCallback<SelfUpdater, void, LPSTR>(this, &SelfUpdater::queryPassword))) {
+ throw MyException(tr("failed to open archive \"%1\": %2")
+ .arg(m_UpdateFile.fileName())
+ .arg(InstallationManager::getErrorString(m_CurrentArchive->getLastError())));
+ }
+
+ // move all files contained in the archive out of the way,
+ // otherwise we can't overwrite everything
+ FileData* const *data;
+ size_t size;
+ m_CurrentArchive->getFileList(data, size);
+
+ for (size_t i = 0; i < size; ++i) {
+ QString outputName = ToQString(data[i]->getFileName());
+ if (outputName.startsWith("ModOrganizer\\", Qt::CaseInsensitive)) {
+ outputName = outputName.mid(13);
+ data[i]->setOutputFileName(ToWString(outputName).c_str());
+ } else if (outputName == "ModOrganizer") {
+ data[i]->setSkip(true);
+ }
+ QFileInfo file(mopath.mid(0).append("/").append(outputName));
+ if (file.exists() && file.isFile()) {
+ if (!shellMove(QStringList(mopath.mid(0).append("/").append(outputName)),
+ QStringList(backupPath.mid(0).append("/").append(outputName)))) {
+ reportError(tr("failed to move outdated files: %1. Please update manually.").arg(windowsErrorString(::GetLastError())));
+ return;
+ }
+ }
+ }
+
+ // now unpack the archive into the mo directory
+ if (!m_CurrentArchive->extract(GameInfo::instance().getOrganizerDirectory().c_str(),
+ new MethodCallback<SelfUpdater, void, float>(this, &SelfUpdater::updateProgress),
+ new MethodCallback<SelfUpdater, void, LPCWSTR>(this, &SelfUpdater::updateProgressFile),
+ new MethodCallback<SelfUpdater, void, LPCWSTR>(this, &SelfUpdater::report7ZipError))) {
+ throw std::runtime_error("extracting failed");
+ }
+
+ m_CurrentArchive->close();
+
+ m_UpdateFile.remove();
+
+ QMessageBox::information(m_Parent, tr("Update"), tr("Update installed, Mod Organizer will now be restarted."));
+
+ QProcess newProcess;
+ if (QFile::exists(mopath.mid(0).append("/ModOrganizer.exe"))) {
+ newProcess.startDetached(mopath.mid(0).append("/ModOrganizer.exe"), QStringList("update"));
+ } else {
+ newProcess.startDetached(mopath.mid(0).append("/ModOrganiser.exe"), QStringList("update"));
+ }
+ emit restart();
+}
+
+void SelfUpdater::queryPassword(LPSTR)
+{
+ // nop
+}
+
+void SelfUpdater::updateProgress(float)
+{
+ // nop
+}
+
+void SelfUpdater::updateProgressFile(LPCWSTR)
+{
+ // nop
+}
+
+void SelfUpdater::report7ZipError(LPCWSTR errorMessage)
+{
+ QMessageBox::critical(m_Parent, tr("Error"), ToQString(errorMessage));
+}
+
+
+QString SelfUpdater::retrieveNews(const QString &description)
+{
+ QStringList temp = description.split("[s][/s]");
+ if (temp.length() < 2) {
+ return QString();
+ } else {
+ return temp.at(1);
+ }
+}
+
+
+void SelfUpdater::nxmDescriptionAvailable(int, QVariant, QVariant resultData, int requestID)
+{
+ if (requestID == m_UpdateRequestID) {
+ m_UpdateRequestID = -1;
+
+ QVariantMap result = resultData.toMap();
+ QString motd = retrieveNews(result["description"].toString()).trimmed();
+ if (motd.length() != 0) {
+ emit motdAvailable(motd);
+ }
+
+ m_NewestVersion = result["version"].toString();
+ if (m_NewestVersion.isEmpty()) {
+ QTimer::singleShot(5000, this, SLOT(testForUpdate()));
+ }
+ VersionInfo currentVersion(m_MOVersion);
+ VersionInfo newestVersion(m_NewestVersion);
+
+ if (!m_NewestVersion.isEmpty() && (currentVersion < newestVersion)) {
+ emit updateAvailable();
+ } else if (newestVersion < currentVersion) {
+ qDebug("this version is newer than the current version on nexus (%s vs %s)",
+ currentVersion.canonicalString().toUtf8().constData(),
+ newestVersion.canonicalString().toUtf8().constData());
+ }
+ }
+}
+
+
+void SelfUpdater::nxmFilesAvailable(int, QVariant userData, QVariant resultData, int requestID)
+{
+ if (requestID != m_UpdateRequestID) {
+ return;
+ }
+ QString version = userData.toString();
+
+ m_UpdateRequestID = -1;
+
+ if (!resultData.canConvert<QVariantList>()) {
+ qCritical("invalid files result: %s", resultData.toString().toUtf8().constData());
+ reportError(tr("Failed to parse response. Please report this as a bug and include the file mo_interface.log."));
+ return;
+ }
+
+ QVariantList result = resultData.toList();
+
+ QRegExp updateExpList(QString("updates version ([0-9., ]*) to %1").arg(version));
+ QRegExp updateExpRange(QString("updates version ([0-9.]*) - ([0-9.]*) to %1").arg(version));
+ int updateFileID = -1;
+ QString updateFileName;
+ int mainFileID = -1;
+ QString mainFileName;
+ int mainFileSize = 0;
+
+ foreach(QVariant file, result) {
+ QVariantMap fileInfo = file.toMap();
+ if (!fileInfo["uri"].toString().endsWith(".7z")) {
+ continue;
+ }
+
+ if (fileInfo["version"].toString() == version) {
+ if (fileInfo["category_id"].toInt() == 2) {
+ QString description = fileInfo["description"].toString();
+ // update
+ if (updateExpList.indexIn(description) != -1) {
+ // there is an update for the newest version of MO, but does
+ // it apply to the current version?
+ QStringList supportedVersions = updateExpList.cap(1).split(QRegExp(",[ ]*"), QString::SkipEmptyParts);
+ if (supportedVersions.contains(m_MOVersion.canonicalString())) {
+ updateFileID = fileInfo["id"].toInt();
+ updateFileName = fileInfo["uri"].toString();
+ } else {
+ qDebug("update not supported from %s", m_MOVersion.canonicalString().toUtf8().constData());
+ }
+ } else if (updateExpRange.indexIn(description) != -1) {
+ VersionInfo rangeLowEnd(updateExpRange.cap(1));
+ VersionInfo rangeHighEnd(updateExpRange.cap(2));
+ if ((rangeLowEnd <= m_MOVersion) &&
+ (m_MOVersion <= rangeHighEnd)) {
+ updateFileID = fileInfo["id"].toInt();
+ updateFileName = fileInfo["uri"].toString();
+ break;
+ } else {
+ qDebug("update not supported from %s", m_MOVersion.canonicalString().toUtf8().constData());
+ }
+ } else {
+ qWarning("invalid update description: %s",
+ description.toUtf8().constData());
+ }
+ } else if (fileInfo["category_id"].toInt() == 1) {
+ mainFileID = fileInfo["id"].toInt();
+ mainFileName = fileInfo["uri"].toString();
+ mainFileSize = fileInfo["size"].toInt();
+ }
+ }
+ }
+
+ if (updateFileID != -1) {
+ qDebug("update available: %d", updateFileID);
+ m_UpdateRequestID = m_Interface->requestDownloadURL(SkyrimInfo::getNexusModIDStatic(),
+ updateFileID, this, updateFileName,
+ ToQString(SkyrimInfo::getNexusInfoUrlStatic()));
+ } else if (mainFileID != -1) {
+ qDebug("full download required: %d", mainFileID);
+ if (QMessageBox::question(m_Parent, tr("Update"),
+ tr("No incremental update available for this version, "
+ "the complete package needs to be downloaded (%1 kB)").arg(mainFileSize),
+ QMessageBox::Ok | QMessageBox::Cancel) == QMessageBox::Ok) {
+ m_UpdateRequestID = m_Interface->requestDownloadURL(SkyrimInfo::getNexusModIDStatic(),
+ mainFileID, this, mainFileName,
+ ToQString(SkyrimInfo::getNexusInfoUrlStatic()));
+ }
+ } else {
+ qCritical("no file for update found");
+ MessageDialog::showMessage(tr("no file for update found. Please update manually."), m_Parent);
+ m_Progress.hide();
+ }
+}
+
+
+void SelfUpdater::nxmRequestFailed(int, int, QVariant, int requestID, const QString &errorMessage)
+{
+ if (requestID == m_UpdateRequestID) {
+ m_UpdateRequestID = -1;
+ if (m_Attempts > 0) {
+ QTimer::singleShot(60000, this, SLOT(testForUpdate()));
+ --m_Attempts;
+ } else {
+ qWarning("Failed to retrieve update information: %s", qPrintable(errorMessage));
+ MessageDialog::showMessage(tr("Failed to retrieve update information: %1").arg(errorMessage), m_Parent, false);
+ }
+ }
+}
+
+
+void SelfUpdater::nxmDownloadURLsAvailable(int, int, QVariant userData, QVariant resultData, int requestID)
+{
+ if (requestID == m_UpdateRequestID) {
+ m_UpdateRequestID = -1;
+ QVariantList serverList = resultData.toList();
+ if (serverList.count() != 0) {
+ std::map<QString, int> dummy;
+ qSort(serverList.begin(), serverList.end(), boost::bind(&DownloadManager::ServerByPreference, dummy, _1, _2));
+
+
+ QVariantMap dlServer = serverList.first().toMap();
+
+ download(dlServer["URI"].toString(), userData.toString());
+ } else {
+ MessageDialog::showMessage(tr("No download server available. Please try again later."), m_Parent);
+ m_Progress.hide();
+ }
+ }
+}
+
diff --git a/src/selfupdater.h b/src/selfupdater.h index 14f7e90a..9648f15a 100644 --- a/src/selfupdater.h +++ b/src/selfupdater.h @@ -1,145 +1,145 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef SELFUPDATER_H -#define SELFUPDATER_H - - -#include "nexusinterface.h" -#include <archive.h> -#include <versioninfo.h> - -#include <QObject> -#include <QNetworkReply> -#include <QFile> -#include <QProgressDialog> - - -/** - * @brief manages updates for Mod Organizer itself - * This class is used to update the Mod Organizer - * The process looks like this: - * 1. call testForUpdate() to determine is available - * 2. if the updateAvailable() signal is received, allow the user to start the update - * 3. if the user start the update, call startUpdate() - * 4. startUpdate() will first query a list of files, try to determine if there is an - * incremental update. If not, the user will have to confirm the download of a full download. - * Once the correct file is selected, it is downloaded. - * 5. before the downloaded file is extracted, existing files that are going to be replaced are - * moved to "update_backup" on because files that are currently open can't be replaced. - * 6. the update is extracted and then deleted - * 7. finally, a restart is requested via signal. - * 8. at restart, Mod Organizer will remove the update_backup directory since none of the files - * should now be open - * - * @todo use NexusBridge - **/ -class SelfUpdater : public QObject -{ - - Q_OBJECT - -public: - - /** - * @brief constructor - * - * @param nexusInterface interface to query information from nexus - * @param parent parent widget - * @todo passing the nexus interface is unneccessary - **/ - SelfUpdater(NexusInterface *nexusInterface, QWidget *parent); - ~SelfUpdater(); - - /** - * @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(); - - void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID); - void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorMessage); - void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); - -signals: - - /** - * @brief emitted if a restart of the client is necessary to complete the update - **/ - void restart(); - - /** - * @brief emitted if an update is available - **/ - void updateAvailable(); - - /** - * @brief emitted if a message of the day was received - **/ - void motdAvailable(const QString &motd); - -private: - - void download(const QString &downloadLink, const QString &fileName); - void installUpdate(); - void queryPassword(LPSTR password); - void updateProgress(float percentage); - void updateProgressFile(LPCWSTR fileName); - void report7ZipError(LPCWSTR errorMessage); - QString retrieveNews(const QString &description); - -private slots: - - void downloadProgress(qint64 bytesReceived, qint64 bytesTotal); - void downloadReadyRead(); - void downloadFinished(); - void downloadCancel(); - -private: - - QWidget *m_Parent; - MOBase::VersionInfo m_MOVersion; - NexusInterface *m_Interface; - int m_UpdateRequestID; - QString m_NewestVersion; - QFile m_UpdateFile; - QNetworkReply *m_Reply; - QProgressDialog m_Progress; - bool m_Canceled; - int m_Attempts; - - Archive *m_CurrentArchive; - -}; - - -#endif // SELFUPDATER_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef SELFUPDATER_H
+#define SELFUPDATER_H
+
+
+#include "nexusinterface.h"
+#include <archive.h>
+#include <versioninfo.h>
+
+#include <QObject>
+#include <QNetworkReply>
+#include <QFile>
+#include <QProgressDialog>
+
+
+/**
+ * @brief manages updates for Mod Organizer itself
+ * This class is used to update the Mod Organizer
+ * The process looks like this:
+ * 1. call testForUpdate() to determine is available
+ * 2. if the updateAvailable() signal is received, allow the user to start the update
+ * 3. if the user start the update, call startUpdate()
+ * 4. startUpdate() will first query a list of files, try to determine if there is an
+ * incremental update. If not, the user will have to confirm the download of a full download.
+ * Once the correct file is selected, it is downloaded.
+ * 5. before the downloaded file is extracted, existing files that are going to be replaced are
+ * moved to "update_backup" on because files that are currently open can't be replaced.
+ * 6. the update is extracted and then deleted
+ * 7. finally, a restart is requested via signal.
+ * 8. at restart, Mod Organizer will remove the update_backup directory since none of the files
+ * should now be open
+ *
+ * @todo use NexusBridge
+ **/
+class SelfUpdater : public QObject
+{
+
+ Q_OBJECT
+
+public:
+
+ /**
+ * @brief constructor
+ *
+ * @param nexusInterface interface to query information from nexus
+ * @param parent parent widget
+ * @todo passing the nexus interface is unneccessary
+ **/
+ SelfUpdater(NexusInterface *nexusInterface, QWidget *parent);
+ ~SelfUpdater();
+
+ /**
+ * @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();
+
+ void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData, int requestID);
+ void nxmFilesAvailable(int modID, QVariant userData, QVariant resultData, int requestID);
+ void nxmRequestFailed(int modID, int fileID, QVariant userData, int requestID, const QString &errorMessage);
+ void nxmDownloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData, int requestID);
+
+signals:
+
+ /**
+ * @brief emitted if a restart of the client is necessary to complete the update
+ **/
+ void restart();
+
+ /**
+ * @brief emitted if an update is available
+ **/
+ void updateAvailable();
+
+ /**
+ * @brief emitted if a message of the day was received
+ **/
+ void motdAvailable(const QString &motd);
+
+private:
+
+ void download(const QString &downloadLink, const QString &fileName);
+ void installUpdate();
+ void queryPassword(LPSTR password);
+ void updateProgress(float percentage);
+ void updateProgressFile(LPCWSTR fileName);
+ void report7ZipError(LPCWSTR errorMessage);
+ QString retrieveNews(const QString &description);
+
+private slots:
+
+ void downloadProgress(qint64 bytesReceived, qint64 bytesTotal);
+ void downloadReadyRead();
+ void downloadFinished();
+ void downloadCancel();
+
+private:
+
+ QWidget *m_Parent;
+ MOBase::VersionInfo m_MOVersion;
+ NexusInterface *m_Interface;
+ int m_UpdateRequestID;
+ QString m_NewestVersion;
+ QFile m_UpdateFile;
+ QNetworkReply *m_Reply;
+ QProgressDialog m_Progress;
+ bool m_Canceled;
+ int m_Attempts;
+
+ Archive *m_CurrentArchive;
+
+};
+
+
+#endif // SELFUPDATER_H
diff --git a/src/settings.cpp b/src/settings.cpp index 3c78ed62..1cc168dd 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -1,771 +1,771 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "settings.h" - -#include "settingsdialog.h" -#include "utility.h" -#include "helper.h" -#include <gameinfo.h> -#include <appconfig.h> -#include <utility.h> - -#include <QCheckBox> -#include <QLineEdit> -#include <QDirIterator> -#include <QRegExp> -#include <QCoreApplication> -#include <QMessageBox> - - -using namespace MOBase; -using namespace MOShared; - - -template <typename T> -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<T>() < other.data(m_SortRole).value<T>(); - } -private: - 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 = NULL; - - -Settings::Settings() - : m_Settings(ToQString(GameInfo::instance().getIniFilename()), QSettings::IniFormat) -{ - if (s_Instance != NULL) { - throw std::runtime_error("second instance of \"Settings\" created"); - } else { - s_Instance = this; - } -} - - -Settings::~Settings() -{ - s_Instance = NULL; -} - - -Settings &Settings::instance() -{ - if (s_Instance == NULL) { - throw std::runtime_error("no instance of \"Settings\""); - } - return *s_Instance; -} - -void Settings::clearPlugins() -{ - m_Plugins.clear(); - m_PluginSettings.clear(); - - m_PluginBlacklist.clear(); - int count = m_Settings.beginReadArray("pluginBlacklist"); - for (int i = 0; i < count; ++i) { - m_Settings.setArrayIndex(i); - m_PluginBlacklist.insert(m_Settings.value("name").toString()); - } - m_Settings.endArray(); -} - -bool Settings::pluginBlacklisted(const QString &fileName) const -{ - return m_PluginBlacklist.contains(fileName); -} - -void Settings::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"; - ::ShellExecuteW(NULL, L"open", nxmPath.c_str(), - (mode + L" " + GameInfo::instance().getGameShortName() + L" \"" + executable + L"\"").c_str(), NULL, SW_SHOWNORMAL); -} - -void Settings::registerPlugin(IPlugin *plugin) -{ - m_Plugins.push_back(plugin); - m_PluginSettings.insert(plugin->name(), QMap<QString, QVariant>()); - foreach (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; - } -} - - -QString Settings::obfuscate(const QString &password) const -{ - QByteArray temp = password.toUtf8(); - - QByteArray buffer; - for (int i = 0; i < temp.length(); ++i) { - buffer.append(temp.at(i) ^ Key2[i % 20]); - } - return buffer.toBase64(); -} - - -QString Settings::deObfuscate(const QString &password) const -{ - 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()); -} - - -bool Settings::hideUncheckedPlugins() const -{ - return m_Settings.value("Settings/hide_unchecked_plugins", false).toBool(); -} - -bool Settings::forceEnableCoreFiles() const -{ - return m_Settings.value("Settings/force_enable_core_files", true).toBool(); -} - -bool Settings::automaticLoginEnabled() const -{ - return m_Settings.value("Settings/nexus_login", false).toBool(); -} - -QString Settings::getSteamAppID() const -{ - return m_Settings.value("Settings/app_id", ToQString(GameInfo::instance().getSteamAPPId(m_Settings.value("game_edition", 0).toInt()))).toString(); -} - -QString Settings::getDownloadDirectory() const -{ - return QDir::toNativeSeparators(m_Settings.value("Settings/download_directory", ToQString(GameInfo::instance().getDownloadDir())).toString()); -} - - -void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond) -{ - m_Settings.beginGroup("Servers"); - - foreach (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<double>(bytesPerSecond); - m_Settings.setValue(serverKey, data); - } - } - - m_Settings.endGroup(); - m_Settings.sync(); -} - -std::map<QString, int> Settings::getPreferredServers() -{ - std::map<QString, int> result; - m_Settings.beginGroup("Servers"); - - foreach (const QString &serverKey, m_Settings.childKeys()) { - QVariantMap data = m_Settings.value(serverKey).toMap(); - int preference = data["preferred"].toInt(); - if (preference > 0) { - result[serverKey] = preference; - } - } - m_Settings.endGroup(); - - return result; -} - -QString Settings::getCacheDirectory() const -{ - return QDir::toNativeSeparators(m_Settings.value("Settings/cache_directory", ToQString(GameInfo::instance().getCacheDir())).toString()); -} - -QString Settings::getModDirectory() const -{ - return QDir::toNativeSeparators(m_Settings.value("Settings/mod_directory", ToQString(GameInfo::instance().getModsDir())).toString()); -} - -QString Settings::getNMMVersion() const -{ - static const QString MIN_NMM_VERSION = "0.47.0"; - 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::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; - } -} - -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", 0).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)); -} - - -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"); -} - - -void Settings::setupLoadMechanism() -{ - m_LoadMechanism.activate(getLoadMechanism()); -} - - -bool Settings::useProxy() -{ - return m_Settings.value("Settings/use_proxy", false).toBool(); -} - -bool Settings::displayForeign() -{ - 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; -} - -void Settings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value) -{ - auto iterPlugin = m_PluginSettings.find(pluginName); - if (iterPlugin == m_PluginSettings.end()) { - throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); - } - - // store the new setting both in memory and in the ini - m_PluginSettings[pluginName][key] = value; - m_Settings.setValue("Plugins/" + pluginName + "/" + key, value); -} - -QVariant Settings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const -{ - if (!m_PluginSettings.contains(pluginName)) { - return def; - } - return m_Settings.value("PluginPersistance/" + pluginName + "/" + key, def); -} - -void Settings::setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync) -{ - if (!m_PluginSettings.contains(pluginName)) { - throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName)); - } - m_Settings.setValue("PluginPersistance/" + pluginName + "/" + key, value); - if (sync) { - m_Settings.sync(); - } -} - -QString Settings::language() -{ - QString result = m_Settings.value("Settings/language", "").toString(); - 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::updateServers(const QList<ServerInfo> &servers) -{ - m_Settings.beginGroup("Servers"); - QStringList oldServerKeys = m_Settings.childKeys(); - - foreach (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; - - m_Settings.setValue(server.name, data); - } - } - - m_Settings.endGroup(); - m_Settings.sync(); -} - -void Settings::addBlacklistPlugin(const QString &fileName) -{ - m_PluginBlacklist.insert(fileName); - writePluginBlacklist(); -} - -void Settings::writePluginBlacklist() -{ - m_Settings.beginWriteArray("pluginBlacklist"); - int idx = 0; - foreach (const QString &plugin, m_PluginBlacklist) { - m_Settings.setArrayIndex(idx++); - m_Settings.setValue("name", plugin); - } - - m_Settings.endArray(); -} - -void Settings::addLanguages(QComboBox *languageBox) -{ - languageBox->addItem("English", "en_US"); - - 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 = QLocale::languageToString(locale.language()); - if (locale.language() == QLocale::Chinese) { - if (languageCode == "zh_TW") { - languageString = "Chinese (traditional)"; - } else { - languageString = "Chinese (simplified)"; - } - } - languageBox->addItem(QString("%1").arg(languageString), exp.cap(1)); - } - } -} - -void Settings::addStyles(QComboBox *styleBox) -{ - styleBox->addItem("None", ""); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - styleBox->addItem("Fusion", "Fusion"); -#else - styleBox->addItem("Plastique", "Plastique"); - styleBox->addItem("Cleanlooks", "Cleanlooks"); -#endif - - QDirIterator langIter(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()), QStringList("*.qss"), QDir::Files); - while (langIter.hasNext()) { - langIter.next(); - QString style = langIter.fileName(); - styleBox->addItem(style, style); - } -} - -bool Settings::isNXMHandler(bool *modifyable) -{ - QSettings handlerReg("HKEY_CURRENT_USER\\Software\\Classes\\nxm\\shell\\open\\command", - QSettings::NativeFormat); - - QString currentExe = handlerReg.value("Default", "").toString().toUtf8().constData(); - QString myExe = QString("\"%1\" ").arg(QDir::toNativeSeparators(QCoreApplication::applicationFilePath())).append("\"%1\""); - if (modifyable != NULL) { - handlerReg.setValue("Default", currentExe); - handlerReg.sync(); - - *modifyable = handlerReg.status() == QSettings::NoError; - // QSettings::isWritable returns wrong results... - } - return currentExe == myExe; -} - - -void Settings::setNXMHandlerActive(bool active, bool writable) -{ -// QSettings handlerReg("HKEY_CLASSES_ROOT\\nxm\\", QSettings::NativeFormat); - QSettings handlerReg("HKEY_CURRENT_USER\\Software\\Classes\\nxm\\", QSettings::NativeFormat); - - if (writable) { - QString myExe = QString("\"%1\" ").arg(QDir::toNativeSeparators(QCoreApplication::applicationFilePath())).append("\"%1\""); - handlerReg.setValue("Default", "URL:NXM Protocol"); - handlerReg.setValue("URL Protocol", ""); - handlerReg.setValue("shell/open/command/Default", active ? myExe : ""); - handlerReg.sync(); - } else { - Helper::setNXMHandler(GameInfo::instance().getOrganizerDirectory(), active); - } -} - - -void Settings::resetDialogs() -{ - m_Settings.beginGroup("DialogChoices"); - QStringList keys = m_Settings.childKeys(); - foreach (QString key, keys) { - m_Settings.remove(key); - } - - m_Settings.endGroup(); -} - - -void Settings::query(QWidget *parent) -{ - SettingsDialog dialog(parent); - - connect(&dialog, SIGNAL(resetDialogs()), this, SLOT(resetDialogs())); - - // General Page - QComboBox *languageBox = dialog.findChild<QComboBox*>("languageBox"); - QComboBox *styleBox = dialog.findChild<QComboBox*>("styleBox"); - QComboBox *logLevelBox = dialog.findChild<QComboBox*>("logLevelBox"); - QCheckBox *compactBox = dialog.findChild<QCheckBox*>("compactBox"); - QCheckBox *showMetaBox = dialog.findChild<QCheckBox*>("showMetaBox"); - - QLineEdit *downloadDirEdit = dialog.findChild<QLineEdit*>("downloadDirEdit"); - QLineEdit *modDirEdit = dialog.findChild<QLineEdit*>("modDirEdit"); - QLineEdit *cacheDirEdit = dialog.findChild<QLineEdit*>("cacheDirEdit"); - - // nexus page - QCheckBox *loginCheckBox = dialog.findChild<QCheckBox*>("loginCheckBox"); - QLineEdit *usernameEdit = dialog.findChild<QLineEdit*>("usernameEdit"); - QLineEdit *passwordEdit = dialog.findChild<QLineEdit*>("passwordEdit"); - QCheckBox *offlineBox = dialog.findChild<QCheckBox*>("offlineBox"); - QCheckBox *proxyBox = dialog.findChild<QCheckBox*>("proxyBox"); - - QListWidget *knownServersList = dialog.findChild<QListWidget*>("knownServersList"); - QListWidget *preferredServersList = dialog.findChild<QListWidget*>("preferredServersList"); - - // plugis page - QListWidget *pluginsList = dialog.findChild<QListWidget*>("pluginsList"); - QListWidget *pluginBlacklistList = dialog.findChild<QListWidget*>("pluginBlacklist"); - - // workarounds page - QCheckBox *forceEnableBox = dialog.findChild<QCheckBox*>("forceEnableBox"); - QComboBox *mechanismBox = dialog.findChild<QComboBox*>("mechanismBox"); - QLineEdit *appIDEdit = dialog.findChild<QLineEdit*>("appIDEdit"); - QLineEdit *nmmVersionEdit = dialog.findChild<QLineEdit*>("nmmVersionEdit"); - QCheckBox *hideUncheckedBox = dialog.findChild<QCheckBox*>("hideUncheckedBox"); - QCheckBox *displayForeignBox = dialog.findChild<QCheckBox*>("displayForeignBox"); - - - // - // set up current settings - // - LoadMechanism::EMechanism mechanismID = getLoadMechanism(); - int index = 0; - - if (m_LoadMechanism.isDirectLoadingSupported()) { - mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER); - if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) { - index = mechanismBox->count() - 1; - } - } - - if (m_LoadMechanism.isScriptExtenderSupported()) { - mechanismBox->addItem(QObject::tr("Script Extender"), LoadMechanism::LOAD_SCRIPTEXTENDER); - if (mechanismID == LoadMechanism::LOAD_SCRIPTEXTENDER) { - index = mechanismBox->count() - 1; - } - } - - if (m_LoadMechanism.isProxyDLLSupported()) { - mechanismBox->addItem(QObject::tr("Proxy DLL"), LoadMechanism::LOAD_PROXYDLL); - if (mechanismID == LoadMechanism::LOAD_PROXYDLL) { - index = mechanismBox->count() - 1; - } - } - - mechanismBox->setCurrentIndex(index); - - { - addLanguages(languageBox); - QString languageCode = language(); - int currentID = 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 = languageBox->findData(languageCode.mid(0, 2)); - } - if (currentID != -1) { - languageBox->setCurrentIndex(currentID); - } - } - - { - addStyles(styleBox); - int currentID = styleBox->findData(m_Settings.value("Settings/style", "").toString()); - if (currentID != -1) { - styleBox->setCurrentIndex(currentID); - } - } - - compactBox->setChecked(compactDownloads()); - showMetaBox->setChecked(metaDownloads()); - - hideUncheckedBox->setChecked(hideUncheckedPlugins()); - displayForeignBox->setChecked(displayForeign()); - forceEnableBox->setChecked(forceEnableCoreFiles()); - - appIDEdit->setText(getSteamAppID()); - - if (automaticLoginEnabled()) { - loginCheckBox->setChecked(true); - usernameEdit->setText(m_Settings.value("Settings/nexus_username", "").toString()); - passwordEdit->setText(deObfuscate(m_Settings.value("Settings/nexus_password", "").toString())); - } - - downloadDirEdit->setText(getDownloadDirectory()); - modDirEdit->setText(getModDirectory()); - cacheDirEdit->setText(getCacheDirectory()); - offlineBox->setChecked(offlineMode()); - proxyBox->setChecked(useProxy()); - nmmVersionEdit->setText(getNMMVersion()); - logLevelBox->setCurrentIndex(logLevel()); - - // display plugin settings - foreach (IPlugin *plugin, m_Plugins) { - QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), pluginsList); - listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin)); - listItem->setData(Qt::UserRole + 1, m_PluginSettings[plugin->name()]); - pluginsList->addItem(listItem); - } - - // display plugin blacklist - foreach (const QString &pluginName, m_PluginBlacklist) { - pluginBlacklistList->addItem(pluginName); - } - - // display server preferences - m_Settings.beginGroup("Servers"); - foreach (const QString &key, m_Settings.childKeys()) { - QVariantMap val = m_Settings.value(key).toMap(); - 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<int>(val["downloadSpeed"].toDouble() / val["downloadCount"].toInt()); - descriptor += QString(" (%1 kbps)").arg(bps / 1024); - } - - QListWidgetItem *newItem = new QListWidgetItemEx<int>(descriptor, Qt::UserRole + 1); - - newItem->setData(Qt::UserRole, key); - newItem->setData(Qt::UserRole + 1, val["preferred"].toInt()); - if (val["preferred"].toInt() > 0) { - preferredServersList->addItem(newItem); - } else { - knownServersList->addItem(newItem); - } - preferredServersList->sortItems(Qt::DescendingOrder); - } - m_Settings.endGroup(); - - if (dialog.exec() == QDialog::Accepted) { - // - // transfer modified settings to configuration file - // - - m_Settings.setValue("Settings/hide_unchecked_plugins", hideUncheckedBox->checkState() ? true : false); - m_Settings.setValue("Settings/force_enable_core_files", forceEnableBox->checkState() ? true : false); - m_Settings.setValue("Settings/compact_downloads", compactBox->isChecked()); - m_Settings.setValue("Settings/meta_downloads", showMetaBox->isChecked()); - m_Settings.setValue("Settings/load_mechanism", mechanismBox->itemData(mechanismBox->currentIndex()).toInt()); - - - { // advanced settings - if ((QDir::fromNativeSeparators(modDirEdit->text()) != QDir::fromNativeSeparators(getModDirectory())) && - (QMessageBox::question(NULL, 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)) { - modDirEdit->setText(getModDirectory()); - } - - if (!QDir(downloadDirEdit->text()).exists()) { - QDir().mkpath(downloadDirEdit->text()); - } - if (!QDir(cacheDirEdit->text()).exists()) { - QDir().mkpath(cacheDirEdit->text()); - } - if (!QDir(modDirEdit->text()).exists()) { - QDir().mkpath(modDirEdit->text()); - } - - m_Settings.setValue("Settings/download_directory", QDir::toNativeSeparators(downloadDirEdit->text())); - m_Settings.setValue("Settings/cache_directory", QDir::toNativeSeparators(cacheDirEdit->text())); - m_Settings.setValue("Settings/mod_directory", QDir::toNativeSeparators(modDirEdit->text())); - } - - - QString oldLanguage = m_Settings.value("Settings/language", "en_US").toString(); - QString newLanguage = languageBox->itemData(languageBox->currentIndex()).toString(); - if (newLanguage != oldLanguage) { - m_Settings.setValue("Settings/language", newLanguage); - emit languageChanged(newLanguage); - } - - QString oldStyle = m_Settings.value("Settings/style", "").toString(); - QString newStyle = styleBox->itemData(styleBox->currentIndex()).toString(); - if (oldStyle != newStyle) { - m_Settings.setValue("Settings/style", newStyle); - emit styleChanged(newStyle); - } - - m_Settings.setValue("Settings/log_level", logLevelBox->currentIndex()); - - if (appIDEdit->text() != ToQString(GameInfo::instance().getSteamAPPId())) { - m_Settings.setValue("Settings/app_id", appIDEdit->text()); - } else { - m_Settings.remove("Settings/app_id"); - } - if (loginCheckBox->isChecked()) { - m_Settings.setValue("Settings/nexus_login", true); - m_Settings.setValue("Settings/nexus_username", usernameEdit->text()); - m_Settings.setValue("Settings/nexus_password", obfuscate(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", offlineBox->isChecked()); - m_Settings.setValue("Settings/use_proxy", proxyBox->isChecked()); - m_Settings.setValue("Settings/display_foreign", displayForeignBox->isChecked()); - - m_Settings.setValue("Settings/nmm_version", nmmVersionEdit->text()); - - // transfer plugin settings to in-memory structure - for (int i = 0; i < pluginsList->count(); ++i) { - QListWidgetItem *item = pluginsList->item(i); - m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap(); - } - // store plugin settings on disc - for (auto iterPlugins = m_PluginSettings.begin(); iterPlugins != 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_PluginBlacklist.clear(); - foreach (QListWidgetItem *item, pluginBlacklistList->findItems("*", Qt::MatchWildcard)) { - m_PluginBlacklist.insert(item->text()); - } - writePluginBlacklist(); - - // store server preference - m_Settings.beginGroup("Servers"); - for (int i = 0; i < knownServersList->count(); ++i) { - QString key = knownServersList->item(i)->data(Qt::UserRole).toString(); - QVariantMap val = m_Settings.value(key).toMap(); - val["preferred"] = 0; - m_Settings.setValue(key, val); - } - int count = preferredServersList->count(); - for (int i = 0; i < count; ++i) { - QString key = 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(); - } -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "settings.h"
+
+#include "settingsdialog.h"
+#include "utility.h"
+#include "helper.h"
+#include <gameinfo.h>
+#include <appconfig.h>
+#include <utility.h>
+
+#include <QCheckBox>
+#include <QLineEdit>
+#include <QDirIterator>
+#include <QRegExp>
+#include <QCoreApplication>
+#include <QMessageBox>
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+template <typename T>
+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<T>() < other.data(m_SortRole).value<T>();
+ }
+private:
+ 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 = NULL;
+
+
+Settings::Settings()
+ : m_Settings(ToQString(GameInfo::instance().getIniFilename()), QSettings::IniFormat)
+{
+ if (s_Instance != NULL) {
+ throw std::runtime_error("second instance of \"Settings\" created");
+ } else {
+ s_Instance = this;
+ }
+}
+
+
+Settings::~Settings()
+{
+ s_Instance = NULL;
+}
+
+
+Settings &Settings::instance()
+{
+ if (s_Instance == NULL) {
+ throw std::runtime_error("no instance of \"Settings\"");
+ }
+ return *s_Instance;
+}
+
+void Settings::clearPlugins()
+{
+ m_Plugins.clear();
+ m_PluginSettings.clear();
+
+ m_PluginBlacklist.clear();
+ int count = m_Settings.beginReadArray("pluginBlacklist");
+ for (int i = 0; i < count; ++i) {
+ m_Settings.setArrayIndex(i);
+ m_PluginBlacklist.insert(m_Settings.value("name").toString());
+ }
+ m_Settings.endArray();
+}
+
+bool Settings::pluginBlacklisted(const QString &fileName) const
+{
+ return m_PluginBlacklist.contains(fileName);
+}
+
+void Settings::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";
+ ::ShellExecuteW(NULL, L"open", nxmPath.c_str(),
+ (mode + L" " + GameInfo::instance().getGameShortName() + L" \"" + executable + L"\"").c_str(), NULL, SW_SHOWNORMAL);
+}
+
+void Settings::registerPlugin(IPlugin *plugin)
+{
+ m_Plugins.push_back(plugin);
+ m_PluginSettings.insert(plugin->name(), QMap<QString, QVariant>());
+ foreach (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;
+ }
+}
+
+
+QString Settings::obfuscate(const QString &password) const
+{
+ QByteArray temp = password.toUtf8();
+
+ QByteArray buffer;
+ for (int i = 0; i < temp.length(); ++i) {
+ buffer.append(temp.at(i) ^ Key2[i % 20]);
+ }
+ return buffer.toBase64();
+}
+
+
+QString Settings::deObfuscate(const QString &password) const
+{
+ 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());
+}
+
+
+bool Settings::hideUncheckedPlugins() const
+{
+ return m_Settings.value("Settings/hide_unchecked_plugins", false).toBool();
+}
+
+bool Settings::forceEnableCoreFiles() const
+{
+ return m_Settings.value("Settings/force_enable_core_files", true).toBool();
+}
+
+bool Settings::automaticLoginEnabled() const
+{
+ return m_Settings.value("Settings/nexus_login", false).toBool();
+}
+
+QString Settings::getSteamAppID() const
+{
+ return m_Settings.value("Settings/app_id", ToQString(GameInfo::instance().getSteamAPPId(m_Settings.value("game_edition", 0).toInt()))).toString();
+}
+
+QString Settings::getDownloadDirectory() const
+{
+ return QDir::toNativeSeparators(m_Settings.value("Settings/download_directory", ToQString(GameInfo::instance().getDownloadDir())).toString());
+}
+
+
+void Settings::setDownloadSpeed(const QString &serverName, int bytesPerSecond)
+{
+ m_Settings.beginGroup("Servers");
+
+ foreach (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<double>(bytesPerSecond);
+ m_Settings.setValue(serverKey, data);
+ }
+ }
+
+ m_Settings.endGroup();
+ m_Settings.sync();
+}
+
+std::map<QString, int> Settings::getPreferredServers()
+{
+ std::map<QString, int> result;
+ m_Settings.beginGroup("Servers");
+
+ foreach (const QString &serverKey, m_Settings.childKeys()) {
+ QVariantMap data = m_Settings.value(serverKey).toMap();
+ int preference = data["preferred"].toInt();
+ if (preference > 0) {
+ result[serverKey] = preference;
+ }
+ }
+ m_Settings.endGroup();
+
+ return result;
+}
+
+QString Settings::getCacheDirectory() const
+{
+ return QDir::toNativeSeparators(m_Settings.value("Settings/cache_directory", ToQString(GameInfo::instance().getCacheDir())).toString());
+}
+
+QString Settings::getModDirectory() const
+{
+ return QDir::toNativeSeparators(m_Settings.value("Settings/mod_directory", ToQString(GameInfo::instance().getModsDir())).toString());
+}
+
+QString Settings::getNMMVersion() const
+{
+ static const QString MIN_NMM_VERSION = "0.47.0";
+ 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::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;
+ }
+}
+
+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", 0).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));
+}
+
+
+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");
+}
+
+
+void Settings::setupLoadMechanism()
+{
+ m_LoadMechanism.activate(getLoadMechanism());
+}
+
+
+bool Settings::useProxy()
+{
+ return m_Settings.value("Settings/use_proxy", false).toBool();
+}
+
+bool Settings::displayForeign()
+{
+ 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;
+}
+
+void Settings::setPluginSetting(const QString &pluginName, const QString &key, const QVariant &value)
+{
+ auto iterPlugin = m_PluginSettings.find(pluginName);
+ if (iterPlugin == m_PluginSettings.end()) {
+ throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName));
+ }
+
+ // store the new setting both in memory and in the ini
+ m_PluginSettings[pluginName][key] = value;
+ m_Settings.setValue("Plugins/" + pluginName + "/" + key, value);
+}
+
+QVariant Settings::pluginPersistent(const QString &pluginName, const QString &key, const QVariant &def) const
+{
+ if (!m_PluginSettings.contains(pluginName)) {
+ return def;
+ }
+ return m_Settings.value("PluginPersistance/" + pluginName + "/" + key, def);
+}
+
+void Settings::setPluginPersistent(const QString &pluginName, const QString &key, const QVariant &value, bool sync)
+{
+ if (!m_PluginSettings.contains(pluginName)) {
+ throw MyException(tr("attempt to store setting for unknown plugin \"%1\"").arg(pluginName));
+ }
+ m_Settings.setValue("PluginPersistance/" + pluginName + "/" + key, value);
+ if (sync) {
+ m_Settings.sync();
+ }
+}
+
+QString Settings::language()
+{
+ QString result = m_Settings.value("Settings/language", "").toString();
+ 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::updateServers(const QList<ServerInfo> &servers)
+{
+ m_Settings.beginGroup("Servers");
+ QStringList oldServerKeys = m_Settings.childKeys();
+
+ foreach (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;
+
+ m_Settings.setValue(server.name, data);
+ }
+ }
+
+ m_Settings.endGroup();
+ m_Settings.sync();
+}
+
+void Settings::addBlacklistPlugin(const QString &fileName)
+{
+ m_PluginBlacklist.insert(fileName);
+ writePluginBlacklist();
+}
+
+void Settings::writePluginBlacklist()
+{
+ m_Settings.beginWriteArray("pluginBlacklist");
+ int idx = 0;
+ foreach (const QString &plugin, m_PluginBlacklist) {
+ m_Settings.setArrayIndex(idx++);
+ m_Settings.setValue("name", plugin);
+ }
+
+ m_Settings.endArray();
+}
+
+void Settings::addLanguages(QComboBox *languageBox)
+{
+ languageBox->addItem("English", "en_US");
+
+ 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 = QLocale::languageToString(locale.language());
+ if (locale.language() == QLocale::Chinese) {
+ if (languageCode == "zh_TW") {
+ languageString = "Chinese (traditional)";
+ } else {
+ languageString = "Chinese (simplified)";
+ }
+ }
+ languageBox->addItem(QString("%1").arg(languageString), exp.cap(1));
+ }
+ }
+}
+
+void Settings::addStyles(QComboBox *styleBox)
+{
+ styleBox->addItem("None", "");
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+ styleBox->addItem("Fusion", "Fusion");
+#else
+ styleBox->addItem("Plastique", "Plastique");
+ styleBox->addItem("Cleanlooks", "Cleanlooks");
+#endif
+
+ QDirIterator langIter(QCoreApplication::applicationDirPath() + "/" + ToQString(AppConfig::stylesheetsPath()), QStringList("*.qss"), QDir::Files);
+ while (langIter.hasNext()) {
+ langIter.next();
+ QString style = langIter.fileName();
+ styleBox->addItem(style, style);
+ }
+}
+
+bool Settings::isNXMHandler(bool *modifyable)
+{
+ QSettings handlerReg("HKEY_CURRENT_USER\\Software\\Classes\\nxm\\shell\\open\\command",
+ QSettings::NativeFormat);
+
+ QString currentExe = handlerReg.value("Default", "").toString().toUtf8().constData();
+ QString myExe = QString("\"%1\" ").arg(QDir::toNativeSeparators(QCoreApplication::applicationFilePath())).append("\"%1\"");
+ if (modifyable != NULL) {
+ handlerReg.setValue("Default", currentExe);
+ handlerReg.sync();
+
+ *modifyable = handlerReg.status() == QSettings::NoError;
+ // QSettings::isWritable returns wrong results...
+ }
+ return currentExe == myExe;
+}
+
+
+void Settings::setNXMHandlerActive(bool active, bool writable)
+{
+// QSettings handlerReg("HKEY_CLASSES_ROOT\\nxm\\", QSettings::NativeFormat);
+ QSettings handlerReg("HKEY_CURRENT_USER\\Software\\Classes\\nxm\\", QSettings::NativeFormat);
+
+ if (writable) {
+ QString myExe = QString("\"%1\" ").arg(QDir::toNativeSeparators(QCoreApplication::applicationFilePath())).append("\"%1\"");
+ handlerReg.setValue("Default", "URL:NXM Protocol");
+ handlerReg.setValue("URL Protocol", "");
+ handlerReg.setValue("shell/open/command/Default", active ? myExe : "");
+ handlerReg.sync();
+ } else {
+ Helper::setNXMHandler(GameInfo::instance().getOrganizerDirectory(), active);
+ }
+}
+
+
+void Settings::resetDialogs()
+{
+ m_Settings.beginGroup("DialogChoices");
+ QStringList keys = m_Settings.childKeys();
+ foreach (QString key, keys) {
+ m_Settings.remove(key);
+ }
+
+ m_Settings.endGroup();
+}
+
+
+void Settings::query(QWidget *parent)
+{
+ SettingsDialog dialog(parent);
+
+ connect(&dialog, SIGNAL(resetDialogs()), this, SLOT(resetDialogs()));
+
+ // General Page
+ QComboBox *languageBox = dialog.findChild<QComboBox*>("languageBox");
+ QComboBox *styleBox = dialog.findChild<QComboBox*>("styleBox");
+ QComboBox *logLevelBox = dialog.findChild<QComboBox*>("logLevelBox");
+ QCheckBox *compactBox = dialog.findChild<QCheckBox*>("compactBox");
+ QCheckBox *showMetaBox = dialog.findChild<QCheckBox*>("showMetaBox");
+
+ QLineEdit *downloadDirEdit = dialog.findChild<QLineEdit*>("downloadDirEdit");
+ QLineEdit *modDirEdit = dialog.findChild<QLineEdit*>("modDirEdit");
+ QLineEdit *cacheDirEdit = dialog.findChild<QLineEdit*>("cacheDirEdit");
+
+ // nexus page
+ QCheckBox *loginCheckBox = dialog.findChild<QCheckBox*>("loginCheckBox");
+ QLineEdit *usernameEdit = dialog.findChild<QLineEdit*>("usernameEdit");
+ QLineEdit *passwordEdit = dialog.findChild<QLineEdit*>("passwordEdit");
+ QCheckBox *offlineBox = dialog.findChild<QCheckBox*>("offlineBox");
+ QCheckBox *proxyBox = dialog.findChild<QCheckBox*>("proxyBox");
+
+ QListWidget *knownServersList = dialog.findChild<QListWidget*>("knownServersList");
+ QListWidget *preferredServersList = dialog.findChild<QListWidget*>("preferredServersList");
+
+ // plugis page
+ QListWidget *pluginsList = dialog.findChild<QListWidget*>("pluginsList");
+ QListWidget *pluginBlacklistList = dialog.findChild<QListWidget*>("pluginBlacklist");
+
+ // workarounds page
+ QCheckBox *forceEnableBox = dialog.findChild<QCheckBox*>("forceEnableBox");
+ QComboBox *mechanismBox = dialog.findChild<QComboBox*>("mechanismBox");
+ QLineEdit *appIDEdit = dialog.findChild<QLineEdit*>("appIDEdit");
+ QLineEdit *nmmVersionEdit = dialog.findChild<QLineEdit*>("nmmVersionEdit");
+ QCheckBox *hideUncheckedBox = dialog.findChild<QCheckBox*>("hideUncheckedBox");
+ QCheckBox *displayForeignBox = dialog.findChild<QCheckBox*>("displayForeignBox");
+
+
+ //
+ // set up current settings
+ //
+ LoadMechanism::EMechanism mechanismID = getLoadMechanism();
+ int index = 0;
+
+ if (m_LoadMechanism.isDirectLoadingSupported()) {
+ mechanismBox->addItem(QObject::tr("Mod Organizer"), LoadMechanism::LOAD_MODORGANIZER);
+ if (mechanismID == LoadMechanism::LOAD_MODORGANIZER) {
+ index = mechanismBox->count() - 1;
+ }
+ }
+
+ if (m_LoadMechanism.isScriptExtenderSupported()) {
+ mechanismBox->addItem(QObject::tr("Script Extender"), LoadMechanism::LOAD_SCRIPTEXTENDER);
+ if (mechanismID == LoadMechanism::LOAD_SCRIPTEXTENDER) {
+ index = mechanismBox->count() - 1;
+ }
+ }
+
+ if (m_LoadMechanism.isProxyDLLSupported()) {
+ mechanismBox->addItem(QObject::tr("Proxy DLL"), LoadMechanism::LOAD_PROXYDLL);
+ if (mechanismID == LoadMechanism::LOAD_PROXYDLL) {
+ index = mechanismBox->count() - 1;
+ }
+ }
+
+ mechanismBox->setCurrentIndex(index);
+
+ {
+ addLanguages(languageBox);
+ QString languageCode = language();
+ int currentID = 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 = languageBox->findData(languageCode.mid(0, 2));
+ }
+ if (currentID != -1) {
+ languageBox->setCurrentIndex(currentID);
+ }
+ }
+
+ {
+ addStyles(styleBox);
+ int currentID = styleBox->findData(m_Settings.value("Settings/style", "").toString());
+ if (currentID != -1) {
+ styleBox->setCurrentIndex(currentID);
+ }
+ }
+
+ compactBox->setChecked(compactDownloads());
+ showMetaBox->setChecked(metaDownloads());
+
+ hideUncheckedBox->setChecked(hideUncheckedPlugins());
+ displayForeignBox->setChecked(displayForeign());
+ forceEnableBox->setChecked(forceEnableCoreFiles());
+
+ appIDEdit->setText(getSteamAppID());
+
+ if (automaticLoginEnabled()) {
+ loginCheckBox->setChecked(true);
+ usernameEdit->setText(m_Settings.value("Settings/nexus_username", "").toString());
+ passwordEdit->setText(deObfuscate(m_Settings.value("Settings/nexus_password", "").toString()));
+ }
+
+ downloadDirEdit->setText(getDownloadDirectory());
+ modDirEdit->setText(getModDirectory());
+ cacheDirEdit->setText(getCacheDirectory());
+ offlineBox->setChecked(offlineMode());
+ proxyBox->setChecked(useProxy());
+ nmmVersionEdit->setText(getNMMVersion());
+ logLevelBox->setCurrentIndex(logLevel());
+
+ // display plugin settings
+ foreach (IPlugin *plugin, m_Plugins) {
+ QListWidgetItem *listItem = new QListWidgetItem(plugin->name(), pluginsList);
+ listItem->setData(Qt::UserRole, QVariant::fromValue((void*)plugin));
+ listItem->setData(Qt::UserRole + 1, m_PluginSettings[plugin->name()]);
+ pluginsList->addItem(listItem);
+ }
+
+ // display plugin blacklist
+ foreach (const QString &pluginName, m_PluginBlacklist) {
+ pluginBlacklistList->addItem(pluginName);
+ }
+
+ // display server preferences
+ m_Settings.beginGroup("Servers");
+ foreach (const QString &key, m_Settings.childKeys()) {
+ QVariantMap val = m_Settings.value(key).toMap();
+ 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<int>(val["downloadSpeed"].toDouble() / val["downloadCount"].toInt());
+ descriptor += QString(" (%1 kbps)").arg(bps / 1024);
+ }
+
+ QListWidgetItem *newItem = new QListWidgetItemEx<int>(descriptor, Qt::UserRole + 1);
+
+ newItem->setData(Qt::UserRole, key);
+ newItem->setData(Qt::UserRole + 1, val["preferred"].toInt());
+ if (val["preferred"].toInt() > 0) {
+ preferredServersList->addItem(newItem);
+ } else {
+ knownServersList->addItem(newItem);
+ }
+ preferredServersList->sortItems(Qt::DescendingOrder);
+ }
+ m_Settings.endGroup();
+
+ if (dialog.exec() == QDialog::Accepted) {
+ //
+ // transfer modified settings to configuration file
+ //
+
+ m_Settings.setValue("Settings/hide_unchecked_plugins", hideUncheckedBox->checkState() ? true : false);
+ m_Settings.setValue("Settings/force_enable_core_files", forceEnableBox->checkState() ? true : false);
+ m_Settings.setValue("Settings/compact_downloads", compactBox->isChecked());
+ m_Settings.setValue("Settings/meta_downloads", showMetaBox->isChecked());
+ m_Settings.setValue("Settings/load_mechanism", mechanismBox->itemData(mechanismBox->currentIndex()).toInt());
+
+
+ { // advanced settings
+ if ((QDir::fromNativeSeparators(modDirEdit->text()) != QDir::fromNativeSeparators(getModDirectory())) &&
+ (QMessageBox::question(NULL, 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)) {
+ modDirEdit->setText(getModDirectory());
+ }
+
+ if (!QDir(downloadDirEdit->text()).exists()) {
+ QDir().mkpath(downloadDirEdit->text());
+ }
+ if (!QDir(cacheDirEdit->text()).exists()) {
+ QDir().mkpath(cacheDirEdit->text());
+ }
+ if (!QDir(modDirEdit->text()).exists()) {
+ QDir().mkpath(modDirEdit->text());
+ }
+
+ m_Settings.setValue("Settings/download_directory", QDir::toNativeSeparators(downloadDirEdit->text()));
+ m_Settings.setValue("Settings/cache_directory", QDir::toNativeSeparators(cacheDirEdit->text()));
+ m_Settings.setValue("Settings/mod_directory", QDir::toNativeSeparators(modDirEdit->text()));
+ }
+
+
+ QString oldLanguage = m_Settings.value("Settings/language", "en_US").toString();
+ QString newLanguage = languageBox->itemData(languageBox->currentIndex()).toString();
+ if (newLanguage != oldLanguage) {
+ m_Settings.setValue("Settings/language", newLanguage);
+ emit languageChanged(newLanguage);
+ }
+
+ QString oldStyle = m_Settings.value("Settings/style", "").toString();
+ QString newStyle = styleBox->itemData(styleBox->currentIndex()).toString();
+ if (oldStyle != newStyle) {
+ m_Settings.setValue("Settings/style", newStyle);
+ emit styleChanged(newStyle);
+ }
+
+ m_Settings.setValue("Settings/log_level", logLevelBox->currentIndex());
+
+ if (appIDEdit->text() != ToQString(GameInfo::instance().getSteamAPPId())) {
+ m_Settings.setValue("Settings/app_id", appIDEdit->text());
+ } else {
+ m_Settings.remove("Settings/app_id");
+ }
+ if (loginCheckBox->isChecked()) {
+ m_Settings.setValue("Settings/nexus_login", true);
+ m_Settings.setValue("Settings/nexus_username", usernameEdit->text());
+ m_Settings.setValue("Settings/nexus_password", obfuscate(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", offlineBox->isChecked());
+ m_Settings.setValue("Settings/use_proxy", proxyBox->isChecked());
+ m_Settings.setValue("Settings/display_foreign", displayForeignBox->isChecked());
+
+ m_Settings.setValue("Settings/nmm_version", nmmVersionEdit->text());
+
+ // transfer plugin settings to in-memory structure
+ for (int i = 0; i < pluginsList->count(); ++i) {
+ QListWidgetItem *item = pluginsList->item(i);
+ m_PluginSettings[item->text()] = item->data(Qt::UserRole + 1).toMap();
+ }
+ // store plugin settings on disc
+ for (auto iterPlugins = m_PluginSettings.begin(); iterPlugins != 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_PluginBlacklist.clear();
+ foreach (QListWidgetItem *item, pluginBlacklistList->findItems("*", Qt::MatchWildcard)) {
+ m_PluginBlacklist.insert(item->text());
+ }
+ writePluginBlacklist();
+
+ // store server preference
+ m_Settings.beginGroup("Servers");
+ for (int i = 0; i < knownServersList->count(); ++i) {
+ QString key = knownServersList->item(i)->data(Qt::UserRole).toString();
+ QVariantMap val = m_Settings.value(key).toMap();
+ val["preferred"] = 0;
+ m_Settings.setValue(key, val);
+ }
+ int count = preferredServersList->count();
+ for (int i = 0; i < count; ++i) {
+ QString key = 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();
+ }
+}
diff --git a/src/settings.h b/src/settings.h index 0b051831..44e8088e 100644 --- a/src/settings.h +++ b/src/settings.h @@ -1,313 +1,313 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef WORKAROUNDS_H -#define WORKAROUNDS_H - -#include "loadmechanism.h" -#include "serverinfo.h" -#include <iplugin.h> - -#include <QSettings> -#include <QListWidget> -#include <QComboBox> - - -/** - * 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 -{ - - Q_OBJECT - -public: - - /** - * @brief constructor - **/ - Settings(); - - 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<MOBase::IPlugin*> &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; - - /** - * retrieve the directory where downloads are stored (with native separators) - **/ - QString getDownloadDirectory() const; - - /** - * retrieve a sorted list of preferred servers - */ - std::map<QString, int> getPreferredServers(); - - /** - * retrieve the directory where mods are stored (with native separators) - **/ - QString getModDirectory() 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() 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; - - /** - * @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; - - /** - * @brief set the nexus login information - * - * @param username username - * @param password password - */ - void setNexusLogin(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(); - - /** - * @return true if the user wants to see non-official plugins installed outside MO in his mod list - */ - bool displayForeign(); - - /** - * @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<ServerInfo> &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<MOBase::IPlugin*> plugins() const { return m_Plugins; } - - /** - * @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); - -private: - - QString obfuscate(const QString &password) const; - QString deObfuscate(const QString &password) const; - - void addLanguages(QComboBox *languageBox); - void addStyles(QComboBox *styleBox); - bool isNXMHandler(bool *modifyable); - void setNXMHandlerActive(bool active, bool writable); - void readPluginBlacklist(); - void writePluginBlacklist(); - -private slots: - - void resetDialogs(); - -signals: - - void languageChanged(const QString &newLanguage); - void styleChanged(const QString &newStyle); - -private: - - static Settings *s_Instance; - - QSettings m_Settings; - - LoadMechanism m_LoadMechanism; - - std::vector<MOBase::IPlugin*> m_Plugins; - - QMap<QString, QMap<QString, QVariant> > m_PluginSettings; - - QSet<QString> m_PluginBlacklist; - -}; - -#endif // WORKAROUNDS_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef WORKAROUNDS_H
+#define WORKAROUNDS_H
+
+#include "loadmechanism.h"
+#include "serverinfo.h"
+#include <iplugin.h>
+
+#include <QSettings>
+#include <QListWidget>
+#include <QComboBox>
+
+
+/**
+ * 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
+{
+
+ Q_OBJECT
+
+public:
+
+ /**
+ * @brief constructor
+ **/
+ Settings();
+
+ 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<MOBase::IPlugin*> &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;
+
+ /**
+ * retrieve the directory where downloads are stored (with native separators)
+ **/
+ QString getDownloadDirectory() const;
+
+ /**
+ * retrieve a sorted list of preferred servers
+ */
+ std::map<QString, int> getPreferredServers();
+
+ /**
+ * retrieve the directory where mods are stored (with native separators)
+ **/
+ QString getModDirectory() 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() 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;
+
+ /**
+ * @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;
+
+ /**
+ * @brief set the nexus login information
+ *
+ * @param username username
+ * @param password password
+ */
+ void setNexusLogin(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();
+
+ /**
+ * @return true if the user wants to see non-official plugins installed outside MO in his mod list
+ */
+ bool displayForeign();
+
+ /**
+ * @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<ServerInfo> &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<MOBase::IPlugin*> plugins() const { return m_Plugins; }
+
+ /**
+ * @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);
+
+private:
+
+ QString obfuscate(const QString &password) const;
+ QString deObfuscate(const QString &password) const;
+
+ void addLanguages(QComboBox *languageBox);
+ void addStyles(QComboBox *styleBox);
+ bool isNXMHandler(bool *modifyable);
+ void setNXMHandlerActive(bool active, bool writable);
+ void readPluginBlacklist();
+ void writePluginBlacklist();
+
+private slots:
+
+ void resetDialogs();
+
+signals:
+
+ void languageChanged(const QString &newLanguage);
+ void styleChanged(const QString &newStyle);
+
+private:
+
+ static Settings *s_Instance;
+
+ QSettings m_Settings;
+
+ LoadMechanism m_LoadMechanism;
+
+ std::vector<MOBase::IPlugin*> m_Plugins;
+
+ QMap<QString, QMap<QString, QVariant> > m_PluginSettings;
+
+ QSet<QString> m_PluginBlacklist;
+
+};
+
+#endif // WORKAROUNDS_H
diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp index bf1f95dc..a7d44d72 100644 --- a/src/settingsdialog.cpp +++ b/src/settingsdialog.cpp @@ -1,173 +1,173 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "settingsdialog.h" -#include "ui_settingsdialog.h" -#include "categoriesdialog.h" -#include "helper.h" -#include "noeditdelegate.h" -#include <gameinfo.h> -#include <QDirIterator> -#include <QFileDialog> -#include <QMessageBox> -#include <QShortcut> -#define WIN32_LEAN_AND_MEAN -#include <Windows.h> -#include "settings.h" - - -using namespace MOBase; -using namespace MOShared; - - -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() -{ - delete ui; -} - -void SettingsDialog::addPlugins(const std::vector<IPlugin*> &plugins) -{ - foreach (IPlugin *plugin, plugins) { - ui->pluginsList->addItem(plugin->name()); - } -} - -void SettingsDialog::accept() -{ - storeSettings(ui->pluginsList->currentItem()); - TutorableDialog::accept(); -} - - -void SettingsDialog::on_loginCheckBox_toggled(bool checked) -{ - QLineEdit *usernameEdit = findChild<QLineEdit*>("usernameEdit"); - QLineEdit *passwordEdit = findChild<QLineEdit*>("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_bsaDateBtn_clicked() -{ - Helper::backdateBSAs(GameInfo::instance().getOrganizerDirectory(), GameInfo::instance().getGameDirectory().append(L"\\data")); -} - -void SettingsDialog::on_browseDownloadDirBtn_clicked() -{ - QString temp = QFileDialog::getExistingDirectory(this, tr("Select download directory"), ui->downloadDirEdit->text()); - if (!temp.isEmpty()) { - ui->downloadDirEdit->setText(temp); - } -} - -void SettingsDialog::on_browseModDirBtn_clicked() -{ - QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), ui->downloadDirEdit->text()); - if (!temp.isEmpty()) { - ui->modDirEdit->setText(temp); - } -} - -void SettingsDialog::on_browseCacheDirBtn_clicked() -{ - QString temp = QFileDialog::getExistingDirectory(this, tr("Select cache directory"), ui->cacheDirEdit->text()); - if (!temp.isEmpty()) { - ui->cacheDirEdit->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::storeSettings(QListWidgetItem *pluginItem) -{ - if (pluginItem != NULL) { - QMap<QString, QVariant> 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); - } -} - -void SettingsDialog::on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous) -{ - storeSettings(previous); - - ui->pluginSettingsList->clear(); - IPlugin *plugin = static_cast<IPlugin*>(current->data(Qt::UserRole).value<void*>()); - ui->authorLabel->setText(plugin->author()); - ui->versionLabel->setText(plugin->version().canonicalString()); - ui->descriptionLabel->setText(plugin->description()); - - QMap<QString, QVariant> settings = current->data(Qt::UserRole + 1).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; - - ui->pluginSettingsList->setItemDelegateForColumn(0, new NoEditDelegate()); - newItem->setData(1, Qt::DisplayRole, value); - newItem->setData(1, Qt::EditRole, value); - - newItem->setFlags(newItem->flags() | Qt::ItemIsEditable); - ui->pluginSettingsList->addTopLevelItem(newItem); - } -} - -void SettingsDialog::deleteBlacklistItem() -{ - ui->pluginBlacklist->takeItem(ui->pluginBlacklist->currentIndex().row()); -} - -void SettingsDialog::on_associateButton_clicked() -{ - Settings::instance().registerAsNXMHandler(true); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "settingsdialog.h"
+#include "ui_settingsdialog.h"
+#include "categoriesdialog.h"
+#include "helper.h"
+#include "noeditdelegate.h"
+#include <gameinfo.h>
+#include <QDirIterator>
+#include <QFileDialog>
+#include <QMessageBox>
+#include <QShortcut>
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include "settings.h"
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+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()
+{
+ delete ui;
+}
+
+void SettingsDialog::addPlugins(const std::vector<IPlugin*> &plugins)
+{
+ foreach (IPlugin *plugin, plugins) {
+ ui->pluginsList->addItem(plugin->name());
+ }
+}
+
+void SettingsDialog::accept()
+{
+ storeSettings(ui->pluginsList->currentItem());
+ TutorableDialog::accept();
+}
+
+
+void SettingsDialog::on_loginCheckBox_toggled(bool checked)
+{
+ QLineEdit *usernameEdit = findChild<QLineEdit*>("usernameEdit");
+ QLineEdit *passwordEdit = findChild<QLineEdit*>("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_bsaDateBtn_clicked()
+{
+ Helper::backdateBSAs(GameInfo::instance().getOrganizerDirectory(), GameInfo::instance().getGameDirectory().append(L"\\data"));
+}
+
+void SettingsDialog::on_browseDownloadDirBtn_clicked()
+{
+ QString temp = QFileDialog::getExistingDirectory(this, tr("Select download directory"), ui->downloadDirEdit->text());
+ if (!temp.isEmpty()) {
+ ui->downloadDirEdit->setText(temp);
+ }
+}
+
+void SettingsDialog::on_browseModDirBtn_clicked()
+{
+ QString temp = QFileDialog::getExistingDirectory(this, tr("Select mod directory"), ui->downloadDirEdit->text());
+ if (!temp.isEmpty()) {
+ ui->modDirEdit->setText(temp);
+ }
+}
+
+void SettingsDialog::on_browseCacheDirBtn_clicked()
+{
+ QString temp = QFileDialog::getExistingDirectory(this, tr("Select cache directory"), ui->cacheDirEdit->text());
+ if (!temp.isEmpty()) {
+ ui->cacheDirEdit->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::storeSettings(QListWidgetItem *pluginItem)
+{
+ if (pluginItem != NULL) {
+ QMap<QString, QVariant> 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);
+ }
+}
+
+void SettingsDialog::on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous)
+{
+ storeSettings(previous);
+
+ ui->pluginSettingsList->clear();
+ IPlugin *plugin = static_cast<IPlugin*>(current->data(Qt::UserRole).value<void*>());
+ ui->authorLabel->setText(plugin->author());
+ ui->versionLabel->setText(plugin->version().canonicalString());
+ ui->descriptionLabel->setText(plugin->description());
+
+ QMap<QString, QVariant> settings = current->data(Qt::UserRole + 1).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;
+
+ ui->pluginSettingsList->setItemDelegateForColumn(0, new NoEditDelegate());
+ newItem->setData(1, Qt::DisplayRole, value);
+ newItem->setData(1, Qt::EditRole, value);
+
+ newItem->setFlags(newItem->flags() | Qt::ItemIsEditable);
+ ui->pluginSettingsList->addTopLevelItem(newItem);
+ }
+}
+
+void SettingsDialog::deleteBlacklistItem()
+{
+ ui->pluginBlacklist->takeItem(ui->pluginBlacklist->currentIndex().row());
+}
+
+void SettingsDialog::on_associateButton_clicked()
+{
+ Settings::instance().registerAsNXMHandler(true);
+}
diff --git a/src/settingsdialog.h b/src/settingsdialog.h index 0bd9fd23..e99fb3e3 100644 --- a/src/settingsdialog.h +++ b/src/settingsdialog.h @@ -1,84 +1,84 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef WORKAROUNDDIALOG_H -#define WORKAROUNDDIALOG_H - -#include "tutorabledialog.h" -#include <iplugin.h> -#include <QDialog> -#include <QListWidgetItem> - -namespace Ui { - class SettingsDialog; -} - -/** - * dialog used to change settings for Mod Organizer. On top of the - * settings managed by the "Settings" class, this offers a button to open the - * CategoriesDialog - **/ -class SettingsDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - explicit SettingsDialog(QWidget *parent = 0); - ~SettingsDialog(); - - void addPlugins(const std::vector<MOBase::IPlugin*> &plugins); - -public slots: - - virtual void accept(); - -signals: - - void resetDialogs(); - -private: - - void storeSettings(QListWidgetItem *pluginItem); - -private slots: - void on_loginCheckBox_toggled(bool checked); - - void on_categoriesBtn_clicked(); - - void on_bsaDateBtn_clicked(); - - void on_browseDownloadDirBtn_clicked(); - - void on_browseModDirBtn_clicked(); - - void on_browseCacheDirBtn_clicked(); - - void on_resetDialogsButton_clicked(); - - void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous); - - void deleteBlacklistItem(); - - void on_associateButton_clicked(); - -private: - Ui::SettingsDialog *ui; -}; - -#endif // WORKAROUNDDIALOG_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef WORKAROUNDDIALOG_H
+#define WORKAROUNDDIALOG_H
+
+#include "tutorabledialog.h"
+#include <iplugin.h>
+#include <QDialog>
+#include <QListWidgetItem>
+
+namespace Ui {
+ class SettingsDialog;
+}
+
+/**
+ * dialog used to change settings for Mod Organizer. On top of the
+ * settings managed by the "Settings" class, this offers a button to open the
+ * CategoriesDialog
+ **/
+class SettingsDialog : public MOBase::TutorableDialog
+{
+ Q_OBJECT
+
+public:
+ explicit SettingsDialog(QWidget *parent = 0);
+ ~SettingsDialog();
+
+ void addPlugins(const std::vector<MOBase::IPlugin*> &plugins);
+
+public slots:
+
+ virtual void accept();
+
+signals:
+
+ void resetDialogs();
+
+private:
+
+ void storeSettings(QListWidgetItem *pluginItem);
+
+private slots:
+ void on_loginCheckBox_toggled(bool checked);
+
+ void on_categoriesBtn_clicked();
+
+ void on_bsaDateBtn_clicked();
+
+ void on_browseDownloadDirBtn_clicked();
+
+ void on_browseModDirBtn_clicked();
+
+ void on_browseCacheDirBtn_clicked();
+
+ void on_resetDialogsButton_clicked();
+
+ void on_pluginsList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
+
+ void deleteBlacklistItem();
+
+ void on_associateButton_clicked();
+
+private:
+ Ui::SettingsDialog *ui;
+};
+
+#endif // WORKAROUNDDIALOG_H
diff --git a/src/shared/appconfig.cpp b/src/shared/appconfig.cpp index 3f96bff1..f1bad828 100644 --- a/src/shared/appconfig.cpp +++ b/src/shared/appconfig.cpp @@ -30,4 +30,4 @@ namespace MOShared { #undef APPPARAM
}
-} // namespace MOShared +} // namespace MOShared
diff --git a/src/shared/inject.h b/src/shared/inject.h index d5030363..7f4f2962 100644 --- a/src/shared/inject.h +++ b/src/shared/inject.h @@ -1,31 +1,31 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#pragma once - -#define WIN32_LEAN_AND_MEAN -#include <windows.h> - -#include <string> - -namespace MOShared { - -void injectDLL(HANDLE processHandle, HANDLE threadHandle, const std::string &dllname, const std::wstring &profileName, int logLevel); - -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+
+#include <string>
+
+namespace MOShared {
+
+void injectDLL(HANDLE processHandle, HANDLE threadHandle, const std::string &dllname, const std::wstring &profileName, int logLevel);
+
+}
diff --git a/src/singleinstance.cpp b/src/singleinstance.cpp index 2befab70..a1928523 100644 --- a/src/singleinstance.cpp +++ b/src/singleinstance.cpp @@ -1,107 +1,107 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "singleinstance.h" -#include "report.h" -#include "utility.h" -#include <QLocalSocket> - -static const char s_Key[] = "mo-43d1a3ad-eeb0-4818-97c9-eda5216c29b5"; -static const int s_Timeout = 5000; - - -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_PrimaryInstance) { - connect(&m_Server, SIGNAL(newConnection()), this, SLOT(receiveMessage())); - 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); - } - - // other instance may be just starting up - socket.connectToServer(s_Key, QIODevice::WriteOnly); - connected = socket.waitForConnected(s_Timeout); - } - - if (!connected) { - reportError(tr("failed to connect to running instance: %1").arg(socket.errorString())); - return; - } - - 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(); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "singleinstance.h"
+#include "report.h"
+#include "utility.h"
+#include <QLocalSocket>
+
+static const char s_Key[] = "mo-43d1a3ad-eeb0-4818-97c9-eda5216c29b5";
+static const int s_Timeout = 5000;
+
+
+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_PrimaryInstance) {
+ connect(&m_Server, SIGNAL(newConnection()), this, SLOT(receiveMessage()));
+ 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);
+ }
+
+ // other instance may be just starting up
+ socket.connectToServer(s_Key, QIODevice::WriteOnly);
+ connected = socket.waitForConnected(s_Timeout);
+ }
+
+ if (!connected) {
+ reportError(tr("failed to connect to running instance: %1").arg(socket.errorString()));
+ return;
+ }
+
+ 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();
+}
diff --git a/src/singleinstance.h b/src/singleinstance.h index cc95bf48..14d49d6d 100644 --- a/src/singleinstance.h +++ b/src/singleinstance.h @@ -1,22 +1,22 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
#ifndef SINGLEINSTANCE_H
#define SINGLEINSTANCE_H
diff --git a/src/spawn.cpp b/src/spawn.cpp index d314a326..8cc5abe1 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -1,211 +1,211 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "spawn.h" -#include "report.h" -#include "utility.h" -#include <boost/scoped_array.hpp> -#include <gameinfo.h> -#include <inject.h> -#include <Shellapi.h> -#include <appconfig.h> -#include <windows_error.h> -#include <QApplication> -#include <QMessageBox> - - -using namespace MOBase; -using namespace MOShared; - - -static const int BUFSIZE = 4096; - -bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool suspended, - 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); - int length = wcslen(binary) + wcslen(arguments) + 4; - wchar_t *commandLine = NULL; - 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(commandLine, length, L"\"%ls\"", binary); - } - - QString moPath = QCoreApplication::applicationDirPath(); - - boost::scoped_array<TCHAR> 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<TCHAR> newPath(new TCHAR[offset + moPath.length() + 2]); - _tcsncpy(newPath.get(), oldPath.get(), offset - 1); - newPath.get()[offset - 1] = L'\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 = ::CreateProcess(NULL, - commandLine, - NULL, NULL, // no special process or thread attributes - inheritHandles, // inherit handles if we plan to use stdout or stderr reroute - CREATE_BREAKAWAY_FROM_JOB | (suspended ? CREATE_SUSPENDED : 0), // create suspended so I have time to inject the DLL - NULL, // 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 QString& profileName, - int logLevel, - const QDir ¤tDirectory, - bool hooked, - HANDLE stdOut, - HANDLE stdErr) -{ - HANDLE jobObject = ::CreateJobObject(NULL, NULL); - - if (jobObject == NULL) { - qWarning("failed to create job object: %lu", ::GetLastError()); - } - - 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, - 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(NULL, 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(NULL, 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; - } - } - - if (hooked) { - try { - QFileInfo dllInfo(QApplication::applicationDirPath() + "/" + ToQString(AppConfig::hookDLLName())); - if (!dllInfo.exists()) { - reportError(QObject::tr("\"%1\" doesn't exist").arg(dllInfo.fileName())); - return INVALID_HANDLE_VALUE; - } - injectDLL(processHandle, threadHandle, - QDir::toNativeSeparators(dllInfo.canonicalFilePath()).toLocal8Bit().constData(), - ToWString(profileName).c_str(), logLevel); - } catch (const windows_error& e) { - reportError(QObject::tr("failed to inject dll into \"%1\": %2").arg(binary.fileName()).arg(e.what())); - ::TerminateProcess(processHandle, 1); - return INVALID_HANDLE_VALUE; - } -#ifdef _DEBUG - reportError("ready?"); -#endif // DEBUG - } - - if (::AssignProcessToJobObject(jobObject, processHandle) == 0) { - qWarning("failed to assign to job object: %lu", ::GetLastError()); - ::CloseHandle(jobObject); - jobObject = processHandle; - } else { - ::CloseHandle(processHandle); - } - - if (::ResumeThread(threadHandle) == (DWORD)-1) { - reportError(QObject::tr("failed to run \"%1\"").arg(binary.fileName())); - return INVALID_HANDLE_VALUE; - } - ::CloseHandle(threadHandle); - return jobObject; -} - -/* -ExitProxy *ExitProxy::s_Instance = NULL; - -ExitProxy *ExitProxy::instance() -{ - if (s_Instance == NULL) { - s_Instance = new ExitProxy(); - } - return s_Instance; -} - -void ExitProxy::emitExit() -{ - emit exit(); -}*/ +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "spawn.h"
+#include "report.h"
+#include "utility.h"
+#include <boost/scoped_array.hpp>
+#include <gameinfo.h>
+#include <inject.h>
+#include <Shellapi.h>
+#include <appconfig.h>
+#include <windows_error.h>
+#include <QApplication>
+#include <QMessageBox>
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+static const int BUFSIZE = 4096;
+
+bool spawn(LPCWSTR binary, LPCWSTR arguments, LPCWSTR currentDirectory, bool suspended,
+ 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);
+ int length = wcslen(binary) + wcslen(arguments) + 4;
+ wchar_t *commandLine = NULL;
+ 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(commandLine, length, L"\"%ls\"", binary);
+ }
+
+ QString moPath = QCoreApplication::applicationDirPath();
+
+ boost::scoped_array<TCHAR> 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<TCHAR> newPath(new TCHAR[offset + moPath.length() + 2]);
+ _tcsncpy(newPath.get(), oldPath.get(), offset - 1);
+ newPath.get()[offset - 1] = L'\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 = ::CreateProcess(NULL,
+ commandLine,
+ NULL, NULL, // no special process or thread attributes
+ inheritHandles, // inherit handles if we plan to use stdout or stderr reroute
+ CREATE_BREAKAWAY_FROM_JOB | (suspended ? CREATE_SUSPENDED : 0), // create suspended so I have time to inject the DLL
+ NULL, // 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 QString& profileName,
+ int logLevel,
+ const QDir ¤tDirectory,
+ bool hooked,
+ HANDLE stdOut,
+ HANDLE stdErr)
+{
+ HANDLE jobObject = ::CreateJobObject(NULL, NULL);
+
+ if (jobObject == NULL) {
+ qWarning("failed to create job object: %lu", ::GetLastError());
+ }
+
+ 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,
+ 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(NULL, 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(NULL, 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;
+ }
+ }
+
+ if (hooked) {
+ try {
+ QFileInfo dllInfo(QApplication::applicationDirPath() + "/" + ToQString(AppConfig::hookDLLName()));
+ if (!dllInfo.exists()) {
+ reportError(QObject::tr("\"%1\" doesn't exist").arg(dllInfo.fileName()));
+ return INVALID_HANDLE_VALUE;
+ }
+ injectDLL(processHandle, threadHandle,
+ QDir::toNativeSeparators(dllInfo.canonicalFilePath()).toLocal8Bit().constData(),
+ ToWString(profileName).c_str(), logLevel);
+ } catch (const windows_error& e) {
+ reportError(QObject::tr("failed to inject dll into \"%1\": %2").arg(binary.fileName()).arg(e.what()));
+ ::TerminateProcess(processHandle, 1);
+ return INVALID_HANDLE_VALUE;
+ }
+#ifdef _DEBUG
+ reportError("ready?");
+#endif // DEBUG
+ }
+
+ if (::AssignProcessToJobObject(jobObject, processHandle) == 0) {
+ qWarning("failed to assign to job object: %lu", ::GetLastError());
+ ::CloseHandle(jobObject);
+ jobObject = processHandle;
+ } else {
+ ::CloseHandle(processHandle);
+ }
+
+ if (::ResumeThread(threadHandle) == (DWORD)-1) {
+ reportError(QObject::tr("failed to run \"%1\"").arg(binary.fileName()));
+ return INVALID_HANDLE_VALUE;
+ }
+ ::CloseHandle(threadHandle);
+ return jobObject;
+}
+
+/*
+ExitProxy *ExitProxy::s_Instance = NULL;
+
+ExitProxy *ExitProxy::instance()
+{
+ if (s_Instance == NULL) {
+ s_Instance = new ExitProxy();
+ }
+ return s_Instance;
+}
+
+void ExitProxy::emitExit()
+{
+ emit exit();
+}*/
diff --git a/src/spawn.h b/src/spawn.h index 3f037119..63eda18d 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -1,68 +1,68 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef SPAWN_H -#define SPAWN_H - -#define WIN32_LEAN_AND_MEAN -#include <windows.h> -#include <tchar.h> -#include <QFileInfo> -#include <QDir> - - -/** - * @brief a dirty little trick so we can issue a clean restart from startBinary - * @note unused - */ -/*class ExitProxy : public QObject { - Q_OBJECT -public: - static ExitProxy *instance(); - void emitExit(); -signals: - void exit(); -private: - ExitProxy() {} -private: - static ExitProxy *s_Instance; -};*/ - - -/** - * @brief spawn a binary with Mod Organizer injected - * - * @param binary the binary to spawn - * @param arguments arguments to pass to the binary - * @param profileName name of the active profile - * @param currentDirectory the directory to use as the working directory to run in - * @param logLevel log level to be used by the hook library. Ignored if hooked is false - * @param hooked if set, the binary is started with mo injected - * @param stdout if not equal to INVALID_HANDLE_VALUE, this is used as stdout for the process - * @param stderr if not equal to INVALID_HANDLE_VALUE, this is used as stderr for the process - * @return the process handle - * @todo is the profile name even used any more? - * @todo is the hooked parameter used? - **/ -HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString &profileName, int logLevel, - const QDir ¤tDirectory, bool hooked, - HANDLE stdOut = INVALID_HANDLE_VALUE, HANDLE stdErr = INVALID_HANDLE_VALUE); - -#endif // SPAWN_H - +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef SPAWN_H
+#define SPAWN_H
+
+#define WIN32_LEAN_AND_MEAN
+#include <windows.h>
+#include <tchar.h>
+#include <QFileInfo>
+#include <QDir>
+
+
+/**
+ * @brief a dirty little trick so we can issue a clean restart from startBinary
+ * @note unused
+ */
+/*class ExitProxy : public QObject {
+ Q_OBJECT
+public:
+ static ExitProxy *instance();
+ void emitExit();
+signals:
+ void exit();
+private:
+ ExitProxy() {}
+private:
+ static ExitProxy *s_Instance;
+};*/
+
+
+/**
+ * @brief spawn a binary with Mod Organizer injected
+ *
+ * @param binary the binary to spawn
+ * @param arguments arguments to pass to the binary
+ * @param profileName name of the active profile
+ * @param currentDirectory the directory to use as the working directory to run in
+ * @param logLevel log level to be used by the hook library. Ignored if hooked is false
+ * @param hooked if set, the binary is started with mo injected
+ * @param stdout if not equal to INVALID_HANDLE_VALUE, this is used as stdout for the process
+ * @param stderr if not equal to INVALID_HANDLE_VALUE, this is used as stderr for the process
+ * @return the process handle
+ * @todo is the profile name even used any more?
+ * @todo is the hooked parameter used?
+ **/
+HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString &profileName, int logLevel,
+ 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 32b76c97..380719d8 100644 --- a/src/syncoverwritedialog.cpp +++ b/src/syncoverwritedialog.cpp @@ -1,166 +1,166 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "syncoverwritedialog.h" -#include "ui_syncoverwritedialog.h" -#include <utility.h> -#include <report.h> -#include <gameinfo.h> -#include <QDir> -#include <QDirIterator> -#include <QComboBox> -#include <QStringList> - - -using namespace MOBase; -using namespace MOShared; - - -SyncOverwriteDialog::SyncOverwriteDialog(const QString &path, DirectoryEntry *directoryStructure, QWidget *parent) - : TutorableDialog("SyncOverwrite", parent), - ui(new Ui::SyncOverwriteDialog), m_SourcePath(path), m_DirectoryStructure(directoryStructure) -{ - ui->setupUi(this); - refresh(path); - - QHeaderView *headerView = ui->syncTree->header(); -#if QT_VERSION >= QT_VERSION_CHECK(5,0,0) - headerView->setSectionResizeMode(0, QHeaderView::Stretch); - headerView->setSectionResizeMode(1, QHeaderView::Interactive); -#else - headerView->setResizeMode(0, QHeaderView::Stretch); - headerView->setResizeMode(1, QHeaderView::Interactive); -#endif -} - - -SyncOverwriteDialog::~SyncOverwriteDialog() -{ - delete ui; -} - - -static void addToComboBox(QComboBox *box, const QString &name, const QVariant &userData) -{ - if (QString::compare(name, "overwrite", Qt::CaseInsensitive) != 0) { - box->addItem(name, userData); - } -} - - -void SyncOverwriteDialog::readTree(const QString &path, DirectoryEntry *directoryStructure, QTreeWidgetItem *subTree) -{ - QDir overwrite(path); - overwrite.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot); - QDirIterator dirIter(overwrite); - while (dirIter.hasNext()) { - dirIter.next(); - QFileInfo fileInfo = dirIter.fileInfo(); - - QString file = fileInfo.fileName(); - if (file == "meta.ini") { - continue; - } - - QTreeWidgetItem *newItem = new QTreeWidgetItem(subTree, QStringList(file)); - - if (fileInfo.isDir()) { - DirectoryEntry *subDir = directoryStructure->findSubDirectory(ToWString(file)); - if (subDir != NULL) { - readTree(fileInfo.absoluteFilePath(), subDir, newItem); - } else { - qCritical("no directory structure for %s?", file.toUtf8().constData()); - delete newItem; - newItem = NULL; - } - } else { - const FileEntry::Ptr entry = directoryStructure->findFile(ToWString(file)); - QComboBox* combo = new QComboBox(ui->syncTree); - combo->addItem(tr("<don't sync>"), -1); - if (entry.get() != NULL) { - bool ignore; - int origin = entry->getOrigin(ignore); - addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(origin).getName()), origin); - const std::vector<int> &alternatives = entry->getAlternatives(); - for (std::vector<int>::const_iterator iter = alternatives.begin(); iter != alternatives.end(); ++iter) { - addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(*iter).getName()), *iter); - } - combo->setCurrentIndex(combo->count() - 1); - } else { - combo->setCurrentIndex(0); - } - ui->syncTree->setItemWidget(newItem, 1, combo); - } - if (newItem != NULL) { - subTree->addChild(newItem); - } - } -} - - -void SyncOverwriteDialog::refresh(const QString &path) -{ - QTreeWidgetItem *rootItem = new QTreeWidgetItem(ui->syncTree, QStringList("<data>")); - 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.mid(0).append("/").append(child->text(0)); - } else { - filePath = child->text(0); - } - if (child->childCount() != 0) { - applyTo(child, filePath, modDirectory); - } else { - QComboBox *comboBox = qobject_cast<QComboBox*>(ui->syncTree->itemWidget(child, 1)); - if (comboBox != NULL) { - int originID = comboBox->itemData(comboBox->currentIndex(), Qt::UserRole).toInt(); - if (originID != -1) { - FilesOrigin &origin = m_DirectoryStructure->getOriginByID(originID); - QString source = m_SourcePath.mid(0).append("/").append(filePath); - QString destination = modDirectory.mid(0).append("/").append(ToQString(origin.getName())).append("/").append(filePath); - if (!QFile::remove(destination)) { - reportError(tr("failed to remove %1").arg(destination)); - } else if (!QFile::rename(source, destination)) { - reportError(tr("failed to move %1 to %2").arg(source).arg(destination)); - } - } - } - } - } - - QDir dir(m_SourcePath + "/" + path); - if ((path.length() > 0) && (dir.count() == 2)) { - dir.rmpath("."); - } -} - - -void SyncOverwriteDialog::apply(const QString &modDirectory) -{ - applyTo(ui->syncTree->topLevelItem(0), "", modDirectory); -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "syncoverwritedialog.h"
+#include "ui_syncoverwritedialog.h"
+#include <utility.h>
+#include <report.h>
+#include <gameinfo.h>
+#include <QDir>
+#include <QDirIterator>
+#include <QComboBox>
+#include <QStringList>
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+SyncOverwriteDialog::SyncOverwriteDialog(const QString &path, DirectoryEntry *directoryStructure, QWidget *parent)
+ : TutorableDialog("SyncOverwrite", parent),
+ ui(new Ui::SyncOverwriteDialog), m_SourcePath(path), m_DirectoryStructure(directoryStructure)
+{
+ ui->setupUi(this);
+ refresh(path);
+
+ QHeaderView *headerView = ui->syncTree->header();
+#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
+ headerView->setSectionResizeMode(0, QHeaderView::Stretch);
+ headerView->setSectionResizeMode(1, QHeaderView::Interactive);
+#else
+ headerView->setResizeMode(0, QHeaderView::Stretch);
+ headerView->setResizeMode(1, QHeaderView::Interactive);
+#endif
+}
+
+
+SyncOverwriteDialog::~SyncOverwriteDialog()
+{
+ delete ui;
+}
+
+
+static void addToComboBox(QComboBox *box, const QString &name, const QVariant &userData)
+{
+ if (QString::compare(name, "overwrite", Qt::CaseInsensitive) != 0) {
+ box->addItem(name, userData);
+ }
+}
+
+
+void SyncOverwriteDialog::readTree(const QString &path, DirectoryEntry *directoryStructure, QTreeWidgetItem *subTree)
+{
+ QDir overwrite(path);
+ overwrite.setFilter(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot);
+ QDirIterator dirIter(overwrite);
+ while (dirIter.hasNext()) {
+ dirIter.next();
+ QFileInfo fileInfo = dirIter.fileInfo();
+
+ QString file = fileInfo.fileName();
+ if (file == "meta.ini") {
+ continue;
+ }
+
+ QTreeWidgetItem *newItem = new QTreeWidgetItem(subTree, QStringList(file));
+
+ if (fileInfo.isDir()) {
+ DirectoryEntry *subDir = directoryStructure->findSubDirectory(ToWString(file));
+ if (subDir != NULL) {
+ readTree(fileInfo.absoluteFilePath(), subDir, newItem);
+ } else {
+ qCritical("no directory structure for %s?", file.toUtf8().constData());
+ delete newItem;
+ newItem = NULL;
+ }
+ } else {
+ const FileEntry::Ptr entry = directoryStructure->findFile(ToWString(file));
+ QComboBox* combo = new QComboBox(ui->syncTree);
+ combo->addItem(tr("<don't sync>"), -1);
+ if (entry.get() != NULL) {
+ bool ignore;
+ int origin = entry->getOrigin(ignore);
+ addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(origin).getName()), origin);
+ const std::vector<int> &alternatives = entry->getAlternatives();
+ for (std::vector<int>::const_iterator iter = alternatives.begin(); iter != alternatives.end(); ++iter) {
+ addToComboBox(combo, ToQString(m_DirectoryStructure->getOriginByID(*iter).getName()), *iter);
+ }
+ combo->setCurrentIndex(combo->count() - 1);
+ } else {
+ combo->setCurrentIndex(0);
+ }
+ ui->syncTree->setItemWidget(newItem, 1, combo);
+ }
+ if (newItem != NULL) {
+ subTree->addChild(newItem);
+ }
+ }
+}
+
+
+void SyncOverwriteDialog::refresh(const QString &path)
+{
+ QTreeWidgetItem *rootItem = new QTreeWidgetItem(ui->syncTree, QStringList("<data>"));
+ 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.mid(0).append("/").append(child->text(0));
+ } else {
+ filePath = child->text(0);
+ }
+ if (child->childCount() != 0) {
+ applyTo(child, filePath, modDirectory);
+ } else {
+ QComboBox *comboBox = qobject_cast<QComboBox*>(ui->syncTree->itemWidget(child, 1));
+ if (comboBox != NULL) {
+ int originID = comboBox->itemData(comboBox->currentIndex(), Qt::UserRole).toInt();
+ if (originID != -1) {
+ FilesOrigin &origin = m_DirectoryStructure->getOriginByID(originID);
+ QString source = m_SourcePath.mid(0).append("/").append(filePath);
+ QString destination = modDirectory.mid(0).append("/").append(ToQString(origin.getName())).append("/").append(filePath);
+ if (!QFile::remove(destination)) {
+ reportError(tr("failed to remove %1").arg(destination));
+ } else if (!QFile::rename(source, destination)) {
+ reportError(tr("failed to move %1 to %2").arg(source).arg(destination));
+ }
+ }
+ }
+ }
+ }
+
+ QDir dir(m_SourcePath + "/" + path);
+ if ((path.length() > 0) && (dir.count() == 2)) {
+ dir.rmpath(".");
+ }
+}
+
+
+void SyncOverwriteDialog::apply(const QString &modDirectory)
+{
+ applyTo(ui->syncTree->topLevelItem(0), "", modDirectory);
+}
diff --git a/src/syncoverwritedialog.h b/src/syncoverwritedialog.h index 7fbd876e..79b30a8d 100644 --- a/src/syncoverwritedialog.h +++ b/src/syncoverwritedialog.h @@ -1,55 +1,55 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef SYNCOVERWRITEDIALOG_H -#define SYNCOVERWRITEDIALOG_H - - -#include "tutorabledialog.h" -#include <QTreeWidgetItem> -#include <directoryentry.h> - - -namespace Ui { -class SyncOverwriteDialog; -} - -class SyncOverwriteDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - explicit SyncOverwriteDialog(const QString &path, MOShared::DirectoryEntry *directoryStructure, QWidget *parent = 0); - ~SyncOverwriteDialog(); - - void apply(const QString &modDirectory); -private: - void refresh(const QString &path); - void readTree(const QString &path, MOShared::DirectoryEntry *directoryStructure, QTreeWidgetItem *subTree); - void applyTo(QTreeWidgetItem *item, const QString &path, const QString &modDirectory); - -private: - - Ui::SyncOverwriteDialog *ui; - QString m_SourcePath; - MOShared::DirectoryEntry *m_DirectoryStructure; - -}; - -#endif // SYNCOVERWRITEDIALOG_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef SYNCOVERWRITEDIALOG_H
+#define SYNCOVERWRITEDIALOG_H
+
+
+#include "tutorabledialog.h"
+#include <QTreeWidgetItem>
+#include <directoryentry.h>
+
+
+namespace Ui {
+class SyncOverwriteDialog;
+}
+
+class SyncOverwriteDialog : public MOBase::TutorableDialog
+{
+ Q_OBJECT
+
+public:
+ explicit SyncOverwriteDialog(const QString &path, MOShared::DirectoryEntry *directoryStructure, QWidget *parent = 0);
+ ~SyncOverwriteDialog();
+
+ void apply(const QString &modDirectory);
+private:
+ void refresh(const QString &path);
+ void readTree(const QString &path, MOShared::DirectoryEntry *directoryStructure, QTreeWidgetItem *subTree);
+ void applyTo(QTreeWidgetItem *item, const QString &path, const QString &modDirectory);
+
+private:
+
+ Ui::SyncOverwriteDialog *ui;
+ QString m_SourcePath;
+ MOShared::DirectoryEntry *m_DirectoryStructure;
+
+};
+
+#endif // SYNCOVERWRITEDIALOG_H
diff --git a/src/transfersavesdialog.cpp b/src/transfersavesdialog.cpp index 8e2b9b59..b07b1673 100644 --- a/src/transfersavesdialog.cpp +++ b/src/transfersavesdialog.cpp @@ -1,328 +1,328 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#include "transfersavesdialog.h" -#include "ui_transfersavesdialog.h" -#include "utility.h" -#include <gameinfo.h> -#include <QDir> -#include <QMessageBox> -#include <Shlwapi.h> -#include <shlobj.h> - - -using namespace MOBase; -using namespace MOShared; - - -TransferSavesDialog::TransferSavesDialog(const Profile &profile, QWidget *parent) - : TutorableDialog("TransferSaves", parent), ui(new Ui::TransferSavesDialog), m_Profile(profile) -{ - ui->setupUi(this); - refreshGlobalSaves(); - refreshLocalSaves(); - refreshGlobalCharacters(); - refreshLocalCharacters(); -} - -TransferSavesDialog::~TransferSavesDialog() -{ - delete ui; -} - - -void TransferSavesDialog::refreshGlobalSaves() -{ - m_GlobalSaves.clear(); - - QDir savesDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir()))); - - QStringList filters; - filters << ToQString(GameInfo::instance().getSaveGameExtension()); - savesDir.setNameFilters(filters); - - QStringList files = savesDir.entryList(QDir::Files, QDir::Time); - - foreach (const QString &filename, files) { - SaveGameGamebryo *save = new SaveGameGamebryo(this, savesDir.absoluteFilePath(filename)); - save->setParent(this); - m_GlobalSaves.push_back(save); - } -} - - -void TransferSavesDialog::refreshLocalSaves() -{ - m_LocalSaves.clear(); - - QDir savesDir(m_Profile.getPath().append("/saves")); - - QStringList filters; - filters << ToQString(GameInfo::instance().getSaveGameExtension()); - savesDir.setNameFilters(filters); - - QStringList files = savesDir.entryList(QDir::Files, QDir::Time); - - foreach (const QString &filename, files) { - SaveGameGamebryo *save = new SaveGameGamebryo(this, savesDir.absoluteFilePath(filename)); - save->setParent(this); - m_LocalSaves.push_back(save); - } -} - - -void TransferSavesDialog::refreshGlobalCharacters() -{ - std::set<QString> characters; - for (std::vector<SaveGame*>::const_iterator iter = m_GlobalSaves.begin(); - iter != m_GlobalSaves.end(); ++iter) { - characters.insert((*iter)->pcName()); - } - ui->globalCharacterList->clear(); - for (std::set<QString>::const_iterator iter = characters.begin(); - iter != characters.end(); ++iter) { - ui->globalCharacterList->addItem(*iter); - } - if (ui->globalCharacterList->count() > 0) { - ui->globalCharacterList->setCurrentRow(0); - ui->copyToLocalBtn->setEnabled(true); - ui->moveToLocalBtn->setEnabled(true); - } else { - ui->copyToLocalBtn->setEnabled(false); - ui->moveToLocalBtn->setEnabled(false); - } -} - - -void TransferSavesDialog::refreshLocalCharacters() -{ - std::set<QString> characters; - for (std::vector<SaveGame*>::const_iterator iter = m_LocalSaves.begin(); - iter != m_LocalSaves.end(); ++iter) { - characters.insert((*iter)->pcName()); - } - ui->localCharacterList->clear(); - for (std::set<QString>::const_iterator iter = characters.begin(); - iter != characters.end(); ++iter) { - ui->localCharacterList->addItem(*iter); - } - if (ui->localCharacterList->count() > 0) { - ui->localCharacterList->setCurrentRow(0); - ui->copyToGlobalBtn->setEnabled(true); - ui->moveToGlobalBtn->setEnabled(true); - } else { - ui->copyToGlobalBtn->setEnabled(false); - ui->moveToGlobalBtn->setEnabled(false); - } -} - - -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; -} - -void TransferSavesDialog::on_moveToLocalBtn_clicked() -{ - QString selectedCharacter = ui->globalCharacterList->currentItem()->text(); - if (QMessageBox::question(this, tr("Confirm"), - tr("Copy all save games of character \"%1\" to the profile?").arg(selectedCharacter), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - QString destination = m_Profile.getPath().append("/saves"); - OverwriteMode overwriteMode = OVERWRITE_ASK; - - for (std::vector<SaveGame*>::const_iterator iter = m_GlobalSaves.begin(); - iter != m_GlobalSaves.end(); ++iter) { - if ((*iter)->pcName() == selectedCharacter) { - QStringList files = (*iter)->saveFiles(); - foreach (const QString &file, files) { - QFileInfo fileInfo(file); - QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); - if (QFile::exists(destinationFile)) { - if (testOverwrite(overwriteMode, destinationFile)) { - QFile::remove(destinationFile); - } else { - continue; - } - } - if (!QFile::rename(fileInfo.absoluteFilePath(), destinationFile)) { - qCritical("failed to move %s to %s", - fileInfo.absoluteFilePath().toUtf8().constData(), - destinationFile.toUtf8().constData()); - } - } - } - } - } - refreshGlobalSaves(); - refreshGlobalCharacters(); - refreshLocalSaves(); - refreshLocalCharacters(); -} - -void TransferSavesDialog::on_copyToLocalBtn_clicked() -{ - QString selectedCharacter = ui->globalCharacterList->currentItem()->text(); - if (QMessageBox::question(this, tr("Confirm"), - tr("Copy all save games of character \"%1\" to the profile?").arg(selectedCharacter), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - QString destination = m_Profile.getPath().append("/saves"); - OverwriteMode overwriteMode = OVERWRITE_ASK; - for (std::vector<SaveGame*>::const_iterator iter = m_GlobalSaves.begin(); - iter != m_GlobalSaves.end(); ++iter) { - if ((*iter)->pcName() == selectedCharacter) { - QStringList files = (*iter)->saveFiles(); - foreach (const QString &file, files) { - QFileInfo fileInfo(file); - QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); - if (QFile::exists(destinationFile)) { - if (testOverwrite(overwriteMode, destinationFile)) { - QFile::remove(destinationFile); - } else { - continue; - } - } - if (!QFile::copy(fileInfo.absoluteFilePath(), destinationFile)) { - qCritical("failed to copy %s to %s", - fileInfo.absoluteFilePath().toUtf8().constData(), - destinationFile.toUtf8().constData()); - } - } - } - } - } - refreshLocalSaves(); - refreshLocalCharacters(); -} - -void TransferSavesDialog::on_moveToGlobalBtn_clicked() -{ - QString selectedCharacter = ui->localCharacterList->currentItem()->text(); - if (QMessageBox::question(this, tr("Confirm"), - tr("Move all save games of character \"%1\" to the global location? Please be aware " - "that this will mess up the running number of save games.").arg(selectedCharacter), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - - QString destination = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir())); - OverwriteMode overwriteMode = OVERWRITE_ASK; - for (std::vector<SaveGame*>::const_iterator iter = m_LocalSaves.begin(); - iter != m_LocalSaves.end(); ++iter) { - if ((*iter)->pcName() == selectedCharacter) { - QStringList files = (*iter)->saveFiles(); - foreach (const QString &file, files) { - QFileInfo fileInfo(file); - QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); - if (QFile::exists(destinationFile)) { - if (testOverwrite(overwriteMode, destinationFile)) { - QFile::remove(destinationFile); - } else { - continue; - } - } - if (!QFile::rename(fileInfo.absoluteFilePath(), destinationFile)) { - qCritical("failed to move %s to %s", - fileInfo.absoluteFilePath().toUtf8().constData(), - destinationFile.toUtf8().constData()); - } - } - } - } - } - refreshGlobalSaves(); - refreshGlobalCharacters(); - refreshLocalSaves(); - refreshLocalCharacters(); -} - -void TransferSavesDialog::on_copyToGlobalBtn_clicked() -{ - QString selectedCharacter = ui->localCharacterList->currentItem()->text(); - if (QMessageBox::question(this, tr("Confirm"), - tr("Copy all save games of character \"%1\" to the global location? Please be aware " - "that this will mess up the running number of save games.").arg(selectedCharacter), - QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) { - - QString destination = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir())); - OverwriteMode overwriteMode = OVERWRITE_ASK; - for (std::vector<SaveGame*>::const_iterator iter = m_LocalSaves.begin(); - iter != m_LocalSaves.end(); ++iter) { - if ((*iter)->pcName() == selectedCharacter) { - QStringList files = (*iter)->saveFiles(); - foreach (const QString &file, files) { - QFileInfo fileInfo(file); - QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName()); - if (QFile::exists(destinationFile)) { - if (testOverwrite(overwriteMode, destinationFile)) { - QFile::remove(destinationFile); - } else { - continue; - } - } - if (!QFile::copy(fileInfo.absoluteFilePath(), destinationFile)) { - qCritical("failed to copy %s to %s", - fileInfo.absoluteFilePath().toUtf8().constData(), - destinationFile.toUtf8().constData()); - } - } - } - } - } - refreshGlobalSaves(); - refreshGlobalCharacters(); -} - -void TransferSavesDialog::on_doneButton_clicked() -{ - close(); -} - -void TransferSavesDialog::on_globalCharacterList_currentTextChanged(const QString ¤tText) -{ - ui->globalSavesList->clear(); - for (std::vector<SaveGame*>::const_iterator iter = m_GlobalSaves.begin(); - iter != m_GlobalSaves.end(); ++iter) { - if ((*iter)->pcName() == currentText) { - ui->globalSavesList->addItem(QFileInfo((*iter)->fileName()).fileName()); - } - } -} - -void TransferSavesDialog::on_localCharacterList_currentTextChanged(const QString ¤tText) -{ - ui->localSavesList->clear(); - for (std::vector<SaveGame*>::const_iterator iter = m_LocalSaves.begin(); - iter != m_LocalSaves.end(); ++iter) { - if ((*iter)->pcName() == currentText) { - ui->localSavesList->addItem(QFileInfo((*iter)->fileName()).fileName()); - } - } -} +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "transfersavesdialog.h"
+#include "ui_transfersavesdialog.h"
+#include "utility.h"
+#include <gameinfo.h>
+#include <QDir>
+#include <QMessageBox>
+#include <Shlwapi.h>
+#include <shlobj.h>
+
+
+using namespace MOBase;
+using namespace MOShared;
+
+
+TransferSavesDialog::TransferSavesDialog(const Profile &profile, QWidget *parent)
+ : TutorableDialog("TransferSaves", parent), ui(new Ui::TransferSavesDialog), m_Profile(profile)
+{
+ ui->setupUi(this);
+ refreshGlobalSaves();
+ refreshLocalSaves();
+ refreshGlobalCharacters();
+ refreshLocalCharacters();
+}
+
+TransferSavesDialog::~TransferSavesDialog()
+{
+ delete ui;
+}
+
+
+void TransferSavesDialog::refreshGlobalSaves()
+{
+ m_GlobalSaves.clear();
+
+ QDir savesDir(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir())));
+
+ QStringList filters;
+ filters << ToQString(GameInfo::instance().getSaveGameExtension());
+ savesDir.setNameFilters(filters);
+
+ QStringList files = savesDir.entryList(QDir::Files, QDir::Time);
+
+ foreach (const QString &filename, files) {
+ SaveGameGamebryo *save = new SaveGameGamebryo(this, savesDir.absoluteFilePath(filename));
+ save->setParent(this);
+ m_GlobalSaves.push_back(save);
+ }
+}
+
+
+void TransferSavesDialog::refreshLocalSaves()
+{
+ m_LocalSaves.clear();
+
+ QDir savesDir(m_Profile.getPath().append("/saves"));
+
+ QStringList filters;
+ filters << ToQString(GameInfo::instance().getSaveGameExtension());
+ savesDir.setNameFilters(filters);
+
+ QStringList files = savesDir.entryList(QDir::Files, QDir::Time);
+
+ foreach (const QString &filename, files) {
+ SaveGameGamebryo *save = new SaveGameGamebryo(this, savesDir.absoluteFilePath(filename));
+ save->setParent(this);
+ m_LocalSaves.push_back(save);
+ }
+}
+
+
+void TransferSavesDialog::refreshGlobalCharacters()
+{
+ std::set<QString> characters;
+ for (std::vector<SaveGame*>::const_iterator iter = m_GlobalSaves.begin();
+ iter != m_GlobalSaves.end(); ++iter) {
+ characters.insert((*iter)->pcName());
+ }
+ ui->globalCharacterList->clear();
+ for (std::set<QString>::const_iterator iter = characters.begin();
+ iter != characters.end(); ++iter) {
+ ui->globalCharacterList->addItem(*iter);
+ }
+ if (ui->globalCharacterList->count() > 0) {
+ ui->globalCharacterList->setCurrentRow(0);
+ ui->copyToLocalBtn->setEnabled(true);
+ ui->moveToLocalBtn->setEnabled(true);
+ } else {
+ ui->copyToLocalBtn->setEnabled(false);
+ ui->moveToLocalBtn->setEnabled(false);
+ }
+}
+
+
+void TransferSavesDialog::refreshLocalCharacters()
+{
+ std::set<QString> characters;
+ for (std::vector<SaveGame*>::const_iterator iter = m_LocalSaves.begin();
+ iter != m_LocalSaves.end(); ++iter) {
+ characters.insert((*iter)->pcName());
+ }
+ ui->localCharacterList->clear();
+ for (std::set<QString>::const_iterator iter = characters.begin();
+ iter != characters.end(); ++iter) {
+ ui->localCharacterList->addItem(*iter);
+ }
+ if (ui->localCharacterList->count() > 0) {
+ ui->localCharacterList->setCurrentRow(0);
+ ui->copyToGlobalBtn->setEnabled(true);
+ ui->moveToGlobalBtn->setEnabled(true);
+ } else {
+ ui->copyToGlobalBtn->setEnabled(false);
+ ui->moveToGlobalBtn->setEnabled(false);
+ }
+}
+
+
+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;
+}
+
+void TransferSavesDialog::on_moveToLocalBtn_clicked()
+{
+ QString selectedCharacter = ui->globalCharacterList->currentItem()->text();
+ if (QMessageBox::question(this, tr("Confirm"),
+ tr("Copy all save games of character \"%1\" to the profile?").arg(selectedCharacter),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ QString destination = m_Profile.getPath().append("/saves");
+ OverwriteMode overwriteMode = OVERWRITE_ASK;
+
+ for (std::vector<SaveGame*>::const_iterator iter = m_GlobalSaves.begin();
+ iter != m_GlobalSaves.end(); ++iter) {
+ if ((*iter)->pcName() == selectedCharacter) {
+ QStringList files = (*iter)->saveFiles();
+ foreach (const QString &file, files) {
+ QFileInfo fileInfo(file);
+ QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName());
+ if (QFile::exists(destinationFile)) {
+ if (testOverwrite(overwriteMode, destinationFile)) {
+ QFile::remove(destinationFile);
+ } else {
+ continue;
+ }
+ }
+ if (!QFile::rename(fileInfo.absoluteFilePath(), destinationFile)) {
+ qCritical("failed to move %s to %s",
+ fileInfo.absoluteFilePath().toUtf8().constData(),
+ destinationFile.toUtf8().constData());
+ }
+ }
+ }
+ }
+ }
+ refreshGlobalSaves();
+ refreshGlobalCharacters();
+ refreshLocalSaves();
+ refreshLocalCharacters();
+}
+
+void TransferSavesDialog::on_copyToLocalBtn_clicked()
+{
+ QString selectedCharacter = ui->globalCharacterList->currentItem()->text();
+ if (QMessageBox::question(this, tr("Confirm"),
+ tr("Copy all save games of character \"%1\" to the profile?").arg(selectedCharacter),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+ QString destination = m_Profile.getPath().append("/saves");
+ OverwriteMode overwriteMode = OVERWRITE_ASK;
+ for (std::vector<SaveGame*>::const_iterator iter = m_GlobalSaves.begin();
+ iter != m_GlobalSaves.end(); ++iter) {
+ if ((*iter)->pcName() == selectedCharacter) {
+ QStringList files = (*iter)->saveFiles();
+ foreach (const QString &file, files) {
+ QFileInfo fileInfo(file);
+ QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName());
+ if (QFile::exists(destinationFile)) {
+ if (testOverwrite(overwriteMode, destinationFile)) {
+ QFile::remove(destinationFile);
+ } else {
+ continue;
+ }
+ }
+ if (!QFile::copy(fileInfo.absoluteFilePath(), destinationFile)) {
+ qCritical("failed to copy %s to %s",
+ fileInfo.absoluteFilePath().toUtf8().constData(),
+ destinationFile.toUtf8().constData());
+ }
+ }
+ }
+ }
+ }
+ refreshLocalSaves();
+ refreshLocalCharacters();
+}
+
+void TransferSavesDialog::on_moveToGlobalBtn_clicked()
+{
+ QString selectedCharacter = ui->localCharacterList->currentItem()->text();
+ if (QMessageBox::question(this, tr("Confirm"),
+ tr("Move all save games of character \"%1\" to the global location? Please be aware "
+ "that this will mess up the running number of save games.").arg(selectedCharacter),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+
+ QString destination = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir()));
+ OverwriteMode overwriteMode = OVERWRITE_ASK;
+ for (std::vector<SaveGame*>::const_iterator iter = m_LocalSaves.begin();
+ iter != m_LocalSaves.end(); ++iter) {
+ if ((*iter)->pcName() == selectedCharacter) {
+ QStringList files = (*iter)->saveFiles();
+ foreach (const QString &file, files) {
+ QFileInfo fileInfo(file);
+ QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName());
+ if (QFile::exists(destinationFile)) {
+ if (testOverwrite(overwriteMode, destinationFile)) {
+ QFile::remove(destinationFile);
+ } else {
+ continue;
+ }
+ }
+ if (!QFile::rename(fileInfo.absoluteFilePath(), destinationFile)) {
+ qCritical("failed to move %s to %s",
+ fileInfo.absoluteFilePath().toUtf8().constData(),
+ destinationFile.toUtf8().constData());
+ }
+ }
+ }
+ }
+ }
+ refreshGlobalSaves();
+ refreshGlobalCharacters();
+ refreshLocalSaves();
+ refreshLocalCharacters();
+}
+
+void TransferSavesDialog::on_copyToGlobalBtn_clicked()
+{
+ QString selectedCharacter = ui->localCharacterList->currentItem()->text();
+ if (QMessageBox::question(this, tr("Confirm"),
+ tr("Copy all save games of character \"%1\" to the global location? Please be aware "
+ "that this will mess up the running number of save games.").arg(selectedCharacter),
+ QMessageBox::Yes | QMessageBox::No) == QMessageBox::Yes) {
+
+ QString destination = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getSaveGameDir()));
+ OverwriteMode overwriteMode = OVERWRITE_ASK;
+ for (std::vector<SaveGame*>::const_iterator iter = m_LocalSaves.begin();
+ iter != m_LocalSaves.end(); ++iter) {
+ if ((*iter)->pcName() == selectedCharacter) {
+ QStringList files = (*iter)->saveFiles();
+ foreach (const QString &file, files) {
+ QFileInfo fileInfo(file);
+ QString destinationFile = destination.mid(0).append("/").append(fileInfo.fileName());
+ if (QFile::exists(destinationFile)) {
+ if (testOverwrite(overwriteMode, destinationFile)) {
+ QFile::remove(destinationFile);
+ } else {
+ continue;
+ }
+ }
+ if (!QFile::copy(fileInfo.absoluteFilePath(), destinationFile)) {
+ qCritical("failed to copy %s to %s",
+ fileInfo.absoluteFilePath().toUtf8().constData(),
+ destinationFile.toUtf8().constData());
+ }
+ }
+ }
+ }
+ }
+ refreshGlobalSaves();
+ refreshGlobalCharacters();
+}
+
+void TransferSavesDialog::on_doneButton_clicked()
+{
+ close();
+}
+
+void TransferSavesDialog::on_globalCharacterList_currentTextChanged(const QString ¤tText)
+{
+ ui->globalSavesList->clear();
+ for (std::vector<SaveGame*>::const_iterator iter = m_GlobalSaves.begin();
+ iter != m_GlobalSaves.end(); ++iter) {
+ if ((*iter)->pcName() == currentText) {
+ ui->globalSavesList->addItem(QFileInfo((*iter)->fileName()).fileName());
+ }
+ }
+}
+
+void TransferSavesDialog::on_localCharacterList_currentTextChanged(const QString ¤tText)
+{
+ ui->localSavesList->clear();
+ for (std::vector<SaveGame*>::const_iterator iter = m_LocalSaves.begin();
+ iter != m_LocalSaves.end(); ++iter) {
+ if ((*iter)->pcName() == currentText) {
+ ui->localSavesList->addItem(QFileInfo((*iter)->fileName()).fileName());
+ }
+ }
+}
diff --git a/src/transfersavesdialog.h b/src/transfersavesdialog.h index 4306b960..37a85000 100644 --- a/src/transfersavesdialog.h +++ b/src/transfersavesdialog.h @@ -1,82 +1,82 @@ -/* -Copyright (C) 2012 Sebastian Herbord. All rights reserved. - -This file is part of Mod Organizer. - -Mod Organizer is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Mod Organizer is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>. -*/ - -#ifndef TRANSFERSAVESDIALOG_H -#define TRANSFERSAVESDIALOG_H - -#include "tutorabledialog.h" -#include "savegamegamebyro.h" -#include "profile.h" - -namespace Ui { -class TransferSavesDialog; -} - -class TransferSavesDialog : public MOBase::TutorableDialog -{ - Q_OBJECT - -public: - explicit TransferSavesDialog(const Profile &profile, QWidget *parent = 0); - ~TransferSavesDialog(); - -private slots: - - void on_moveToLocalBtn_clicked(); - - void on_doneButton_clicked(); - - void on_globalCharacterList_currentTextChanged(const QString ¤tText); - - void on_localCharacterList_currentTextChanged(const QString ¤tText); - - void on_copyToLocalBtn_clicked(); - - void on_moveToGlobalBtn_clicked(); - - void on_copyToGlobalBtn_clicked(); - -private: - - enum OverwriteMode { - OVERWRITE_ASK, - OVERWRITE_YES, - OVERWRITE_NO - }; - -private: - - void refreshGlobalCharacters(); - void refreshLocalCharacters(); - void refreshGlobalSaves(); - void refreshLocalSaves(); - bool testOverwrite(OverwriteMode &overwriteMode, const QString &destinationFile); - -private: - - Ui::TransferSavesDialog *ui; - - Profile m_Profile; - - std::vector<SaveGame*> m_GlobalSaves; - std::vector<SaveGame*> m_LocalSaves; - -}; - -#endif // TRANSFERSAVESDIALOG_H +/*
+Copyright (C) 2012 Sebastian Herbord. All rights reserved.
+
+This file is part of Mod Organizer.
+
+Mod Organizer is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+Mod Organizer is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#ifndef TRANSFERSAVESDIALOG_H
+#define TRANSFERSAVESDIALOG_H
+
+#include "tutorabledialog.h"
+#include "savegamegamebyro.h"
+#include "profile.h"
+
+namespace Ui {
+class TransferSavesDialog;
+}
+
+class TransferSavesDialog : public MOBase::TutorableDialog
+{
+ Q_OBJECT
+
+public:
+ explicit TransferSavesDialog(const Profile &profile, QWidget *parent = 0);
+ ~TransferSavesDialog();
+
+private slots:
+
+ void on_moveToLocalBtn_clicked();
+
+ void on_doneButton_clicked();
+
+ void on_globalCharacterList_currentTextChanged(const QString ¤tText);
+
+ void on_localCharacterList_currentTextChanged(const QString ¤tText);
+
+ void on_copyToLocalBtn_clicked();
+
+ void on_moveToGlobalBtn_clicked();
+
+ void on_copyToGlobalBtn_clicked();
+
+private:
+
+ enum OverwriteMode {
+ OVERWRITE_ASK,
+ OVERWRITE_YES,
+ OVERWRITE_NO
+ };
+
+private:
+
+ void refreshGlobalCharacters();
+ void refreshLocalCharacters();
+ void refreshGlobalSaves();
+ void refreshLocalSaves();
+ bool testOverwrite(OverwriteMode &overwriteMode, const QString &destinationFile);
+
+private:
+
+ Ui::TransferSavesDialog *ui;
+
+ Profile m_Profile;
+
+ std::vector<SaveGame*> m_GlobalSaves;
+ std::vector<SaveGame*> m_LocalSaves;
+
+};
+
+#endif // TRANSFERSAVESDIALOG_H
|
