summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/activatemodsdialog.cpp194
-rw-r--r--src/activatemodsdialog.h140
-rw-r--r--src/bbcode.cpp468
-rw-r--r--src/bbcode.h80
-rw-r--r--src/browserdialog.cpp540
-rw-r--r--src/browserdialog.h248
-rw-r--r--src/browserview.cpp152
-rw-r--r--src/browserview.h160
-rw-r--r--src/categories.cpp602
-rw-r--r--src/categories.h400
-rw-r--r--src/categoriesdialog.cpp486
-rw-r--r--src/categoriesdialog.h142
-rw-r--r--src/credentialsdialog.cpp38
-rw-r--r--src/credentialsdialog.h38
-rw-r--r--src/directoryrefresher.cpp312
-rw-r--r--src/directoryrefresher.h294
-rw-r--r--src/downloadlist.cpp228
-rw-r--r--src/downloadlist.h192
-rw-r--r--src/downloadlistsortproxy.cpp130
-rw-r--r--src/downloadlistsortproxy.h84
-rw-r--r--src/downloadlistwidget.cpp702
-rw-r--r--src/downloadlistwidget.h242
-rw-r--r--src/downloadlistwidgetcompact.cpp682
-rw-r--r--src/downloadlistwidgetcompact.h244
-rw-r--r--src/downloadmanager.cpp2942
-rw-r--r--src/downloadmanager.h984
-rw-r--r--src/dummybsa.cpp488
-rw-r--r--src/dummybsa.h38
-rw-r--r--src/editexecutablesdialog.cpp578
-rw-r--r--src/editexecutablesdialog.h196
-rw-r--r--src/executableslist.cpp406
-rw-r--r--src/executableslist.h310
-rw-r--r--src/filedialogmemory.cpp188
-rw-r--r--src/filedialogmemory.h38
-rw-r--r--src/gameinfoimpl.cpp108
-rw-r--r--src/gameinfoimpl.h78
-rw-r--r--src/helper.cpp38
-rw-r--r--src/helper.h38
-rw-r--r--src/icondelegate.cpp140
-rw-r--r--src/icondelegate.h104
-rw-r--r--src/installationmanager.cpp1604
-rw-r--r--src/installationmanager.h432
-rw-r--r--src/json.cpp1044
-rw-r--r--src/json.h188
-rw-r--r--src/loadmechanism.cpp580
-rw-r--r--src/loadmechanism.h38
-rw-r--r--src/lockeddialog.cpp150
-rw-r--r--src/lockeddialog.h134
-rw-r--r--src/logbuffer.cpp522
-rw-r--r--src/logbuffer.h198
-rw-r--r--src/loghighlighter.cpp38
-rw-r--r--src/loghighlighter.h38
-rw-r--r--src/main.cpp1162
-rw-r--r--src/mainwindow.cpp11046
-rw-r--r--src/mainwindow.h1224
-rw-r--r--src/messagedialog.cpp184
-rw-r--r--src/messagedialog.h134
-rw-r--r--src/moapplication.cpp296
-rw-r--r--src/moapplication.h102
-rw-r--r--src/modeltest.cpp1166
-rw-r--r--src/modeltest.h188
-rw-r--r--src/modinfo.cpp1996
-rw-r--r--src/modinfo.h2016
-rw-r--r--src/modinfodialog.cpp2506
-rw-r--r--src/modinfodialog.h474
-rw-r--r--src/modlist.cpp1950
-rw-r--r--src/modlist.h576
-rw-r--r--src/modlistsortproxy.cpp752
-rw-r--r--src/modlistsortproxy.h242
-rw-r--r--src/motddialog.cpp98
-rw-r--r--src/motddialog.h66
-rw-r--r--src/nexusinterface.cpp1244
-rw-r--r--src/nexusinterface.h644
-rw-r--r--src/nxmaccessmanager.cpp416
-rw-r--r--src/nxmaccessmanager.h206
-rw-r--r--src/overwriteinfodialog.cpp520
-rw-r--r--src/overwriteinfodialog.h146
-rw-r--r--src/pluginlist.cpp2288
-rw-r--r--src/pluginlist.h688
-rw-r--r--src/pluginlistsortproxy.cpp304
-rw-r--r--src/pluginlistsortproxy.h142
-rw-r--r--src/profile.cpp1564
-rw-r--r--src/profile.h660
-rw-r--r--src/profileinputdialog.cpp96
-rw-r--r--src/profileinputdialog.h38
-rw-r--r--src/profilesdialog.cpp640
-rw-r--r--src/profilesdialog.h192
-rw-r--r--src/queryoverwritedialog.cpp38
-rw-r--r--src/queryoverwritedialog.h38
-rw-r--r--src/report.cpp106
-rw-r--r--src/report.h38
-rw-r--r--src/savegame.cpp212
-rw-r--r--src/savegame.h262
-rw-r--r--src/savegamegamebryo.cpp676
-rw-r--r--src/savegamegamebyro.h204
-rw-r--r--src/savegameinfowidget.cpp124
-rw-r--r--src/savegameinfowidget.h38
-rw-r--r--src/savegameinfowidgetgamebryo.cpp108
-rw-r--r--src/savegameinfowidgetgamebryo.h38
-rw-r--r--src/selectiondialog.cpp166
-rw-r--r--src/selectiondialog.h136
-rw-r--r--src/selfupdater.cpp932
-rw-r--r--src/selfupdater.h290
-rw-r--r--src/settings.cpp1520
-rw-r--r--src/settings.h626
-rw-r--r--src/settingsdialog.cpp346
-rw-r--r--src/settingsdialog.h168
-rw-r--r--src/shared/appconfig.cpp2
-rw-r--r--src/shared/inject.h62
-rw-r--r--src/singleinstance.cpp214
-rw-r--r--src/singleinstance.h38
-rw-r--r--src/spawn.cpp390
-rw-r--r--src/spawn.h136
-rw-r--r--src/syncoverwritedialog.cpp332
-rw-r--r--src/syncoverwritedialog.h110
-rw-r--r--src/transfersavesdialog.cpp656
-rw-r--r--src/transfersavesdialog.h164
117 files changed, 31287 insertions, 31287 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
diff --git a/src/json.h b/src/json.h
index a61f6c85..57842e37 100644
--- a/src/json.h
+++ b/src/json.h
@@ -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 1936bdb1..a0373ce4 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -1,5523 +1,5523 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General 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());
- 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(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->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();
-
- 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);
- }
-
- { // 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 &currentDirectory, 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 &currentDirectory, 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();
-
- while ((::WaitForSingleObject(processHandle, 100) == WAIT_TIMEOUT) &&
- !dialog->unlockClicked()) {
- // keep processing events so the app doesn't appear dead
- QCoreApplication::processEvents();
- }
-
- 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);
-
- languageChange(m_Settings.language());
- 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::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)
-{
- 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;
-
- if (newLanguage != "en_US") {
- installTranslator("qt");
- installTranslator(ToQString(AppConfig::translationPrefix()));
- foreach(IPlugin *plugin, m_Settings.plugins()) {
- QObject *pluginObj = dynamic_cast<QObject*>(plugin);
- if (pluginObj != NULL) {
- QVariant fileNameVariant = pluginObj->property("filename");
- if (fileNameVariant.isValid()) {
- QString fileName = QFileInfo(fileNameVariant.toString()).baseName();
- installTranslator(fileName);
- }
- }
- }
- }
- ui->retranslateUi(this);
- ui->profileBox->setItemText(0, QObject::tr("<Manage...>"));
-// ui->toolBar->addWidget(createHelpWidget(ui->toolBar));
-
- updateDownloadListDelegate();
- updateProblemsButton();
-}
-
-
-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)
-{
- 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();
-
- if (loot != INVALID_HANDLE_VALUE) {
- while (::WaitForSingleObject(loot, 100) == WAIT_TIMEOUT) {
- // keep processing events so the app doesn't appear dead
- QCoreApplication::processEvents();
- if (dialog.wasCanceled()) {
- ::TerminateProcess(loot, 1);
- }
- std::string lootOut = readFromPipe(stdOutRead);
- processLOOTOut(lootOut, reportURL, errorMessages, dialog);
- }
- 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;
- }
- }
- } 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());
+ 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(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->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();
+
+ 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);
+ }
+
+ { // 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 &currentDirectory, 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 &currentDirectory, 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();
+
+ while ((::WaitForSingleObject(processHandle, 100) == WAIT_TIMEOUT) &&
+ !dialog->unlockClicked()) {
+ // keep processing events so the app doesn't appear dead
+ QCoreApplication::processEvents();
+ }
+
+ 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);
+
+ languageChange(m_Settings.language());
+ 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::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)
+{
+ 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;
+
+ if (newLanguage != "en_US") {
+ installTranslator("qt");
+ installTranslator(ToQString(AppConfig::translationPrefix()));
+ foreach(IPlugin *plugin, m_Settings.plugins()) {
+ QObject *pluginObj = dynamic_cast<QObject*>(plugin);
+ if (pluginObj != NULL) {
+ QVariant fileNameVariant = pluginObj->property("filename");
+ if (fileNameVariant.isValid()) {
+ QString fileName = QFileInfo(fileNameVariant.toString()).baseName();
+ installTranslator(fileName);
+ }
+ }
+ }
+ }
+ ui->retranslateUi(this);
+ ui->profileBox->setItemText(0, QObject::tr("<Manage...>"));
+// ui->toolBar->addWidget(createHelpWidget(ui->toolBar));
+
+ updateDownloadListDelegate();
+ updateProblemsButton();
+}
+
+
+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)
+{
+ 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();
+
+ if (loot != INVALID_HANDLE_VALUE) {
+ while (::WaitForSingleObject(loot, 100) == WAIT_TIMEOUT) {
+ // keep processing events so the app doesn't appear dead
+ QCoreApplication::processEvents();
+ if (dialog.wasCanceled()) {
+ ::TerminateProcess(loot, 1);
+ }
+ std::string lootOut = readFromPipe(stdOutRead);
+ processLOOTOut(lootOut, reportURL, errorMessages, dialog);
+ }
+ 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;
+ }
+ }
+ } 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 c1cfdb3c..ab5e0cd5 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -1,612 +1,612 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General 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 &currentDirectory);*/
-
- 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);
-
-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 &currentDirectory, const QString &steamAppID);
- void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir &currentDirectory = 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);
-
-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 &currentDirectory);*/
+
+ 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);
+
+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 &currentDirectory, const QString &steamAppID);
+ void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir &currentDirectory = 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);
+
+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 2dadf2c6..fff613dc 100644
--- a/src/modinfo.cpp
+++ b/src/modinfo.cpp
@@ -1,998 +1,998 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General 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)) {
- bool overwrite = false;
- bool overwritten = false;
- 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 (auto iter = files.begin(); iter != files.end() && (!overwrite || !overwritten || !regular); ++iter) {
- const std::vector<int> &alternatives = (*iter)->getAlternatives();
- if (alternatives.size() == 0) {
- // no alternatives -> no conflict
- regular = true;
- } else {
- for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) {
- // don't treat files overwritten in data as "conflict"
- if (*altIter != dataID) {
- bool ignore = false;
- if ((*iter)->getOrigin(ignore) == origin.getID()) {
- overwrite = true;
- break;
- } else {
- overwritten = true;
- break;
- }
- } else if (alternatives.size() == 1) {
- // only alternative is data -> no conflict
- regular = true;
- }
- }
- }
- }
- }
-
- m_LastConflictCheck = QTime::currentTime();
-
- if (overwrite && overwritten) m_CurrentConflictState = CONFLICT_MIXED;
- else if (overwrite) m_CurrentConflictState = CONFLICT_OVERWRITE;
- else if (overwritten) {
- 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 &notes)
-{
- 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)) {
+ bool overwrite = false;
+ bool overwritten = false;
+ 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 (auto iter = files.begin(); iter != files.end() && (!overwrite || !overwritten || !regular); ++iter) {
+ const std::vector<int> &alternatives = (*iter)->getAlternatives();
+ if (alternatives.size() == 0) {
+ // no alternatives -> no conflict
+ regular = true;
+ } else {
+ for (auto altIter = alternatives.begin(); altIter != alternatives.end(); ++altIter) {
+ // don't treat files overwritten in data as "conflict"
+ if (*altIter != dataID) {
+ bool ignore = false;
+ if ((*iter)->getOrigin(ignore) == origin.getID()) {
+ overwrite = true;
+ break;
+ } else {
+ overwritten = true;
+ break;
+ }
+ } else if (alternatives.size() == 1) {
+ // only alternative is data -> no conflict
+ regular = true;
+ }
+ }
+ }
+ }
+ }
+
+ m_LastConflictCheck = QTime::currentTime();
+
+ if (overwrite && overwritten) m_CurrentConflictState = CONFLICT_MIXED;
+ else if (overwrite) m_CurrentConflictState = CONFLICT_OVERWRITE;
+ else if (overwritten) {
+ 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 &notes)
+{
+ 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 ef647eab..453c8159 100644
--- a/src/modinfo.h
+++ b/src/modinfo.h
@@ -1,1008 +1,1008 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General 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 &notes) = 0;
-
- /**
- * @brief set/change the nexus mod id of this mod
- *
- * @param modID the nexus mod id
- **/
- virtual void setNexusID(int modID) = 0;
-
- /**
- * @brief set/change the version of this mod
- * @param version new version of the mod
- */
- virtual void setVersion(const MOBase::VersionInfo &version);
-
- /**
- * @brief 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() {}
-
-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();
-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;
-
-};
-
-
-/**
- * @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 &notes);
-
- /**
- * @brief set/change the nexus mod id of this mod
- *
- * @param modID the nexus mod id
- **/
- void setNexusID(int modID);
-
- /**
- * @brief set the version of this mod
- *
- * this can be used to overwrite the version of a mod without actually
- * updating the mod
- *
- * @param version the new version to use
- **/
- void setVersion(const MOBase::VersionInfo &version);
-
- /**
- * @brief set the newest version of this mod on the nexus
- *
- * this can be used to overwrite the version of a mod without actually
- * updating the mod
- *
- * @param version the new version to use
- * @todo this function should be made obsolete. All queries for mod information should go through
- * this class so no public function for this change is required
- **/
- void setNewestVersion(const MOBase::VersionInfo &version);
-
- /**
- * @brief changes/updates the nexus description text
- * @param description the current description text
- */
- virtual void setNexusDescription(const QString &description);
-
- /**
- * @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 &notes) = 0;
+
+ /**
+ * @brief set/change the nexus mod id of this mod
+ *
+ * @param modID the nexus mod id
+ **/
+ virtual void setNexusID(int modID) = 0;
+
+ /**
+ * @brief set/change the version of this mod
+ * @param version new version of the mod
+ */
+ virtual void setVersion(const MOBase::VersionInfo &version);
+
+ /**
+ * @brief 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() {}
+
+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();
+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;
+
+};
+
+
+/**
+ * @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 &notes);
+
+ /**
+ * @brief set/change the nexus mod id of this mod
+ *
+ * @param modID the nexus mod id
+ **/
+ void setNexusID(int modID);
+
+ /**
+ * @brief set the version of this mod
+ *
+ * this can be used to overwrite the version of a mod without actually
+ * updating the mod
+ *
+ * @param version the new version to use
+ **/
+ void setVersion(const MOBase::VersionInfo &version);
+
+ /**
+ * @brief set the newest version of this mod on the nexus
+ *
+ * this can be used to overwrite the version of a mod without actually
+ * updating the mod
+ *
+ * @param version the new version to use
+ * @todo this function should be made obsolete. All queries for mod information should go through
+ * this class so no public function for this change is required
+ **/
+ void setNewestVersion(const MOBase::VersionInfo &version);
+
+ /**
+ * @brief changes/updates the nexus description text
+ * @param description the current description text
+ */
+ virtual void setNexusDescription(const QString &description);
+
+ /**
+ * @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 b18dc58c..a3653e86 100644
--- a/src/modinfodialog.cpp
+++ b/src/modinfodialog.cpp
@@ -1,1253 +1,1253 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General 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);
- } else {
- initFiletree(modInfo);
- addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0);
- refreshPrimaryCategoriesBox();
- }
- refreshLists();
- initINITweaks();
-
- 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));
- 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);
+ } else {
+ initFiletree(modInfo);
+ addCategories(CategoryFactory::instance(), modInfo->getCategories(), ui->categoriesTree->invisibleRootItem(), 0);
+ refreshPrimaryCategoriesBox();
+ }
+ refreshLists();
+ initINITweaks();
+
+ 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));
+ 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 3f46b85f..edfdf40f 100644
--- a/src/modlist.cpp
+++ b/src/modlist.cpp
@@ -1,975 +1,975 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General 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 <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::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::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 <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::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::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 b946e009..c43632db 100644
--- a/src/modlist.h
+++ b/src/modlist.h
@@ -1,288 +1,288 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General 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 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;
-
- 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 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;
+
+ 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 08c16b62..8231819d 100644
--- a/src/nxmaccessmanager.cpp
+++ b/src/nxmaccessmanager.cpp
@@ -1,208 +1,208 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General 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();
- emit loginFailed(m_LoginReply->errorString());
- m_LoginTimeout.stop();
- m_LoginReply->deleteLater();
- m_LoginReply = NULL;
- 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();
+ emit loginFailed(m_LoginReply->errorString());
+ m_LoginTimeout.stop();
+ m_LoginReply->deleteLater();
+ m_LoginReply = NULL;
+ 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 4f363900..88b17ed3 100644
--- a/src/pluginlist.cpp
+++ b/src/pluginlist.cpp
@@ -1,1144 +1,1144 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General 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) {
- QFile deleterFile(deleterFileName);
- deleterFile.open(QIODevice::WriteOnly);
- deleterFile.resize(0);
- 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");
- }
- }
- deleterFile.close();
- qDebug("%s saved", QDir::toNativeSeparators(deleterFileName).toUtf8().constData());
- }
-
- 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) {
- 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)
-{
- if (role == Qt::CheckStateRole) {
- m_ESPs[modIndex.row()].m_Enabled = value.toInt() == Qt::Checked;
- emit dataChanged(modIndex, modIndex);
-
- refreshLoadOrder();
- startSaveTime();
-
- return true;
- } else {
- return false;
- }
-}
-
-
-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;
- }
- } 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) {
+ QFile deleterFile(deleterFileName);
+ deleterFile.open(QIODevice::WriteOnly);
+ deleterFile.resize(0);
+ 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");
+ }
+ }
+ deleterFile.close();
+ qDebug("%s saved", QDir::toNativeSeparators(deleterFileName).toUtf8().constData());
+ }
+
+ 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) {
+ 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)
+{
+ if (role == Qt::CheckStateRole) {
+ m_ESPs[modIndex.row()].m_Enabled = value.toInt() == Qt::Checked;
+ emit dataChanged(modIndex, modIndex);
+
+ refreshLoadOrder();
+ startSaveTime();
+
+ return true;
+ } else {
+ return false;
+ }
+}
+
+
+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;
+ }
+ } 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 &lt;no filter&gt;.
- **/
- 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 &lt;no filter&gt;.
+ **/
+ 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 d4f80e87..deaa3e01 100644
--- a/src/savegameinfowidgetgamebryo.cpp
+++ b/src/savegameinfowidgetgamebryo.cpp
@@ -17,57 +17,57 @@ 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);
- }
-}
+#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 c2855a85..e608427c 100644
--- a/src/settings.cpp
+++ b/src/settings.cpp
@@ -1,760 +1,760 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General 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());
- if (QDir(downloadDirEdit->text()).exists()) {
- m_Settings.setValue("Settings/download_directory", QDir::toNativeSeparators(downloadDirEdit->text()));
- }
- if (!QDir(cacheDirEdit->text()).exists()) {
- QDir().mkpath(cacheDirEdit->text());
- }
- m_Settings.setValue("Settings/cache_directory", QDir::toNativeSeparators(cacheDirEdit->text()));
- if (QDir(modDirEdit->text()).exists()) {
- 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::Yes)) {
- 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());
+ if (QDir(downloadDirEdit->text()).exists()) {
+ m_Settings.setValue("Settings/download_directory", QDir::toNativeSeparators(downloadDirEdit->text()));
+ }
+ if (!QDir(cacheDirEdit->text()).exists()) {
+ QDir().mkpath(cacheDirEdit->text());
+ }
+ m_Settings.setValue("Settings/cache_directory", QDir::toNativeSeparators(cacheDirEdit->text()));
+ if (QDir(modDirEdit->text()).exists()) {
+ 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::Yes)) {
+ 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 6adafba0..1392ec88 100644
--- a/src/spawn.cpp
+++ b/src/spawn.cpp
@@ -1,195 +1,195 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General 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
- 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 &currentDirectory,
- bool hooked,
- HANDLE stdOut,
- HANDLE stdErr)
-{
- HANDLE processHandle, threadHandle;
- std::wstring binaryName = ToWString(QDir::toNativeSeparators(binary.absoluteFilePath()));
- std::wstring currentDirectoryName = ToWString(QDir::toNativeSeparators(currentDirectory.absolutePath()));
-
- try {
- if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), hooked,
- stdOut, stdErr, processHandle, threadHandle)) {
- reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName()));
- return INVALID_HANDLE_VALUE;
- }
- } catch (const windows_error &e) {
- if (e.getErrorCode() == ERROR_ELEVATION_REQUIRED) {
- // TODO: check if this is really correct. Are all settings updated that the secondary instance may use?
-
- if (QMessageBox::question(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 (::ResumeThread(threadHandle) == (DWORD)-1) {
- reportError(QObject::tr("failed to run \"%1\"").arg(binary.fileName()));
- return INVALID_HANDLE_VALUE;
- }
- }
- return processHandle;
-}
-
-/*
-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
+ 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 &currentDirectory,
+ bool hooked,
+ HANDLE stdOut,
+ HANDLE stdErr)
+{
+ HANDLE processHandle, threadHandle;
+ std::wstring binaryName = ToWString(QDir::toNativeSeparators(binary.absoluteFilePath()));
+ std::wstring currentDirectoryName = ToWString(QDir::toNativeSeparators(currentDirectory.absolutePath()));
+
+ try {
+ if (!spawn(binaryName.c_str(), ToWString(arguments).c_str(), currentDirectoryName.c_str(), hooked,
+ stdOut, stdErr, processHandle, threadHandle)) {
+ reportError(QObject::tr("failed to spawn \"%1\"").arg(binary.fileName()));
+ return INVALID_HANDLE_VALUE;
+ }
+ } catch (const windows_error &e) {
+ if (e.getErrorCode() == ERROR_ELEVATION_REQUIRED) {
+ // TODO: check if this is really correct. Are all settings updated that the secondary instance may use?
+
+ if (QMessageBox::question(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 (::ResumeThread(threadHandle) == (DWORD)-1) {
+ reportError(QObject::tr("failed to run \"%1\"").arg(binary.fileName()));
+ return INVALID_HANDLE_VALUE;
+ }
+ }
+ return processHandle;
+}
+
+/*
+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 &currentDirectory, 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 &currentDirectory, 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 &currentText)
-{
- 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 &currentText)
-{
- 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 &currentText)
+{
+ 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 &currentText)
+{
+ 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 &currentText);
-
- void on_localCharacterList_currentTextChanged(const QString &currentText);
-
- void on_copyToLocalBtn_clicked();
-
- void on_moveToGlobalBtn_clicked();
-
- void on_copyToGlobalBtn_clicked();
-
-private:
-
- enum OverwriteMode {
- OVERWRITE_ASK,
- OVERWRITE_YES,
- OVERWRITE_NO
- };
-
-private:
-
- void refreshGlobalCharacters();
- void refreshLocalCharacters();
- void refreshGlobalSaves();
- void refreshLocalSaves();
- bool testOverwrite(OverwriteMode &overwriteMode, const QString &destinationFile);
-
-private:
-
- Ui::TransferSavesDialog *ui;
-
- Profile m_Profile;
-
- 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 &currentText);
+
+ void on_localCharacterList_currentTextChanged(const QString &currentText);
+
+ void on_copyToLocalBtn_clicked();
+
+ void on_moveToGlobalBtn_clicked();
+
+ void on_copyToGlobalBtn_clicked();
+
+private:
+
+ enum OverwriteMode {
+ OVERWRITE_ASK,
+ OVERWRITE_YES,
+ OVERWRITE_NO
+ };
+
+private:
+
+ void refreshGlobalCharacters();
+ void refreshLocalCharacters();
+ void refreshGlobalSaves();
+ void refreshLocalSaves();
+ bool testOverwrite(OverwriteMode &overwriteMode, const QString &destinationFile);
+
+private:
+
+ Ui::TransferSavesDialog *ui;
+
+ Profile m_Profile;
+
+ std::vector<SaveGame*> m_GlobalSaves;
+ std::vector<SaveGame*> m_LocalSaves;
+
+};
+
+#endif // TRANSFERSAVESDIALOG_H