summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTannin <Tannin@Miranda>2013-02-16 19:09:57 +0100
committerTannin <Tannin@Miranda>2013-02-16 19:09:57 +0100
commit5d1154c24e2475ed2b7ac248de27ab7b501a1e5e (patch)
tree265341b9c8d057da46b56ad9fde40c9b39963ebb /src
parent44b96e6fb5f484e9a60fe03b99021a1c5f78488d (diff)
- hooks for CreateHardLink
- createprocess hook will now reroute the cwd (fixes SUM, may break other tools?) - profile code moved to separate file - executables can now be linked to toolbar - category filters are now represented as a tree - csv export of mod list - ModInfoDialog is now "window modal" instead of "application modal" - nexus dialog now updates the mod-id field while browsing - right-click in nexus browser allows to open in external browser - ini viewer is no longer modal - bugfix: another attempt to fix processing of invalid header lines in fomod xmls (bom now handled) - bugfix: integrated fomod installer will no longer overwrite detected mod name by the one from the xml - bugfix: "hide file" from conflicted files list now updates that list - bugfix: conflicted files list no longer offers to hide files in BSAs - bugfix: pressing delete on the mod list with multiplie files selected offered to delete the wrong files - regression: mod name guessing with the old regular expression was less likely to lead to an empty mod name. Now both are tried
Diffstat (limited to 'src')
-rw-r--r--src/baincomplexinstallerdialog.cpp202
-rw-r--r--src/csvbuilder.cpp272
-rw-r--r--src/csvbuilder.h93
-rw-r--r--src/downloadmanager.cpp1817
-rw-r--r--src/editexecutablesdialog.cpp3
-rw-r--r--src/executableslist.cpp366
-rw-r--r--src/executableslist.h262
-rw-r--r--src/fomodinstallerdialog.cpp1857
-rw-r--r--src/fomodinstallerdialog.h386
-rw-r--r--src/installationmanager.cpp94
-rw-r--r--src/installationmanager.h382
-rw-r--r--src/main.cpp844
-rw-r--r--src/mainwindow.cpp7616
-rw-r--r--src/mainwindow.h815
-rw-r--r--src/mainwindow.ui10
-rw-r--r--src/modinfo.cpp1651
-rw-r--r--src/modinfo.h1612
-rw-r--r--src/modinfodialog.cpp2397
-rw-r--r--src/modinfodialog.h397
-rw-r--r--src/modlist.cpp1301
-rw-r--r--src/modlistsortproxy.cpp425
-rw-r--r--src/modlistsortproxy.h124
-rw-r--r--src/nexusdialog.cpp618
-rw-r--r--src/nexusdialog.h283
-rw-r--r--src/nexusinterface.cpp903
-rw-r--r--src/nexusview.cpp167
-rw-r--r--src/nexusview.h146
-rw-r--r--src/organizer.pro30
-rw-r--r--src/savetextasdialog.cpp46
-rw-r--r--src/savetextasdialog.h31
-rw-r--r--src/savetextasdialog.ui69
-rw-r--r--src/shared/directoryentry.cpp1560
-rw-r--r--src/textviewer.h93
-rw-r--r--src/version.rc4
34 files changed, 13790 insertions, 13086 deletions
diff --git a/src/baincomplexinstallerdialog.cpp b/src/baincomplexinstallerdialog.cpp
index c21fa5ad..9b282c82 100644
--- a/src/baincomplexinstallerdialog.cpp
+++ b/src/baincomplexinstallerdialog.cpp
@@ -17,104 +17,104 @@ 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 "baincomplexinstallerdialog.h"
-#include "textviewer.h"
-#include "ui_baincomplexinstallerdialog.h"
-
-#include <QDir>
-
-BainComplexInstallerDialog::BainComplexInstallerDialog(DirectoryTree *tree, const QString &modName, bool hasPackageTXT, QWidget *parent)
- : TutorableDialog("BainInstaller", parent), ui(new Ui::BainComplexInstallerDialog), m_Manual(false)
-{
- ui->setupUi(this);
-
- ui->nameEdit->setText(modName);
-
- for (DirectoryTree::const_node_iterator iter = tree->nodesBegin(); iter != tree->nodesEnd(); ++iter) {
- const QString &dirName = (*iter)->getData().name;
- if ((dirName.compare("fomod", Qt::CaseInsensitive) == 0) ||
- (dirName.startsWith("--"))) {
- continue;
- }
-
- QListWidgetItem *item = new QListWidgetItem(ui->optionsList);
- item->setText((*iter)->getData().name);
- item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
- item->setCheckState(item->text().mid(0, 2) == "00" ? Qt::Checked : Qt::Unchecked);
- item->setData(Qt::UserRole, qVariantFromValue((void*)(*iter)));
- ui->optionsList->addItem(item);
- }
-
- ui->packageBtn->setEnabled(hasPackageTXT);
-}
-
-
-BainComplexInstallerDialog::~BainComplexInstallerDialog()
-{
- delete ui;
-}
-
-
-QString BainComplexInstallerDialog::getName() const
-{
- return ui->nameEdit->text();
-}
-
-
-void BainComplexInstallerDialog::moveTreeUp(DirectoryTree *target, DirectoryTree::Node *child)
-{
- for (DirectoryTree::const_node_iterator iter = child->nodesBegin();
- iter != child->nodesEnd();) {
- target->addNode(*iter, true);
- iter = child->detach(iter);
- }
-
- for (DirectoryTree::const_leaf_reverse_iterator iter = child->leafsRBegin();
- iter != child->leafsREnd(); ++iter) {
- target->addLeaf(*iter);
- }
-}
-
-
-DirectoryTree *BainComplexInstallerDialog::updateTree(DirectoryTree *tree)
-{
- DirectoryTree *newTree = new DirectoryTree;
-
- for (DirectoryTree::const_node_reverse_iterator iter = tree->nodesRBegin();
- iter != tree->nodesREnd();) {
- QList<QListWidgetItem*> items = ui->optionsList->findItems((*iter)->getData().name, Qt::MatchFixedString);
- if ((items.count() == 1) && (items.at(0)->checkState() == Qt::Checked)) {
- moveTreeUp(newTree, *iter);
- }
- iter = tree->erase(iter);
- }
-
- return newTree;
-}
-
-
-void BainComplexInstallerDialog::on_okBtn_clicked()
-{
- this->accept();
-}
-
-
-void BainComplexInstallerDialog::on_cancelBtn_clicked()
-{
- this->reject();
-}
-
-
-void BainComplexInstallerDialog::on_manualBtn_clicked()
-{
- m_Manual = true;
- this->reject();
-}
-
-void BainComplexInstallerDialog::on_packageBtn_clicked()
-{
- TextViewer viewer(this);
- viewer.setDescription("");
- viewer.addFile(QDir::tempPath().append("/package.txt"), false);
- viewer.exec();
-}
+#include "baincomplexinstallerdialog.h"
+#include "textviewer.h"
+#include "ui_baincomplexinstallerdialog.h"
+
+#include <QDir>
+
+BainComplexInstallerDialog::BainComplexInstallerDialog(DirectoryTree *tree, const QString &modName, bool hasPackageTXT, QWidget *parent)
+ : TutorableDialog("BainInstaller", parent), ui(new Ui::BainComplexInstallerDialog), m_Manual(false)
+{
+ ui->setupUi(this);
+
+ ui->nameEdit->setText(modName);
+
+ for (DirectoryTree::const_node_iterator iter = tree->nodesBegin(); iter != tree->nodesEnd(); ++iter) {
+ const QString &dirName = (*iter)->getData().name;
+ if ((dirName.compare("fomod", Qt::CaseInsensitive) == 0) ||
+ (dirName.startsWith("--"))) {
+ continue;
+ }
+
+ QListWidgetItem *item = new QListWidgetItem(ui->optionsList);
+ item->setText((*iter)->getData().name);
+ item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
+ item->setCheckState(item->text().mid(0, 2) == "00" ? Qt::Checked : Qt::Unchecked);
+ item->setData(Qt::UserRole, qVariantFromValue((void*)(*iter)));
+ ui->optionsList->addItem(item);
+ }
+
+ ui->packageBtn->setEnabled(hasPackageTXT);
+}
+
+
+BainComplexInstallerDialog::~BainComplexInstallerDialog()
+{
+ delete ui;
+}
+
+
+QString BainComplexInstallerDialog::getName() const
+{
+ return ui->nameEdit->text();
+}
+
+
+void BainComplexInstallerDialog::moveTreeUp(DirectoryTree *target, DirectoryTree::Node *child)
+{
+ for (DirectoryTree::const_node_iterator iter = child->nodesBegin();
+ iter != child->nodesEnd();) {
+ target->addNode(*iter, true);
+ iter = child->detach(iter);
+ }
+
+ for (DirectoryTree::const_leaf_reverse_iterator iter = child->leafsRBegin();
+ iter != child->leafsREnd(); ++iter) {
+ target->addLeaf(*iter);
+ }
+}
+
+
+DirectoryTree *BainComplexInstallerDialog::updateTree(DirectoryTree *tree)
+{
+ DirectoryTree *newTree = new DirectoryTree;
+
+ for (DirectoryTree::const_node_reverse_iterator iter = tree->nodesRBegin();
+ iter != tree->nodesREnd();) {
+ QList<QListWidgetItem*> items = ui->optionsList->findItems((*iter)->getData().name, Qt::MatchFixedString);
+ if ((items.count() == 1) && (items.at(0)->checkState() == Qt::Checked)) {
+ moveTreeUp(newTree, *iter);
+ }
+ iter = tree->erase(iter);
+ }
+
+ return newTree;
+}
+
+
+void BainComplexInstallerDialog::on_okBtn_clicked()
+{
+ this->accept();
+}
+
+
+void BainComplexInstallerDialog::on_cancelBtn_clicked()
+{
+ this->reject();
+}
+
+
+void BainComplexInstallerDialog::on_manualBtn_clicked()
+{
+ m_Manual = true;
+ this->reject();
+}
+
+void BainComplexInstallerDialog::on_packageBtn_clicked()
+{
+ TextViewer viewer("package.txt", this);
+ viewer.setDescription("");
+ viewer.addFile(QDir::tempPath().append("/package.txt"), false);
+ viewer.exec();
+}
diff --git a/src/csvbuilder.cpp b/src/csvbuilder.cpp
new file mode 100644
index 00000000..fa4218a2
--- /dev/null
+++ b/src/csvbuilder.cpp
@@ -0,0 +1,272 @@
+#include "csvbuilder.h"
+
+
+CSVBuilder::CSVBuilder(QIODevice *target)
+ : m_Out(target), m_Separator(','), m_LineBreak(BREAK_CRLF)
+{
+ m_Out.setCodec("UTF-8");
+
+ m_QuoteMode[TYPE_INTEGER] = QUOTE_NEVER;
+ m_QuoteMode[TYPE_FLOAT] = QUOTE_NEVER;
+ m_QuoteMode[TYPE_STRING] = QUOTE_ONDEMAND;
+}
+
+
+CSVBuilder::~CSVBuilder()
+{
+
+}
+
+
+void CSVBuilder::setFieldSeparator(char sep)
+{
+ char oldSeparator = m_Separator;
+ m_Separator = sep;
+ try {
+ checkFields(m_Fields);
+ } catch (const CSVException&) {
+ m_Separator = oldSeparator;
+ throw;
+ }
+}
+
+
+void CSVBuilder::setLineBreak(CSVBuilder::ELineBreak lineBreak)
+{
+ m_LineBreak = lineBreak;
+}
+
+
+void CSVBuilder::setEscapeMode(CSVBuilder::EFieldType type, CSVBuilder::EQuoteMode mode)
+{
+ m_QuoteMode[type] = mode;
+}
+
+
+void CSVBuilder::setFields(const std::vector<std::pair<QString, EFieldType> > &fields)
+{
+ std::vector<QString> fieldNames;
+ std::map<QString, EFieldType> fieldTypes;
+
+ for (auto iter = fields.begin(); iter != fields.end(); ++iter) {
+ fieldNames.push_back(iter->first);
+ fieldTypes[iter->first] = iter->second;
+ }
+
+ checkFields(fieldNames);
+
+ m_Fields = fieldNames;
+ m_FieldTypes = fieldTypes;
+ m_Defaults.clear();
+ m_RowBuffer.clear();
+
+}
+
+
+void CSVBuilder::checkValue(const QString &field, const QVariant &value)
+{
+ auto typeIter = m_FieldTypes.find(field);
+ if (typeIter == m_FieldTypes.end()) {
+ throw CSVException(QObject::tr("invalid field name \"%1\"").arg(field));
+ }
+
+ switch (typeIter->second) {
+ case TYPE_INTEGER: {
+ if (!value.canConvert<int>()) {
+ throw CSVException(QObject::tr("invalid type for \"%1\" (should be integer)").arg(field));
+ }
+ } break;
+ case TYPE_STRING: {
+ if (!value.canConvert<QString>()) {
+ throw CSVException(QObject::tr("invalid type for \"%1\" (should be string)").arg(field));
+ }
+ } break;
+ case TYPE_FLOAT: {
+ if (!value.canConvert<float>()) {
+ throw CSVException(QObject::tr("invalid type for \"%1\" (should be float)").arg(field));
+ }
+ } break;
+ }
+}
+
+
+void CSVBuilder::setDefault(const QString &field, const QVariant &value)
+{
+ checkValue(field, value);
+ m_Defaults[field] = value;
+}
+
+
+void CSVBuilder::writeHeader()
+{
+ if (m_Fields.size() == 0) {
+ throw CSVException(QObject::tr("no fields set up yet!"));
+ }
+
+ for (auto iter = m_Fields.begin(); iter != m_Fields.end(); ++iter) {
+ if (iter != m_Fields.begin()) {
+ m_Out << m_Separator;
+ }
+ m_Out << *iter;
+ }
+ m_Out << lineBreak();
+ m_Out.flush();
+}
+
+
+void CSVBuilder::setRowField(const QString &field, const QVariant &value)
+{
+ checkValue(field, value);
+ m_RowBuffer[field] = value;
+}
+
+
+void CSVBuilder::writeData(const std::map<QString, QVariant> &data, bool check)
+{
+ QString line;
+ QTextStream temp(&line);
+
+ for (auto iter = m_Fields.begin(); iter != m_Fields.end(); ++iter) {
+ if (iter != m_Fields.begin()) {
+ temp << separator();
+ }
+
+ QVariant val;
+
+ auto valIter = data.find(*iter);
+ if (valIter == data.end()) {
+ auto defaultIter = m_Defaults.find(*iter);
+ if (defaultIter == m_Defaults.end()) {
+ throw CSVException(QObject::tr("field not set \"%1\"").arg(*iter));
+ } else {
+ val = defaultIter->second;
+ }
+ } else {
+ val = valIter->second;
+ }
+
+ if (check) {
+ checkValue(*iter, val);
+ }
+
+ switch (m_FieldTypes[*iter]) {
+ case TYPE_INTEGER: {
+ quoteInsert(temp, val.toInt());
+ } break;
+ case TYPE_FLOAT: {
+ quoteInsert(temp, val.toFloat());
+ } break;
+ case TYPE_STRING: {
+ quoteInsert(temp, val.toString());
+ } break;
+ }
+ }
+ m_Out << line << lineBreak();
+ m_Out.flush();
+}
+
+
+void CSVBuilder::quoteInsert(QTextStream &stream, int value)
+{
+ switch (m_QuoteMode[TYPE_INTEGER]) {
+ case QUOTE_NEVER:
+ case QUOTE_ONDEMAND: {
+ stream << value;
+ } break;
+ case QUOTE_ALWAYS: {
+ stream << "\"" << value << "\"";
+ } break;
+ }
+}
+
+
+void CSVBuilder::quoteInsert(QTextStream &stream, float value)
+{
+ switch (m_QuoteMode[TYPE_FLOAT]) {
+ case QUOTE_NEVER:
+ case QUOTE_ONDEMAND: {
+ stream << value;
+ } break;
+ case QUOTE_ALWAYS: {
+ stream << "\"" << value << "\"";
+ } break;
+ }
+}
+
+
+void CSVBuilder::quoteInsert(QTextStream &stream, const QString &value)
+{
+ switch (m_QuoteMode[TYPE_STRING]) {
+ case QUOTE_NEVER: {
+ stream << value;
+ } break;
+ case QUOTE_ONDEMAND: {
+ if (value.contains("[,\r\n]")) {
+ stream << "\"" << value.mid(0).replace("\"", "\"\"") << "\"";
+ } else {
+ stream << value;
+ }
+ } break;
+ case QUOTE_ALWAYS: {
+ stream << "\"" << value.mid(0).replace("\"", "\"\"") << "\"";
+ } break;
+ }
+}
+
+
+void CSVBuilder::writeRow()
+{
+ writeData(m_RowBuffer, false); // data was tested on input
+ m_RowBuffer.clear();
+}
+
+
+void CSVBuilder::addRow(const std::map<QString, QVariant> &data)
+{
+ writeData(data, true);
+}
+
+
+void CSVBuilder::checkFields(const std::vector<QString> &fields)
+{
+ for (auto iter = fields.begin(); iter != fields.end(); ++iter) {
+ if (iter->contains(m_Separator) ||
+ iter->contains('\r') ||
+ iter->contains('\n') ||
+ iter->contains('"')) {
+ throw CSVException(QObject::tr("invalid character in field \"%1\"").arg(*iter));
+ }
+ if (iter->length() == 0) {
+ throw CSVException(QObject::tr("empty field name"));
+ }
+ }
+}
+
+
+/*
+
+if(cell.contains(KDefaultEscapeChar) || cell.contains(KDefaultNewLine)
+ || cell.contains(KDefaultDelimiter)) {
+ m_currentLine->append(cell.replace(KDefaultNewLine,
+ QString(KDefaultEscapeChar) + KDefaultEscapeChar)
+ .prepend(KDefaultEscapeChar)
+ .append(KDefaultEscapeChar));
+} else {
+m_currentLine->append(cell);
+}*/
+
+
+const char *CSVBuilder::lineBreak()
+{
+ switch (m_LineBreak) {
+ case BREAK_CR: return "\r";
+ case BREAK_CRLF: return "\r\n";
+ case BREAK_LF: return "\n";
+ default: return "\n"; // default shouldn't be necessary
+ }
+}
+
+const char CSVBuilder::separator()
+{
+ return m_Separator;
+}
diff --git a/src/csvbuilder.h b/src/csvbuilder.h
new file mode 100644
index 00000000..f2e0e987
--- /dev/null
+++ b/src/csvbuilder.h
@@ -0,0 +1,93 @@
+#ifndef CSVBUILDER_H
+#define CSVBUILDER_H
+
+
+#include <vector>
+#include <QString>
+#include <QVariant>
+#include <QTextStream>
+
+
+class CSVException : public std::exception {
+
+public:
+ CSVException(const QString &text)
+ : std::exception(), m_Message(text.toLocal8Bit()) {}
+
+ virtual const char* what() const throw()
+ { return m_Message.constData(); }
+private:
+ QByteArray m_Message;
+
+};
+
+
+class CSVBuilder
+{
+
+public:
+
+ enum EFieldType {
+ TYPE_INTEGER,
+ TYPE_STRING,
+ TYPE_FLOAT
+ };
+
+ enum EQuoteMode {
+ QUOTE_NEVER,
+ QUOTE_ONDEMAND,
+ QUOTE_ALWAYS
+ };
+
+ enum ELineBreak {
+ BREAK_LF,
+ BREAK_CRLF,
+ BREAK_CR
+ };
+
+public:
+
+ CSVBuilder(QIODevice *target);
+ ~CSVBuilder();
+
+ void setFieldSeparator(char sep);
+ void setLineBreak(ELineBreak lineBreak);
+ void setEscapeMode(EFieldType type, EQuoteMode mode);
+ void setFields(const std::vector<std::pair<QString, EFieldType> > &fields);
+ void setDefault(const QString &field, const QVariant &value);
+
+ void writeHeader();
+
+ void setRowField(const QString &field, const QVariant &value);
+ void writeRow();
+
+ void addRow(const std::map<QString, QVariant> &data);
+
+private:
+
+ const char *lineBreak();
+ const char separator();
+
+ void fieldValid();
+ void checkFields(const std::vector<QString> &fields);
+ void checkValue(const QString &field, const QVariant &value);
+ void writeData(const std::map<QString, QVariant> &data, bool check);
+
+ void quoteInsert(QTextStream &stream, int value);
+ void quoteInsert(QTextStream &stream, float value);
+ void quoteInsert(QTextStream &stream, const QString &value);
+
+private:
+
+ QTextStream m_Out;
+ char m_Separator;
+ ELineBreak m_LineBreak;
+ std::map<EFieldType, EQuoteMode> m_QuoteMode;
+ std::vector<QString> m_Fields;
+ std::map<QString, EFieldType> m_FieldTypes;
+ std::map<QString, QVariant> m_Defaults;
+ std::map<QString, QVariant> m_RowBuffer;
+
+};
+
+#endif // CSVBUILDER_H
diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp
index a08490e6..2e756e5d 100644
--- a/src/downloadmanager.cpp
+++ b/src/downloadmanager.cpp
@@ -17,912 +17,911 @@ 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 "utility.h"
-#include "json.h"
-#include "selectiondialog.h"
-#include <QTimer>
-#include <QFileInfo>
-#include <QRegExp>
-#include <QDirIterator>
-#include <QSettings>
-#include <QInputDialog>
-#include <regex>
-#include <QMessageBox>
-
-
-using QtJson::Json;
-
-
-// TODO limit number of downloads, also display download during nxm requests, store modid/fileid with downloads
-
-
-static const char UNFINISHED[] = ".unfinished";
-
-
-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"));
- }
- }
- m_Output.setFileName(newName);
-}
-
-
-DownloadManager::DownloadManager(NexusInterface *nexusInterface, QObject *parent)
- : QObject(parent), m_NexusInterface(nexusInterface), m_DirWatcher()
-{
- 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;
- }
-}
-
-
-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::setOutputDirectory(const QString &outputDirectory)
-{
- QStringList directories = m_DirWatcher.directories();
- if (directories.length() != 0) {
- m_DirWatcher.removePaths(directories);
- }
- m_OutputDirectory = QDir::fromNativeSeparators(outputDirectory);
- m_DirWatcher.addPath(m_OutputDirectory);
- refreshList();
-}
-
-void DownloadManager::refreshList()
-{
- // 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)) {
- delete *Iter;
- Iter = m_ActiveDownloads.erase(Iter);
- } else {
-//qDebug("leaving %s / %s in the download list since it's still being processed",
-// qPrintable((*Iter)->m_FileName), qPrintable((*Iter)->m_Output.fileName()));
- ++Iter;
- }
- }
-
- QStringList nameFilters;
- nameFilters.append("*.rar");
- nameFilters.append("*.zip");
- nameFilters.append("*.7z");
- nameFilters.append("*.fomod");
- nameFilters.append(QString("*").append(UNFINISHED));
-
- QDir dir(QDir::fromNativeSeparators(m_OutputDirectory));
-
- // 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) {
-//qDebug("%s found in m_Output: %s", qPrintable(file), qPrintable((*Iter)->m_Output.fileName()));
- Exists = true;
- }
- }
- if (Exists) {
- continue;
- }
- DownloadInfo *info = new DownloadInfo;
- info->m_State = STATE_READY;
- if (file.endsWith(UNFINISHED)) {
- info->m_FileName = file.mid(0, file.length() - strlen(UNFINISHED));
- info->m_State = STATE_PAUSED;
- } else {
- info->m_FileName = file;
- }
- info->m_Output.setFileName(QString("%1/%2").arg(QDir::fromNativeSeparators(m_OutputDirectory)).arg(file));
-
- QString metaFileName = QString("%1.meta").arg(info->m_Output.fileName());
- QSettings metaFile(metaFileName, QSettings::IniFormat);
- if (metaFile.value("removed", false).toBool()) {
- continue;
- }
- info->m_ModID = metaFile.value("modID", 0).toInt();
- info->m_FileID = metaFile.value("fileID", 0).toInt();
- info->m_Url = metaFile.value("url", "").toString();
- info->m_NexusInfo.m_Name = metaFile.value("name", 0).toString();
- info->m_NexusInfo.m_ModName = metaFile.value("modName", "").toString();
- info->m_NexusInfo.m_Version = metaFile.value("version", 0).toString();
- info->m_NexusInfo.m_NewestVersion = metaFile.value("newestVersion", "").toString();
- info->m_NexusInfo.m_Category = metaFile.value("category", 0).toInt();
- if (metaFile.value("installed", false).toBool()) {
- info->m_State = STATE_INSTALLED;
- }
- if (metaFile.value("paused", false).toBool()) {
- info->m_State = STATE_PAUSED;
- }
- m_ActiveDownloads.push_front(info);
- }
- qDebug("downloads after refresh: %d", m_ActiveDownloads.size());
- emit update(-1);
-}
-
-
-bool DownloadManager::addDownload(const QUrl &url, int modID, int fileID, const NexusInfo &nexusInfo)
-{
- QString fileName = QFileInfo(url.path()).fileName();
- if (fileName.isEmpty()) {
- fileName = "unknown";
- }
-
- QNetworkRequest request(url);
- return addDownload(m_NexusInterface->getAccessManager()->get(request), fileName, modID, fileID, nexusInfo);
-}
-
-bool DownloadManager::addDownload(QNetworkReply *reply, const QString &fileName, int modID,
- int fileID, const NexusInfo &nexusInfo)
-{
- // download invoked from an already open network reply (i.e. download link in the browser)
- DownloadInfo *newDownload = new DownloadInfo;
- newDownload->m_StartTime.start();
- newDownload->m_Progress = 0;
- newDownload->m_ResumePos = 0;
- newDownload->m_ModID = modID;
- newDownload->m_FileID = fileID;
- newDownload->m_NexusInfo = nexusInfo;
-
- QString baseName = fileName;
-
- if (!nexusInfo.m_FileName.isEmpty()) {
- baseName = nexusInfo.m_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)) {
- delete newDownload;
- return false;
- }
-
- newDownload->setName(getDownloadFileName(baseName), false);
-
- addDownload(reply, newDownload, false);
-
- emit update(-1);
- return true;
-}
-
-
-void DownloadManager::addDownload(QNetworkReply *reply, DownloadInfo *newDownload, bool resume)
-{
- newDownload->m_Reply = reply;
- newDownload->m_State = STATE_DOWNLOADING;
- newDownload->m_Url = reply->url().toString();
-
- QIODevice::OpenMode mode = QIODevice::WriteOnly;
- if (resume) {
- mode |= QIODevice::Append;
- }
-
- 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(readyRead()), this, SLOT(downloadReadyRead()));
- connect(newDownload->m_Reply, SIGNAL(metaDataChanged()), this, SLOT(metaDataChanged()));
-
- if (!resume) {
- emit aboutToUpdate();
-
- m_ActiveDownloads.append(newDownload);
-// qDebug("downloads after add: %d", m_ActiveDownloads.size());
-
- emit update(-1);
- }
-}
-
-
-void DownloadManager::addNXMDownload(const QString &url)
-{
- NXMUrl nxmInfo(url);
- m_RequestIDs.insert(m_NexusInterface->requestFileInfo(nxmInfo.getModId(), nxmInfo.getFileId(), this, QVariant()));
-}
-
-
-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) {
- filePath.append(UNFINISHED);
- }
-
- if (deleteFile) {
- if (!QFile(filePath).remove()) {
- reportError(tr("failed to delete %1").arg(filePath));
- return;
- }
-
- QFile metaFile(filePath.append(".meta"));
- if (metaFile.exists() && !metaFile.remove()) {
- 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::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[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[index]->m_State == STATE_DOWNLOADING) {
- m_ActiveDownloads[index]->m_State = STATE_CANCELING;
- qDebug("canceled %d - %s", index, m_ActiveDownloads[index]->m_FileName.toUtf8().constData());
- }
-}
-
-
-void DownloadManager::pauseDownload(int index)
-{
- if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- reportError(tr("invalid index %1").arg(index));
- return;
- }
-
- if (m_ActiveDownloads[index]->m_State == STATE_DOWNLOADING) {
- m_ActiveDownloads[index]->m_State = STATE_PAUSING;
- qDebug("pausing %d - %s", index, m_ActiveDownloads[index]->m_FileName.toUtf8().constData());
- }
-}
-
-
-void DownloadManager::resumeDownload(int index)
-{
- if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- reportError(tr("invalid index %1").arg(index));
- return;
- }
- if (m_ActiveDownloads[index]->m_State == STATE_PAUSED) {
- DownloadInfo *info = m_ActiveDownloads[index];
- qDebug("request resume from url %s", info->m_Url.toUtf8().constData());
- QNetworkRequest request(info->m_Url);
- info->m_ResumePos = info->m_Output.size();
- QByteArray rangeHeader = "bytes=" + QByteArray::number(info->m_ResumePos) + "-";
- request.setRawHeader("Range", rangeHeader);
- addDownload(m_NexusInterface->getAccessManager()->get(request), info, true);
- }
- emit update(index);
-}
-
-
-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_ModID == 0UL) {
- QString fileName = getFileName(index);
- QString ignore;
- NexusInterface::interpretNexusFileName(fileName, ignore, info->m_ModID, true);
- if (info->m_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_ModID = modIDString.toInt(NULL, 10);
- }
- }
- m_RequestIDs.insert(m_NexusInterface->requestFiles(info->m_ModID, this, qVariantFromValue(static_cast<void*>(info))));
-}
-
-
-int DownloadManager::numTotalDownloads() const
-{
- return m_ActiveDownloads.size();
-}
-
-
-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::getFileName(int index) const
-{
- if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
- }
-
- return m_ActiveDownloads.at(index)->m_FileName;
-}
-
-
-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);
- return (info->m_FileID == 0) || (info->m_ModID == 0) || info->m_NexusInfo.m_Version.isEmpty();
-}
-
-
-int DownloadManager::getModID(int index) const
-{
- if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
- }
-
- return m_ActiveDownloads.at(index)->m_ModID;
-}
-
-
-NexusInfo DownloadManager::getNexusInfo(int index) const
-{
- if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
- }
-
- return m_ActiveDownloads.at(index)->m_NexusInfo;
-}
-
-
-void DownloadManager::markInstalled(int index)
-{
- if ((index < 0) || (index >= m_ActiveDownloads.size())) {
- throw MyException(tr("invalid index"));
- }
- QSettings metaFile(QString("%1.meta").arg(m_ActiveDownloads.at(index)->m_Output.fileName()), QSettings::IniFormat);
- metaFile.setValue("installed", true);
-
- m_ActiveDownloads.at(index)->m_State = STATE_INSTALLED;
-}
-
-
-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();
-}
-
-
-DownloadManager::DownloadInfo *DownloadManager::findDownload(QObject *reply, int *index) const
-{
- for (int i = 0; i < m_ActiveDownloads.size(); ++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;
-
- DownloadInfo *info = findDownload(this->sender(), &index);
- if (info != NULL) {
- if (info->m_State == STATE_CANCELING) {
- info->m_Reply->abort();
- info->m_State = STATE_CANCELED;
- } else if (info->m_State == STATE_PAUSING) {
- info->m_Reply->abort();
- info->m_State = STATE_PAUSED;
- } else {
- info->m_Progress = ((info->m_ResumePos + bytesReceived) * 100) / (info->m_ResumePos + bytesTotal);
- emit update(index);
- }
- }
-}
-
-
-void DownloadManager::downloadReadyRead()
-{
- DownloadInfo *info = findDownload(this->sender());
- if (info != NULL) {
- info->m_Output.write(info->m_Reply->readAll());
- }
-}
-
-
-void DownloadManager::createMetaFile(DownloadInfo *info)
-{
- QSettings metaFile(QString("%1.meta").arg(info->m_Output.fileName()), QSettings::IniFormat);
- metaFile.setValue("modID", info->m_ModID);
- metaFile.setValue("fileID", info->m_FileID);
- metaFile.setValue("url", info->m_Url);
- metaFile.setValue("name", info->m_NexusInfo.m_Name);
- metaFile.setValue("modName", info->m_NexusInfo.m_ModName);
- metaFile.setValue("version", info->m_NexusInfo.m_Version);
- metaFile.setValue("newestVersion", info->m_NexusInfo.m_NewestVersion);
- metaFile.setValue("category", info->m_NexusInfo.m_Category);
- metaFile.setValue("installed", info->m_State == DownloadManager::STATE_INSTALLED);
- metaFile.setValue("paused", info->m_State == DownloadManager::STATE_PAUSED);
-
- // 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 = static_cast<DownloadInfo*>(userData.value<void*>());
- info->m_NexusInfo.m_Category = result["category_id"].toInt();
- info->m_NexusInfo.m_ModName = result["name"].toString().trimmed();
- info->m_NexusInfo.m_NewestVersion = result["version"].toString();
-
- if (info->m_FileID != 0) {
- createMetaFile(info);
- info->m_State = STATE_READY;
- } else {
- info->m_State = STATE_FETCHINGFILEINFO;
- m_RequestIDs.insert(m_NexusInterface->requestFiles(info->m_ModID, this, qVariantFromValue(static_cast<void*>(info))));
- }
-}
-
-
-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 = static_cast<DownloadInfo*>(userData.value<void*>());
-
- 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_NexusInfo.m_Name = fileInfo["name"].toString();
- info->m_NexusInfo.m_Version = fileInfo["version"].toString();
- info->m_NexusInfo.m_FileCategory = fileInfo["category_id"].toInt();
- info->m_FileID = fileInfo["id"].toInt();
- found = true;
- break;
- }
- }
-
- if ((info->m_State == STATE_READY) ||
- (info->m_State == STATE_INSTALLED)) {
- 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_NexusInfo.m_Name = fileInfo["name"].toString();
- info->m_NexusInfo.m_Version = fileInfo["version"].toString();
- info->m_NexusInfo.m_FileCategory = fileInfo["category_id"].toInt();
- info->m_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_FileID == 0) {
- qWarning("could not determine file id for %s", info->m_FileName.toUtf8().constData());
- }
-
- info->m_State = STATE_READY;
- }
- createMetaFile(info);
-}
-
-
-void DownloadManager::nxmFileInfoAvailable(int modID, int fileID, 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);
- }
-
- NexusInfo info;
-
- QVariantMap result = resultData.toMap();
-
- info.m_Name = result["name"].toString();
- info.m_Version = result["version"].toString();
- info.m_FileName = result["uri"].toString();
-
- m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(modID, fileID, this, qVariantFromValue(info)));
-}
-
-
-// sort function to sort by best download server
-bool DownloadManager::ServerByPreference(const QVariant &LHS, const QVariant &RHS)
-{
- QVariantMap LHSMap = LHS.toMap();
- QVariantMap RHSMap = RHS.toMap();
- int LHSUsers = LHSMap["ConnectedUsers"].toInt();
- int RHSUsers = RHSMap["ConnectedUsers"].toInt();
- bool LHSPremium = LHSMap["IsPremium"].toBool();
- bool RHSPremium = RHSMap["IsPremium"].toBool();
-
- // 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) && (RHSUsers != 0)) return false;
- if ((LHSUsers != 0) && (RHSUsers == 0)) return true;
-
-
- if (LHSPremium && !RHSPremium) {
- return true;
- } else if (!LHSPremium && RHSPremium) {
- return false;
- }
-
- // TODO implement country preference
-
- return LHSUsers < RHSUsers;
-}
-
-
-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);
- }
-
- NexusInfo info = userData.value<NexusInfo>();
- QVariantList resultList = resultData.toList();
- if (resultList.length() == 0) {
- emit showMessage(tr("No download server available. Please try again later."));
- return;
- }
- qSort(resultList.begin(), resultList.end(), ServerByPreference);
-
- QVariantMap dlServer = resultList.first().toMap();
- qDebug("picked url: %s", dlServer["URI"].toString().toUtf8().constData());
-
-
- addDownload(dlServer["URI"].toString(), modID, fileID, info);
-}
-
-
-void DownloadManager::nxmRequestFailed(int modID, QVariant, 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_ModID == modID) {
- if (info->m_State == STATE_STARTED) {
- m_ActiveDownloads.erase(iter);
- delete info;
- } else if ((info->m_State == STATE_FETCHINGFILEINFO) ||
- (info->m_State == STATE_FETCHINGMODINFO)) {
- info->m_State = STATE_READY;
- }
- emit update(index);
- break;
- }
- }
- 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 = info->m_Reply->readAll();
- qDebug("finished %s (%d) (%d)", info->m_FileName.toUtf8().constData(), reply->error(), info->m_State);
- info->m_Output.write(data);
- info->m_Output.close();
-
- if ((info->m_State != STATE_CANCELING) &&
- (info->m_State != STATE_PAUSING)) {
- if ((info->m_Output.size() == 0) ||
- (reply->error() != QNetworkReply::NoError) ||
- reply->header(QNetworkRequest::ContentTypeHeader).toString().startsWith("text", Qt::CaseInsensitive)) {
- emit showMessage(tr("Download failed: %1 (%2)").arg(reply->errorString()).arg(reply->error()));
- info->m_State = STATE_CANCELING;
- }
- }
-
- if (info->m_State == STATE_CANCELING) {
- info->m_Reply->abort();
- info->m_State = STATE_CANCELED;
- } else if (info->m_State == STATE_PAUSING) {
- info->m_Output.write(info->m_Reply->readAll());
- info->m_Reply->abort();
-
- info->m_State = 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->m_State == STATE_PAUSED) {
- info->m_Output.close();
- createMetaFile(info);
- emit update(index);
- } else {
- info->m_State = STATE_FETCHINGMODINFO;
- 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
- }
-
- m_RequestIDs.insert(m_NexusInterface->requestDescription(info->m_ModID, this, qVariantFromValue(static_cast<void*>(info))));
-
- emit update(index);
- }
- reply->close();
- reply->deleteLater();
- } else {
- qWarning("no download index %d", index);
- }
-}
-
-
-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.open(QIODevice::WriteOnly | QIODevice::Append)) {
- reportError(tr("failed to re-open %1").arg(info->m_FileName));
- info->m_State = STATE_CANCELING;
- info->m_Reply->abort();
- }
- }
- } else {
- qWarning("meta data event for unknown download");
- }
-}
-
-void DownloadManager::directoryChanged(const QString&)
-{
- refreshList();
-}
+#include "downloadmanager.h"
+#include "report.h"
+#include "nxmurl.h"
+#include <gameinfo.h>
+#include "utility.h"
+#include "json.h"
+#include "selectiondialog.h"
+#include <QTimer>
+#include <QFileInfo>
+#include <QRegExp>
+#include <QDirIterator>
+#include <QSettings>
+#include <QInputDialog>
+#include <regex>
+#include <QMessageBox>
+
+
+using QtJson::Json;
+
+
+// TODO limit number of downloads, also display download during nxm requests, store modid/fileid with downloads
+
+
+static const char UNFINISHED[] = ".unfinished";
+
+
+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"));
+ }
+ }
+ m_Output.setFileName(newName);
+}
+
+
+DownloadManager::DownloadManager(NexusInterface *nexusInterface, QObject *parent)
+ : QObject(parent), m_NexusInterface(nexusInterface), m_DirWatcher()
+{
+ 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;
+ }
+}
+
+
+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::setOutputDirectory(const QString &outputDirectory)
+{
+ QStringList directories = m_DirWatcher.directories();
+ if (directories.length() != 0) {
+ m_DirWatcher.removePaths(directories);
+ }
+ m_OutputDirectory = QDir::fromNativeSeparators(outputDirectory);
+ m_DirWatcher.addPath(m_OutputDirectory);
+ refreshList();
+}
+
+void DownloadManager::refreshList()
+{
+ // 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)) {
+ delete *Iter;
+ Iter = m_ActiveDownloads.erase(Iter);
+ } else {
+//qDebug("leaving %s / %s in the download list since it's still being processed",
+// qPrintable((*Iter)->m_FileName), qPrintable((*Iter)->m_Output.fileName()));
+ ++Iter;
+ }
+ }
+
+ QStringList nameFilters;
+ nameFilters.append("*.rar");
+ nameFilters.append("*.zip");
+ nameFilters.append("*.7z");
+ nameFilters.append("*.fomod");
+ nameFilters.append(QString("*").append(UNFINISHED));
+
+ QDir dir(QDir::fromNativeSeparators(m_OutputDirectory));
+
+ // 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) {
+//qDebug("%s found in m_Output: %s", qPrintable(file), qPrintable((*Iter)->m_Output.fileName()));
+ Exists = true;
+ }
+ }
+ if (Exists) {
+ continue;
+ }
+ DownloadInfo *info = new DownloadInfo;
+ info->m_State = STATE_READY;
+ if (file.endsWith(UNFINISHED)) {
+ info->m_FileName = file.mid(0, file.length() - strlen(UNFINISHED));
+ info->m_State = STATE_PAUSED;
+ } else {
+ info->m_FileName = file;
+ }
+ info->m_Output.setFileName(QString("%1/%2").arg(QDir::fromNativeSeparators(m_OutputDirectory)).arg(file));
+
+ QString metaFileName = QString("%1.meta").arg(info->m_Output.fileName());
+ QSettings metaFile(metaFileName, QSettings::IniFormat);
+ if (metaFile.value("removed", false).toBool()) {
+ continue;
+ }
+ info->m_ModID = metaFile.value("modID", 0).toInt();
+ info->m_FileID = metaFile.value("fileID", 0).toInt();
+ info->m_Url = metaFile.value("url", "").toString();
+ info->m_NexusInfo.m_Name = metaFile.value("name", 0).toString();
+ info->m_NexusInfo.m_ModName = metaFile.value("modName", "").toString();
+ info->m_NexusInfo.m_Version = metaFile.value("version", 0).toString();
+ info->m_NexusInfo.m_NewestVersion = metaFile.value("newestVersion", "").toString();
+ info->m_NexusInfo.m_Category = metaFile.value("category", 0).toInt();
+ if (metaFile.value("installed", false).toBool()) {
+ info->m_State = STATE_INSTALLED;
+ }
+ if (metaFile.value("paused", false).toBool()) {
+ info->m_State = STATE_PAUSED;
+ }
+ m_ActiveDownloads.push_front(info);
+ }
+ qDebug("downloads after refresh: %d", m_ActiveDownloads.size());
+ emit update(-1);
+}
+
+
+bool DownloadManager::addDownload(const QUrl &url, int modID, int fileID, const NexusInfo &nexusInfo)
+{
+ QString fileName = QFileInfo(url.path()).fileName();
+ if (fileName.isEmpty()) {
+ fileName = "unknown";
+ }
+
+ QNetworkRequest request(url);
+ return addDownload(m_NexusInterface->getAccessManager()->get(request), fileName, modID, fileID, nexusInfo);
+}
+
+bool DownloadManager::addDownload(QNetworkReply *reply, const QString &fileName, int modID,
+ int fileID, const NexusInfo &nexusInfo)
+{
+ // download invoked from an already open network reply (i.e. download link in the browser)
+ DownloadInfo *newDownload = new DownloadInfo;
+ newDownload->m_StartTime.start();
+ newDownload->m_Progress = 0;
+ newDownload->m_ResumePos = 0;
+ newDownload->m_ModID = modID;
+ newDownload->m_FileID = fileID;
+ newDownload->m_NexusInfo = nexusInfo;
+
+ QString baseName = fileName;
+
+ if (!nexusInfo.m_FileName.isEmpty()) {
+ baseName = nexusInfo.m_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)) {
+ delete newDownload;
+ return false;
+ }
+
+ newDownload->setName(getDownloadFileName(baseName), false);
+
+ addDownload(reply, newDownload, false);
+
+ emit update(-1);
+ return true;
+}
+
+
+void DownloadManager::addDownload(QNetworkReply *reply, DownloadInfo *newDownload, bool resume)
+{
+ newDownload->m_Reply = reply;
+ newDownload->m_State = STATE_DOWNLOADING;
+ newDownload->m_Url = reply->url().toString();
+
+ QIODevice::OpenMode mode = QIODevice::WriteOnly;
+ if (resume) {
+ mode |= QIODevice::Append;
+ }
+
+ 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(readyRead()), this, SLOT(downloadReadyRead()));
+ connect(newDownload->m_Reply, SIGNAL(metaDataChanged()), this, SLOT(metaDataChanged()));
+
+ if (!resume) {
+ emit aboutToUpdate();
+
+ m_ActiveDownloads.append(newDownload);
+// qDebug("downloads after add: %d", m_ActiveDownloads.size());
+
+ emit update(-1);
+ }
+}
+
+
+void DownloadManager::addNXMDownload(const QString &url)
+{
+ NXMUrl nxmInfo(url);
+ m_RequestIDs.insert(m_NexusInterface->requestFileInfo(nxmInfo.getModId(), nxmInfo.getFileId(), this, QVariant()));
+}
+
+
+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) {
+ filePath.append(UNFINISHED);
+ }
+
+ if (deleteFile) {
+ if (!QFile(filePath).remove()) {
+ reportError(tr("failed to delete %1").arg(filePath));
+ return;
+ }
+
+ QFile metaFile(filePath.append(".meta"));
+ if (metaFile.exists() && !metaFile.remove()) {
+ 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::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[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[index]->m_State == STATE_DOWNLOADING) {
+ m_ActiveDownloads[index]->m_State = STATE_CANCELING;
+ qDebug("canceled %d - %s", index, m_ActiveDownloads[index]->m_FileName.toUtf8().constData());
+ }
+}
+
+
+void DownloadManager::pauseDownload(int index)
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ reportError(tr("invalid index %1").arg(index));
+ return;
+ }
+
+ if (m_ActiveDownloads[index]->m_State == STATE_DOWNLOADING) {
+ m_ActiveDownloads[index]->m_State = STATE_PAUSING;
+ qDebug("pausing %d - %s", index, m_ActiveDownloads[index]->m_FileName.toUtf8().constData());
+ }
+}
+
+
+void DownloadManager::resumeDownload(int index)
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ reportError(tr("invalid index %1").arg(index));
+ return;
+ }
+ if (m_ActiveDownloads[index]->m_State == STATE_PAUSED) {
+ DownloadInfo *info = m_ActiveDownloads[index];
+ qDebug("request resume from url %s", info->m_Url.toUtf8().constData());
+ QNetworkRequest request(info->m_Url);
+ info->m_ResumePos = info->m_Output.size();
+ QByteArray rangeHeader = "bytes=" + QByteArray::number(info->m_ResumePos) + "-";
+ request.setRawHeader("Range", rangeHeader);
+ addDownload(m_NexusInterface->getAccessManager()->get(request), info, true);
+ }
+ emit update(index);
+}
+
+
+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_ModID == 0UL) {
+ QString fileName = getFileName(index);
+ QString ignore;
+ NexusInterface::interpretNexusFileName(fileName, ignore, info->m_ModID, true);
+ if (info->m_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_ModID = modIDString.toInt(NULL, 10);
+ }
+ }
+ m_RequestIDs.insert(m_NexusInterface->requestFiles(info->m_ModID, this, qVariantFromValue(static_cast<void*>(info))));
+}
+
+
+int DownloadManager::numTotalDownloads() const
+{
+ return m_ActiveDownloads.size();
+}
+
+
+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::getFileName(int index) const
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ throw MyException(tr("invalid index"));
+ }
+
+ return m_ActiveDownloads.at(index)->m_FileName;
+}
+
+
+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);
+ return (info->m_FileID == 0) || (info->m_ModID == 0) || info->m_NexusInfo.m_Version.isEmpty();
+}
+
+
+int DownloadManager::getModID(int index) const
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ throw MyException(tr("invalid index"));
+ }
+ return m_ActiveDownloads.at(index)->m_ModID;
+}
+
+
+NexusInfo DownloadManager::getNexusInfo(int index) const
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ throw MyException(tr("invalid index"));
+ }
+
+ return m_ActiveDownloads.at(index)->m_NexusInfo;
+}
+
+
+void DownloadManager::markInstalled(int index)
+{
+ if ((index < 0) || (index >= m_ActiveDownloads.size())) {
+ throw MyException(tr("invalid index"));
+ }
+ QSettings metaFile(QString("%1.meta").arg(m_ActiveDownloads.at(index)->m_Output.fileName()), QSettings::IniFormat);
+ metaFile.setValue("installed", true);
+
+ m_ActiveDownloads.at(index)->m_State = STATE_INSTALLED;
+}
+
+
+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();
+}
+
+
+DownloadManager::DownloadInfo *DownloadManager::findDownload(QObject *reply, int *index) const
+{
+ for (int i = 0; i < m_ActiveDownloads.size(); ++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;
+
+ DownloadInfo *info = findDownload(this->sender(), &index);
+ if (info != NULL) {
+ if (info->m_State == STATE_CANCELING) {
+ info->m_Reply->abort();
+ info->m_State = STATE_CANCELED;
+ } else if (info->m_State == STATE_PAUSING) {
+ info->m_Reply->abort();
+ info->m_State = STATE_PAUSED;
+ } else {
+ info->m_Progress = ((info->m_ResumePos + bytesReceived) * 100) / (info->m_ResumePos + bytesTotal);
+ emit update(index);
+ }
+ }
+}
+
+
+void DownloadManager::downloadReadyRead()
+{
+ DownloadInfo *info = findDownload(this->sender());
+ if (info != NULL) {
+ info->m_Output.write(info->m_Reply->readAll());
+ }
+}
+
+
+void DownloadManager::createMetaFile(DownloadInfo *info)
+{
+ QSettings metaFile(QString("%1.meta").arg(info->m_Output.fileName()), QSettings::IniFormat);
+ metaFile.setValue("modID", info->m_ModID);
+ metaFile.setValue("fileID", info->m_FileID);
+ metaFile.setValue("url", info->m_Url);
+ metaFile.setValue("name", info->m_NexusInfo.m_Name);
+ metaFile.setValue("modName", info->m_NexusInfo.m_ModName);
+ metaFile.setValue("version", info->m_NexusInfo.m_Version);
+ metaFile.setValue("newestVersion", info->m_NexusInfo.m_NewestVersion);
+ metaFile.setValue("category", info->m_NexusInfo.m_Category);
+ metaFile.setValue("installed", info->m_State == DownloadManager::STATE_INSTALLED);
+ metaFile.setValue("paused", info->m_State == DownloadManager::STATE_PAUSED);
+
+ // 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 = static_cast<DownloadInfo*>(userData.value<void*>());
+ info->m_NexusInfo.m_Category = result["category_id"].toInt();
+ info->m_NexusInfo.m_ModName = result["name"].toString().trimmed();
+ info->m_NexusInfo.m_NewestVersion = result["version"].toString();
+
+ if (info->m_FileID != 0) {
+ createMetaFile(info);
+ info->m_State = STATE_READY;
+ } else {
+ info->m_State = STATE_FETCHINGFILEINFO;
+ m_RequestIDs.insert(m_NexusInterface->requestFiles(info->m_ModID, this, qVariantFromValue(static_cast<void*>(info))));
+ }
+}
+
+
+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 = static_cast<DownloadInfo*>(userData.value<void*>());
+
+ 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_NexusInfo.m_Name = fileInfo["name"].toString();
+ info->m_NexusInfo.m_Version = fileInfo["version"].toString();
+ info->m_NexusInfo.m_FileCategory = fileInfo["category_id"].toInt();
+ info->m_FileID = fileInfo["id"].toInt();
+ found = true;
+ break;
+ }
+ }
+
+ if ((info->m_State == STATE_READY) ||
+ (info->m_State == STATE_INSTALLED)) {
+ 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_NexusInfo.m_Name = fileInfo["name"].toString();
+ info->m_NexusInfo.m_Version = fileInfo["version"].toString();
+ info->m_NexusInfo.m_FileCategory = fileInfo["category_id"].toInt();
+ info->m_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_FileID == 0) {
+ qWarning("could not determine file id for %s", info->m_FileName.toUtf8().constData());
+ }
+
+ info->m_State = STATE_READY;
+ }
+ createMetaFile(info);
+}
+
+
+void DownloadManager::nxmFileInfoAvailable(int modID, int fileID, 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);
+ }
+
+ NexusInfo info;
+
+ QVariantMap result = resultData.toMap();
+
+ info.m_Name = result["name"].toString();
+ info.m_Version = result["version"].toString();
+ info.m_FileName = result["uri"].toString();
+
+ m_RequestIDs.insert(m_NexusInterface->requestDownloadURL(modID, fileID, this, qVariantFromValue(info)));
+}
+
+
+// sort function to sort by best download server
+bool DownloadManager::ServerByPreference(const QVariant &LHS, const QVariant &RHS)
+{
+ QVariantMap LHSMap = LHS.toMap();
+ QVariantMap RHSMap = RHS.toMap();
+ int LHSUsers = LHSMap["ConnectedUsers"].toInt();
+ int RHSUsers = RHSMap["ConnectedUsers"].toInt();
+ bool LHSPremium = LHSMap["IsPremium"].toBool();
+ bool RHSPremium = RHSMap["IsPremium"].toBool();
+
+ // 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) && (RHSUsers != 0)) return false;
+ if ((LHSUsers != 0) && (RHSUsers == 0)) return true;
+
+
+ if (LHSPremium && !RHSPremium) {
+ return true;
+ } else if (!LHSPremium && RHSPremium) {
+ return false;
+ }
+
+ // TODO implement country preference
+
+ return LHSUsers < RHSUsers;
+}
+
+
+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);
+ }
+
+ NexusInfo info = userData.value<NexusInfo>();
+ QVariantList resultList = resultData.toList();
+ if (resultList.length() == 0) {
+ emit showMessage(tr("No download server available. Please try again later."));
+ return;
+ }
+ qSort(resultList.begin(), resultList.end(), ServerByPreference);
+
+ QVariantMap dlServer = resultList.first().toMap();
+ qDebug("picked url: %s", dlServer["URI"].toString().toUtf8().constData());
+
+
+ addDownload(dlServer["URI"].toString(), modID, fileID, info);
+}
+
+
+void DownloadManager::nxmRequestFailed(int modID, QVariant, 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_ModID == modID) {
+ if (info->m_State == STATE_STARTED) {
+ m_ActiveDownloads.erase(iter);
+ delete info;
+ } else if ((info->m_State == STATE_FETCHINGFILEINFO) ||
+ (info->m_State == STATE_FETCHINGMODINFO)) {
+ info->m_State = STATE_READY;
+ }
+ emit update(index);
+ break;
+ }
+ }
+ 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 = info->m_Reply->readAll();
+ qDebug("finished %s (%d) (%d)", info->m_FileName.toUtf8().constData(), reply->error(), info->m_State);
+ info->m_Output.write(data);
+ info->m_Output.close();
+
+ if ((info->m_State != STATE_CANCELING) &&
+ (info->m_State != STATE_PAUSING)) {
+ if ((info->m_Output.size() == 0) ||
+ (reply->error() != QNetworkReply::NoError) ||
+ reply->header(QNetworkRequest::ContentTypeHeader).toString().startsWith("text", Qt::CaseInsensitive)) {
+ emit showMessage(tr("Download failed: %1 (%2)").arg(reply->errorString()).arg(reply->error()));
+ info->m_State = STATE_CANCELING;
+ }
+ }
+
+ if (info->m_State == STATE_CANCELING) {
+ info->m_Reply->abort();
+ info->m_State = STATE_CANCELED;
+ } else if (info->m_State == STATE_PAUSING) {
+ info->m_Output.write(info->m_Reply->readAll());
+ info->m_Reply->abort();
+
+ info->m_State = 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->m_State == STATE_PAUSED) {
+ info->m_Output.close();
+ createMetaFile(info);
+ emit update(index);
+ } else {
+ info->m_State = STATE_FETCHINGMODINFO;
+ 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
+ }
+
+ m_RequestIDs.insert(m_NexusInterface->requestDescription(info->m_ModID, this, qVariantFromValue(static_cast<void*>(info))));
+
+ emit update(index);
+ }
+ reply->close();
+ reply->deleteLater();
+ } else {
+ qWarning("no download index %d", index);
+ }
+}
+
+
+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.open(QIODevice::WriteOnly | QIODevice::Append)) {
+ reportError(tr("failed to re-open %1").arg(info->m_FileName));
+ info->m_State = STATE_CANCELING;
+ info->m_Reply->abort();
+ }
+ }
+ } else {
+ qWarning("meta data event for unknown download");
+ }
+}
+
+void DownloadManager::directoryChanged(const QString&)
+{
+ refreshList();
+}
diff --git a/src/editexecutablesdialog.cpp b/src/editexecutablesdialog.cpp
index a364c65d..419d1395 100644
--- a/src/editexecutablesdialog.cpp
+++ b/src/editexecutablesdialog.cpp
@@ -100,7 +100,8 @@ void EditExecutablesDialog::on_addButton_clicked()
m_ExecutablesList.addExecutable(titleEdit->text(), QDir::fromNativeSeparators(binaryEdit->text()),
argumentsEdit->text(), QDir::fromNativeSeparators(workingDirEdit->text()),
(closeCheckBox->checkState() == Qt::Checked) ? DEFAULT_CLOSE : DEFAULT_STAY,
- ui->overwriteAppIDBox->isChecked() ? ui->appIDOverwriteEdit->text() : "");
+ ui->overwriteAppIDBox->isChecked() ? ui->appIDOverwriteEdit->text() : "",
+ true, false);
resetInput();
refreshExecutablesWidget();
diff --git a/src/executableslist.cpp b/src/executableslist.cpp
index ed66d8f4..036671f0 100644
--- a/src/executableslist.cpp
+++ b/src/executableslist.cpp
@@ -17,178 +17,194 @@ 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>
-
-
-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;
- 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_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) {
- ExecutableInfo test = *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::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)
-{
- 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 = true;
- } 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;
- m_Executables.push_back(newExe);
- }
-}
-
-/*void ExecutablesList::remove(const QString &executableName)
-{
- for (std::vector<Executable>::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) {
- if (iter->m_Custom && (iter->m_BinaryInfo.absoluteFilePath() == executableName)) {
- m_Executables.erase(iter);
- break;
- }
- }
-}*/
-
-
-void ExecutablesList::remove(const QString &title)
-{
- 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;
- m_Executables.push_back(newExe);
- }
-}
+#include "executableslist.h"
+#include <gameinfo.h>
+#include <QFileInfo>
+#include <QDir>
+#include "utility.h"
+#include <algorithm>
+
+
+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) {
+ ExecutableInfo test = *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 (iter->m_Title == title) {
+ 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 &executableName)
+{
+ for (std::vector<Executable>::iterator iter = m_Executables.begin(); iter != m_Executables.end(); ++iter) {
+ if (iter->m_Custom && (iter->m_BinaryInfo.absoluteFilePath() == executableName)) {
+ m_Executables.erase(iter);
+ break;
+ }
+ }
+}*/
+
+
+void ExecutablesList::remove(const QString &title)
+{
+ 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 6a7d36eb..7016c560 100644
--- a/src/executableslist.h
+++ b/src/executableslist.h
@@ -17,129 +17,139 @@ 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;
- CloseMOStyle m_CloseMO;
- QString m_SteamAppID;
- QString m_WorkingDirectory;
-
- bool m_Custom;
-};
-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 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, CloseMOStyle closeMO, const QString &steamAppID);
-
- /**
- * @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, CloseMOStyle closeMO, const QString &steamAppID);
-
-private:
-
- std::vector<Executable> m_Executables;
-
-};
-
-#endif // EXECUTABLESLIST_H
+#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;
+ 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, 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, CloseMOStyle closeMO, const QString &steamAppID);
+
+private:
+
+ std::vector<Executable> m_Executables;
+
+};
+
+#endif // EXECUTABLESLIST_H
diff --git a/src/fomodinstallerdialog.cpp b/src/fomodinstallerdialog.cpp
index 16f6db60..bca05567 100644
--- a/src/fomodinstallerdialog.cpp
+++ b/src/fomodinstallerdialog.cpp
@@ -17,923 +17,940 @@ 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 "fomodinstallerdialog.h"
-#include "report.h"
-#include "utility.h"
-#include "ui_fomodinstallerdialog.h"
-
-#include <QFile>
-#include <QDir>
-#include <QImage>
-#include <QCheckBox>
-#include <QRadioButton>
-#include <QScrollArea>
-#include <Shellapi.h>
-
-
-bool ControlsAscending(QAbstractButton *LHS, QAbstractButton *RHS)
-{
- return LHS->text() < RHS->text();
-}
-
-
-bool ControlsDescending(QAbstractButton *LHS, QAbstractButton *RHS)
-{
- return LHS->text() > RHS->text();
-}
-
-
-bool PagesAscending(QGroupBox *LHS, QGroupBox *RHS)
-{
- return LHS->title() < RHS->title();
-}
-
-
-bool PagesDescending(QGroupBox *LHS, QGroupBox *RHS)
-{
- return LHS->title() > RHS->title();
-}
-
-
-FomodInstallerDialog::FomodInstallerDialog(const QString &modName, const QString &fomodPath, QWidget *parent)
- : QDialog(parent), ui(new Ui::FomodInstallerDialog), m_FomodPath(fomodPath), m_Manual(false)
-{
- ui->setupUi(this);
- ui->nameEdit->setText(modName);
-}
-
-FomodInstallerDialog::~FomodInstallerDialog()
-{
- delete ui;
-}
-
-#pragma message("implement module dependencies->file dependencies")
-
-void FomodInstallerDialog::initData()
-{
- { // parse provided package information
- QFile file(QDir::tempPath().append("/info.xml"));
- if (file.open(QIODevice::ReadOnly)) {
- // nmm allows files with wrong encoding and of course there are now files with broken
- // so, let's do as nmm does and ignore the standard. yay
- QByteArray header = file.readLine();
- if (strncmp(header.constData(), "<?", 2) != 0) {
- // not a header, rewind
- file.seek(0);
- }
- parseInfo(file.readAll());
- }
- file.close();
- }
-
- QImage screenshot(QDir::tempPath().append("/screenshot.png"));
- if (!screenshot.isNull()) {
- screenshot = screenshot.scaledToWidth(ui->screenshotLabel->width());
- ui->screenshotLabel->setPixmap(QPixmap::fromImage(screenshot));
- }
-
- { // parse xml installer file
- QFile file(QDir::tempPath().append("/ModuleConfig.xml"));
- if (!file.open(QIODevice::ReadOnly)) {
- throw MyException(tr("ModuleConfig.xml missing"));
- }
- // nmm allows files with wrong encoding and of course there are now files that are broken
- QByteArray header = file.readLine();
- if (strncmp(header.constData(), "<?", 2) != 0) {
- // not a header, rewind
- if (!file.seek(0)) {
- qCritical("failed to rewind file");
- }
- }
- parseModuleConfig(file.readAll());
- file.close();
- }
-}
-
-
-QString FomodInstallerDialog::getName() const
-{
- return ui->nameEdit->text();
-}
-
-
-void FomodInstallerDialog::moveTree(DirectoryTree::Node *target, DirectoryTree::Node *source)
-{
- for (DirectoryTree::const_node_iterator iter = source->nodesBegin(); iter != source->nodesEnd();) {
- target->addNode(*iter, true);
- iter = source->detach(iter);
- }
-
- for (DirectoryTree::const_leaf_reverse_iterator iter = source->leafsRBegin();
- iter != source->leafsREnd(); ++iter) {
- target->addLeaf(*iter);
- }
-}
-
-
-DirectoryTree::Node *FomodInstallerDialog::findNode(DirectoryTree::Node *node, const QString &path, bool create)
-{
- if (path.length() == 0) {
- return node;
- }
-
-// static QRegExp pathSeparator("[/\\]");
- int pos = path.indexOf('\\');
- if (pos == -1) {
- pos = path.indexOf('/');
- }
- QString subPath = path;
- if (pos > 0) {
- subPath = path.mid(0, pos);
- }
- for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) {
- if ((*iter)->getData().name.compare(subPath, Qt::CaseInsensitive) == 0) {
- if (pos <= 0) {
- return *iter;
- } else {
- return findNode(*iter, path.mid(pos + 1), create);
- }
- }
- }
- if (create) {
- DirectoryTree::Node *newNode = new DirectoryTree::Node;
- newNode->setData(subPath);
- node->addNode(newNode, false);
- if (pos <= 0) {
- return newNode;
- } else {
- return findNode(newNode, path.mid(pos + 1), create);
- }
- } else {
- throw MyException(QString("%1 not found in archive").arg(path));
- }
-}
-
-void FomodInstallerDialog::copyLeaf(DirectoryTree::Node *sourceTree, const QString &sourcePath,
- DirectoryTree::Node *destinationTree, const QString &destinationPath)
-{
- int sourceFileIndex = sourcePath.lastIndexOf('\\');
- if (sourceFileIndex == -1) {
- sourceFileIndex = sourcePath.lastIndexOf('/');
- if (sourceFileIndex == -1) {
- sourceFileIndex = 0;
- }
- }
- DirectoryTree::Node *sourceNode = sourceFileIndex == 0 ? sourceTree : findNode(sourceTree, sourcePath.mid(0, sourceFileIndex), false);
-
- int destinationFileIndex = destinationPath.lastIndexOf('\\');
- if (destinationFileIndex == -1) {
- destinationFileIndex = destinationPath.lastIndexOf('/');
- if (destinationFileIndex == -1) {
- destinationFileIndex = 0;
- }
- }
-
- DirectoryTree::Node *destinationNode =
- destinationFileIndex == 0 ? destinationTree
- : findNode(destinationTree, destinationPath.mid(0, destinationFileIndex), true);
-
- QString sourceName = sourcePath.mid((sourceFileIndex != 0) ? sourceFileIndex + 1 : 0);
- QString destinationName = (destinationFileIndex != 0) ? destinationPath.mid(destinationFileIndex + 1) : destinationPath;
- if (destinationName.length() == 0) {
- destinationName = sourceName;
- }
-
- bool found = false;
- for (DirectoryTree::const_leaf_reverse_iterator iter = sourceNode->leafsRBegin();
- iter != sourceNode->leafsREnd(); ++iter) {
- if (iter->getName().compare(sourceName, Qt::CaseInsensitive) == 0) {
- destinationNode->addLeaf(*iter);
- found = true;
- }
- }
- if (!found) {
- qCritical("%s not found!", sourceName.toUtf8().constData());
- }
-}
-
-
-void dumpTree(DirectoryTree::Node *node, int indent)
-{
- for (DirectoryTree::const_leaf_reverse_iterator iter = node->leafsRBegin();
- iter != node->leafsREnd(); ++iter) {
- qDebug("%.*s%s", indent, " ", iter->getName().toUtf8().constData());
- }
-
- for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) {
- qDebug("%.*s-- %s", indent, " ", (*iter)->getData().name.toUtf8().constData());
- dumpTree(*iter, indent + 2);
- }
-}
-
-
-bool FomodInstallerDialog::copyFileIterator(DirectoryTree *sourceTree, DirectoryTree *destinationTree, FileDescriptor *descriptor)
-{
- QString source = (m_FomodPath.length() != 0) ? m_FomodPath.mid(0).append("\\").append(descriptor->m_Source)
- : descriptor->m_Source;
- QString destination = descriptor->m_Destination;
- try {
- if (descriptor->m_IsFolder) {
- DirectoryTree::Node *sourceNode = findNode(sourceTree, source, false);
- DirectoryTree::Node *targetNode = findNode(destinationTree, destination, true);
- moveTree(targetNode, sourceNode);
- } else {
- copyLeaf(sourceTree, source, destinationTree, destination);
- }
- return true;
- } catch (const MyException &e) {
- qCritical("failed to extract %s to %s: %s",
- source.toUtf8().constData(), destination.toUtf8().constData(), e.what());
- return false;
- }
-}
-
-
-DirectoryTree *FomodInstallerDialog::updateTree(DirectoryTree *tree)
-{
- DirectoryTree *newTree = new DirectoryTree;
-
- for (std::vector<FileDescriptor*>::iterator iter = m_RequiredFiles.begin(); iter != m_RequiredFiles.end(); ++iter) {
- copyFileIterator(tree, newTree, *iter);
- }
-
- for (std::vector<ConditionalInstall>::iterator installIter = m_ConditionalInstalls.begin();
- installIter != m_ConditionalInstalls.end(); ++installIter) {
- bool match = installIter->m_Operator == ConditionalInstall::OP_AND;
- for (std::vector<Condition>::iterator conditionIter = installIter->m_Conditions.begin();
- conditionIter != installIter->m_Conditions.end(); ++conditionIter) {
- bool conditionMatches = testCondition(ui->stepsStack->count(), conditionIter->m_Name, conditionIter->m_Value);
- if (conditionMatches && (installIter->m_Operator == ConditionalInstall::OP_OR)) {
- match = true;
- break;
- } else if (!conditionMatches && (installIter->m_Operator == ConditionalInstall::OP_AND)) {
- match = false;
- break;
- }
- }
- if (match) {
- for (std::vector<FileDescriptor*>::iterator fileIter = installIter->m_Files.begin();
- fileIter != installIter->m_Files.end(); ++fileIter) {
- copyFileIterator(tree, newTree, *fileIter);
- }
- }
- }
-
- QList<QAbstractButton*> choices = ui->stepsStack->findChildren<QAbstractButton*>("choice");
- foreach (QAbstractButton* choice, choices) {
- if (choice->isChecked()) {
- QVariantList fileList = choice->property("files").toList();
- foreach (QVariant fileVariant, fileList) {
- copyFileIterator(tree, newTree, fileVariant.value<FileDescriptor*>());
- }
- }
- }
-
-// dumpTree(newTree, 0);
-
- return newTree;
-}
-
-
-void FomodInstallerDialog::highlightControl(QAbstractButton *button)
-{
- QVariant screenshotName = button->property("screenshot");
- if (screenshotName.isValid()) {
- QString screenshotFileName = screenshotName.toString();
- if (!screenshotFileName.isEmpty()) {
- QString temp = QFileInfo(screenshotFileName).fileName();
- QImage screenshot(QDir::tempPath().append("/").append(temp));
- if (screenshot.isNull()) {
- qWarning(">%s< is a null image", screenshotName.toString().toUtf8().constData());
- }
- screenshot = screenshot.scaledToWidth(ui->screenshotLabel->width());
- ui->screenshotLabel->setPixmap(QPixmap::fromImage(screenshot));
- } else {
- ui->screenshotLabel->setPixmap(QPixmap());
- }
- }
- ui->descriptionText->setText(button->property("description").toString());
-}
-
-
-bool FomodInstallerDialog::eventFilter(QObject *object, QEvent *event)
-{
- QAbstractButton *button = qobject_cast<QAbstractButton*>(object);
- if ((button != NULL) && (event->type() == QEvent::HoverEnter)) {
- highlightControl(button);
-
- }
- return QDialog::eventFilter(object, event);
-}
-
-
-QString FomodInstallerDialog::readContent(QXmlStreamReader &reader)
-{
- if (reader.readNext() == QXmlStreamReader::Characters) {
- return reader.text().toString();
- } else {
- return QString();
- }
-}
-
-
-void FomodInstallerDialog::parseInfo(const QByteArray &data)
-{
- QXmlStreamReader reader(data);
-/* while (reader.readNext() != QXmlStreamReader::StartDocument) {}
- QTextDecoder *decoder = QTextCodec::codecForName(reader.documentEncoding().toLocal8Bit())->makeDecoder(QTextCodec::ConvertInvalidToNull);
- QString test(decoder->toUnicode(data));
- qDebug("test: %d, %s", test.isNull(), test.toUtf8().constData());
-
- qDebug(">%s<", reader.documentEncoding().toUtf8().constData());*/
- while (!reader.atEnd() && !reader.hasError()) {
- switch (reader.readNext()) {
- case QXmlStreamReader::StartElement: {
- if (reader.name() == "Name") {
- ui->nameEdit->setText(readContent(reader));
- } else if (reader.name() == "Author") {
- ui->authorLabel->setText(readContent(reader));
- } else if (reader.name() == "Version") {
- ui->versionLabel->setText(readContent(reader));
- } else if (reader.name() == "Website") {
- QString url = readContent(reader);
- ui->websiteLabel->setText(tr("<a href=\"%1\">Link</a>").arg(url));
- ui->websiteLabel->setToolTip(url);
- }
- } break;
- default: {} break;
- }
- }
- if (reader.hasError()) {
- throw MyException(tr("failed to parse info.xml: %1 (%2) (line %3, column %4)")
- .arg(reader.errorString())
- .arg(reader.error())
- .arg(reader.lineNumber())
- .arg(reader.columnNumber()));
-
- }
-}
-
-
-FomodInstallerDialog::ItemOrder FomodInstallerDialog::getItemOrder(const QString &orderString)
-{
- if (orderString == "Ascending") {
- return ORDER_ASCENDING;
- } else if (orderString == "Descending") {
- return ORDER_DESCENDING;
- } else if (orderString == "Explicit") {
- return ORDER_EXPLICIT;
- } else {
- throw MyException(tr("unsupported order type %1").arg(orderString));
- }
-}
-
-
-FomodInstallerDialog::GroupType FomodInstallerDialog::getGroupType(const QString &typeString)
-{
- if (typeString == "SelectAtLeastOne") {
- return TYPE_SELECTATLEASTONE;
- } else if (typeString == "SelectAtMostOne") {
- return TYPE_SELECTATMOSTONE;
- } else if (typeString == "SelectExactlyOne") {
- return TYPE_SELECTEXACTLYONE;
- } else if (typeString == "SelectAny") {
- return TYPE_SELECTANY;
- } else if (typeString == "SelectAll") {
- return TYPE_SELECTALL;
- } else {
- throw MyException(tr("unsupported group type %1").arg(typeString));
- }
-}
-
-
-FomodInstallerDialog::PluginType FomodInstallerDialog::getPluginType(const QString &typeString)
-{
- if (typeString == "Required") {
- return FomodInstallerDialog::TYPE_REQUIRED;
- } else if (typeString == "Optional") {
- return FomodInstallerDialog::TYPE_OPTIONAL;
- } else if (typeString == "Recommended") {
- return FomodInstallerDialog::TYPE_RECOMMENDED;
- } else if (typeString == "NotUsable") {
- return FomodInstallerDialog::TYPE_NOTUSABLE;
- } else if (typeString == "CouldBeUsable") {
- return FomodInstallerDialog::TYPE_COULDBEUSABLE;
- } else {
- qCritical("invalid plugin type %s", typeString.toUtf8().constData());
- return FomodInstallerDialog::TYPE_OPTIONAL;
- }
-}
-
-
-void FomodInstallerDialog::readFileList(QXmlStreamReader &reader, std::vector<FileDescriptor*> &fileList)
-{
- QStringRef openTag = reader.name();
- while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
- (reader.name() == openTag))) {
- if (reader.tokenType() == QXmlStreamReader::StartElement) {
- if ((reader.name() == "folder") ||
- (reader.name() == "file")) {
- QXmlStreamAttributes attributes = reader.attributes();
- FileDescriptor *file = new FileDescriptor(this);
- file->m_Source = attributes.value("source").toString();
- file->m_Destination = attributes.hasAttribute("destination") ? attributes.value("destination").toString()
- : file->m_Source;
- file->m_Priority = attributes.hasAttribute("priority") ? attributes.value("priority").string()->toInt()
- : 0;
- file->m_IsFolder = reader.name() == "folder";
- file->m_InstallIfUsable = attributes.hasAttribute("installIfUsable") ? (attributes.value("installIfUsable").compare("true") == 0)
- : false;
- file->m_AlwaysInstall = attributes.hasAttribute("alwaysInstall") ? (attributes.value("alwaysInstall").compare("true") == 0)
- : false;
-
- fileList.push_back(file);
- }
- }
- }
-}
-
-
-void FomodInstallerDialog::readPluginType(QXmlStreamReader &reader, Plugin &plugin)
-{
- plugin.m_Type = TYPE_OPTIONAL;
- while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
- (reader.name() == "typeDescriptor"))) {
- if (reader.tokenType() == QXmlStreamReader::StartElement) {
- if (reader.name() == "type") {
- plugin.m_Type = getPluginType(reader.attributes().value("name").toString());
- }
- }
- }
-}
-
-
-void FomodInstallerDialog::readConditionFlags(QXmlStreamReader &reader, Plugin &plugin)
-{
- while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
- (reader.name() == "conditionFlags"))) {
- if (reader.tokenType() == QXmlStreamReader::StartElement) {
- if (reader.name() == "flag") {
- QString name = reader.attributes().value("name").toString();
- plugin.m_Conditions.push_back(Condition(name, readContent(reader)));
- }
- }
- }
-}
-
-
-bool FomodInstallerDialog::byPriority(const FileDescriptor *LHS, const FileDescriptor *RHS)
-{
- return LHS->m_Priority < RHS->m_Priority;
-}
-
-
-FomodInstallerDialog::Plugin FomodInstallerDialog::readPlugin(QXmlStreamReader &reader)
-{
- Plugin result;
- result.m_Name = reader.attributes().value("name").toString();
-
- while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
- (reader.name() == "plugin"))) {
-// QXmlStreamReader::TokenType type = reader.tokenType();
-// QString name = reader.name().toUtf8();
- if (reader.tokenType() == QXmlStreamReader::StartElement) {
- if (reader.name() == "description") {
- result.m_Description = readContent(reader).trimmed();
- } else if (reader.name() == "image") {
- result.m_ImagePath = reader.attributes().value("path").toString();
- } else if (reader.name() == "typeDescriptor") {
- readPluginType(reader, result);
- } else if (reader.name() == "conditionFlags") {
- readConditionFlags(reader, result);
- } else if (reader.name() == "files") {
- readFileList(reader, result.m_Files);
- }
- }
- }
-
- std::sort(result.m_Files.begin(), result.m_Files.end(), byPriority);
-
- return result;
-}
-
-
-void FomodInstallerDialog::readPlugins(QXmlStreamReader &reader, GroupType groupType, QLayout *layout)
-{
- ItemOrder pluginOrder = reader.attributes().hasAttribute("order") ? getItemOrder(reader.attributes().value("order").toString())
- : ORDER_ASCENDING;
- bool first = true;
- bool maySelectMore = true;
-
- std::vector<QAbstractButton*> controls;
-
- while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
- (reader.name() == "plugins"))) {
- if (reader.tokenType() == QXmlStreamReader::StartElement) {
- if (reader.name() == "plugin") {
- Plugin plugin = readPlugin(reader);
- QAbstractButton *newControl = NULL;
- switch (groupType) {
- case TYPE_SELECTATLEASTONE:
- case TYPE_SELECTANY: {
- newControl = new QCheckBox(plugin.m_Name);
- } break;
- case TYPE_SELECTATMOSTONE: {
- newControl = new QRadioButton(plugin.m_Name);
- } break;
- case TYPE_SELECTEXACTLYONE: {
- newControl = new QRadioButton(plugin.m_Name);
- if (first) {
- newControl->setChecked(true);
- }
- } break;
- case TYPE_SELECTALL: {
- newControl = new QCheckBox(plugin.m_Name);
- newControl->setChecked(true);
- newControl->setEnabled(false);
- } break;
- }
- newControl->setObjectName("choice");
- switch (plugin.m_Type) {
- case TYPE_REQUIRED: {
- newControl->setChecked(true);
- newControl->setEnabled(false);
- newControl->setToolTip(tr("This component is required"));
- } break;
- case TYPE_RECOMMENDED: {
- if (maySelectMore) {
- newControl->setChecked(true);
- }
- newControl->setToolTip(tr("It is recommended you enable this component"));
- if ((groupType == TYPE_SELECTATMOSTONE) || (groupType == TYPE_SELECTEXACTLYONE)) {
- maySelectMore = false;
- }
- } break;
- case TYPE_OPTIONAL: {
- newControl->setToolTip(tr("Optional component"));
- } break;
- case TYPE_NOTUSABLE: {
- newControl->setChecked(false);
- newControl->setEnabled(false);
- newControl->setToolTip(tr("This component is not usable in combination with other installed plugins"));
- } break;
- case TYPE_COULDBEUSABLE: {
- newControl->setCheckable(true);
- newControl->setIcon(QIcon(":/new/guiresources/resources/dialog-warning_16.png"));
- newControl->setToolTip(tr("You may be experiencing instability in combination with other installed plugins"));
- } break;
- }
-
- newControl->setProperty("plugintype", plugin.m_Type);
- newControl->setProperty("screenshot", plugin.m_ImagePath);
- newControl->setProperty("description", plugin.m_Description);
- QVariantList fileList;
- for (std::vector<FileDescriptor*>::iterator iter = plugin.m_Files.begin(); iter != plugin.m_Files.end(); ++iter) {
- fileList.append(qVariantFromValue(*iter));
- }
- newControl->setProperty("files", fileList);
- QVariantList conditionFlags;
- for (std::vector<Condition>::const_iterator iter = plugin.m_Conditions.begin(); iter != plugin.m_Conditions.end(); ++iter) {
- if (iter->m_Name.length() != 0) {
- conditionFlags.append(qVariantFromValue(Condition(iter->m_Name, iter->m_Value)));
- }
- }
- newControl->setProperty("conditionFlags", conditionFlags);
- newControl->installEventFilter(this);
- controls.push_back(newControl);
- first = false;
- }
- }
- }
-
- if (pluginOrder == ORDER_ASCENDING) {
- std::sort(controls.begin(), controls.end(), ControlsAscending);
- } else if (pluginOrder == ORDER_DESCENDING) {
- std::sort(controls.begin(), controls.end(), ControlsDescending);
- }
-
- for (std::vector<QAbstractButton*>::const_iterator iter = controls.begin(); iter != controls.end(); ++iter) {
- layout->addWidget(*iter);
- }
-
- if (groupType == TYPE_SELECTATMOSTONE) {
- QRadioButton *newButton = new QRadioButton(tr("None"));
- if (maySelectMore) {
- newButton->setChecked(true);
- }
- layout->addWidget(newButton);
- }
-}
-
-
-void FomodInstallerDialog::readGroup(QXmlStreamReader &reader, QLayout *layout)
-{
- //FileGroup result;
- QString name = reader.attributes().value("name").toString();
- GroupType type = getGroupType(reader.attributes().value("type").toString());
-
- if (type == TYPE_SELECTATLEASTONE) {
- QLabel *label = new QLabel(tr("Select one or more of these options:"));
- layout->addWidget(label);
- }
-
- QGroupBox *groupBox = new QGroupBox(name);
-
- QVBoxLayout *groupLayout = new QVBoxLayout;
-
- while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
- (reader.name() == "group"))) {
- if (reader.tokenType() == QXmlStreamReader::StartElement) {
- if (reader.name() == "plugins") {
- readPlugins(reader, type, groupLayout);
- }
- }
- }
-
- groupBox->setLayout(groupLayout);
- layout->addWidget(groupBox);
-}
-
-
-void FomodInstallerDialog::readGroups(QXmlStreamReader &reader, QLayout *layout)
-{
- while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
- (reader.name() == "optionalFileGroups"))) {
- if (reader.tokenType() == QXmlStreamReader::StartElement) {
- if (reader.name() == "group") {
- readGroup(reader, layout);
- }
- }
- }
-}
-
-
-void FomodInstallerDialog::readVisible(QXmlStreamReader &reader, QVariantList &conditions)
-{
- while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
- (reader.name() == "visible"))) {
- if (reader.tokenType() == QXmlStreamReader::StartElement) {
- if (reader.name() == "flagDependency") {
- Condition condition(reader.attributes().value("flag").toString(),
- reader.attributes().value("value").toString());
- conditions.append(qVariantFromValue(condition));
- }
- }
- }
-}
-
-QGroupBox *FomodInstallerDialog::readInstallerStep(QXmlStreamReader &reader)
-{
- QString name = reader.attributes().value("name").toString();
- QGroupBox *page = new QGroupBox(name);
- QVBoxLayout *pageLayout = new QVBoxLayout;
- QScrollArea *scrollArea = new QScrollArea;
- QFrame *scrolledArea = new QFrame;
- QVBoxLayout *scrollLayout = new QVBoxLayout;
-
- QVariantList conditions;
-
- while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
- (reader.name() == "installStep"))) {
- if (reader.tokenType() == QXmlStreamReader::StartElement) {
- if (reader.name() == "optionalFileGroups") {
- readGroups(reader, scrollLayout);
- } else if (reader.name() == "visible") {
- readVisible(reader, conditions);
- }
- }
- }
- if (conditions.length() != 0) {
- page->setProperty("conditions", conditions);
- }
-
- scrolledArea->setLayout(scrollLayout);
- scrollArea->setWidget(scrolledArea);
- scrollArea->setWidgetResizable(true);
- pageLayout->addWidget(scrollArea);
- page->setLayout(pageLayout);
- return page;
-}
-
-
-void FomodInstallerDialog::readInstallerSteps(QXmlStreamReader &reader)
-{
- ItemOrder stepOrder = reader.attributes().hasAttribute("order") ? getItemOrder(reader.attributes().value("order").toString())
- : ORDER_ASCENDING;
-
- std::vector<QGroupBox*> pages;
-
- while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
- (reader.name() == "installSteps"))) {
- if (reader.tokenType() == QXmlStreamReader::StartElement) {
- if (reader.name() == "installStep") {
- pages.push_back(readInstallerStep(reader));
- }
- }
- }
-
- if (stepOrder == ORDER_ASCENDING) {
- std::sort(pages.begin(), pages.end(), PagesAscending);
- } else if (stepOrder == ORDER_DESCENDING) {
- std::sort(pages.begin(), pages.end(), PagesDescending);
- }
-
- for (std::vector<QGroupBox*>::const_iterator iter = pages.begin(); iter != pages.end(); ++iter) {
- ui->stepsStack->addWidget(*iter);
- }
-}
-
-
-FomodInstallerDialog::ConditionalInstall FomodInstallerDialog::readConditionalPattern(QXmlStreamReader &reader)
-{
- ConditionalInstall result;
- result.m_Operator = ConditionalInstall::OP_AND;
- while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
- (reader.name() == "pattern"))) {
- if (reader.tokenType() == QXmlStreamReader::StartElement) {
- if (reader.name() == "dependencies") {
- QStringRef dependencyOperator = reader.attributes().value("operator");
- if (dependencyOperator == "And") {
- result.m_Operator = ConditionalInstall::OP_AND;
- } else if (dependencyOperator == "Or") {
- result.m_Operator = ConditionalInstall::OP_OR;
- } // otherwise operator is not set (which we can ignore) or invalid (which we should report actually)
-
- while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
- (reader.name() == "dependencies"))) {
- if (reader.tokenType() == QXmlStreamReader::StartElement) {
- if (reader.name() == "flagDependency") {
- result.m_Conditions.push_back(Condition(reader.attributes().value("flag").toString(),
- reader.attributes().value("value").toString()));
- }
- }
- }
- } else if (reader.name() == "files") {
- readFileList(reader, result.m_Files);
- }
- }
- }
- return result;
-}
-
-
-void FomodInstallerDialog::readConditionalFileInstalls(QXmlStreamReader &reader)
-{
- while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
- (reader.name() == "conditionalFileInstalls"))) {
- if (reader.tokenType() == QXmlStreamReader::StartElement) {
- if (reader.name() == "patterns") {
- while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
- (reader.name() == "patterns"))) {
- if (reader.tokenType() == QXmlStreamReader::StartElement) {
- if (reader.name() == "pattern") {
- m_ConditionalInstalls.push_back(readConditionalPattern(reader));
- }
- }
- }
- }
- }
- }
-}
-
-
-void FomodInstallerDialog::parseModuleConfig(const QByteArray &data)
-{
- QXmlStreamReader reader(data);
- while (!reader.atEnd() && !reader.hasError()) {
- switch (reader.readNext()) {
- case QXmlStreamReader::StartElement: {
- if (reader.name() == "installSteps") {
- readInstallerSteps(reader);
- } else if (reader.name() == "requiredInstallFiles") {
- readFileList(reader, m_RequiredFiles);
- } else if (reader.name() == "conditionalFileInstalls") {
- readConditionalFileInstalls(reader);
- }
- } break;
- default: {} break;
- }
- }
- if (reader.hasError()) {
- reportError(tr("failed to parse ModuleConfig.xml: %1 - %2").arg(reader.errorString()).arg(reader.lineNumber()));
- }
- activateCurrentPage();
-}
-
-
-void FomodInstallerDialog::on_manualBtn_clicked()
-{
- m_Manual = true;
- this->reject();
-}
-
-void FomodInstallerDialog::on_cancelBtn_clicked()
-{
- this->reject();
-}
-
-
-void FomodInstallerDialog::on_websiteLabel_linkActivated(const QString &link)
-{
- ::ShellExecuteW(NULL, L"open", ToWString(link).c_str(), NULL, NULL, SW_SHOWNORMAL);
-}
-
-
-void FomodInstallerDialog::activateCurrentPage()
-{
- QList<QAbstractButton*> choices = ui->stepsStack->currentWidget()->findChildren<QAbstractButton*>("choice");
- if (choices.count() > 0) {
- highlightControl(choices.at(0));
- }
-}
-
-
-bool FomodInstallerDialog::testCondition(int maxIndex, const QString &flag, const QString &value)
-{
- // iterate through all set condition flags on all activated controls on all visible pages if one of them matches the condition
- for (int i = 0; i < maxIndex; ++i) {
- if (testVisible(i)) {
- QWidget *page = ui->stepsStack->widget(i);
- QList<QAbstractButton*> choices = page->findChildren<QAbstractButton*>("choice");
- foreach (QAbstractButton* choice, choices) {
- if (choice->isChecked()) {
- QVariant temp = choice->property("conditionFlags");
- if (temp.isValid()) {
- QVariantList conditionFlags = temp.toList();
- for (QVariantList::const_iterator iter = conditionFlags.begin(); iter != conditionFlags.end(); ++iter) {
- Condition condition = iter->value<Condition>();
- if ((condition.m_Name == flag) && (condition.m_Value == value)) {
- return true;
- }
- }
- }
- }
- }
- }
- }
- return false;
-}
-
-
-bool FomodInstallerDialog::testVisible(int pageIndex)
-{
- QWidget *page = ui->stepsStack->widget(pageIndex);
- QVariant temp = page->property("conditions");
- if (temp.isValid()) {
- QVariantList conditions = temp.toList();
- for (QVariantList::const_iterator iter = conditions.begin(); iter != conditions.end(); ++iter) {
- Condition condition = iter->value<Condition>();
- if (!testCondition(pageIndex, condition.m_Name, condition.m_Value)) {
- return false;
- }
- }
- return true;
- } else {
- return true;
- }
-}
-
-
-bool FomodInstallerDialog::nextPage()
-{
- int index = ui->stepsStack->currentIndex() + 1;
- while (index < ui->stepsStack->count()) {
- if (testVisible(index)) {
- ui->stepsStack->setCurrentIndex(index);
- return true;
- }
- ++index;
- }
- // no more visible pages -> install
- return false;
-}
-
-
-void FomodInstallerDialog::on_nextBtn_clicked()
-{
- if (ui->stepsStack->currentIndex() == ui->stepsStack->count() - 1) {
- this->accept();
- } else {
- if (nextPage()) {
- if (ui->stepsStack->currentIndex() == ui->stepsStack->count() - 1) {
- ui->nextBtn->setText(tr("Install"));
- }
- ui->prevBtn->setEnabled(true);
- activateCurrentPage();
- } else {
- this->accept();
- }
- }
-}
-
-void FomodInstallerDialog::on_prevBtn_clicked()
-{
- if (ui->stepsStack->currentIndex() != 0) {
- ui->stepsStack->setCurrentIndex(ui->stepsStack->currentIndex() - 1);
- ui->nextBtn->setText(tr("Next"));
- }
- if (ui->stepsStack->currentIndex() == 0) {
- ui->prevBtn->setEnabled(false);
- }
- activateCurrentPage();
-}
+#include "fomodinstallerdialog.h"
+#include "report.h"
+#include "utility.h"
+#include "ui_fomodinstallerdialog.h"
+
+#include <QFile>
+#include <QDir>
+#include <QImage>
+#include <QCheckBox>
+#include <QRadioButton>
+#include <QScrollArea>
+#include <Shellapi.h>
+
+
+bool ControlsAscending(QAbstractButton *LHS, QAbstractButton *RHS)
+{
+ return LHS->text() < RHS->text();
+}
+
+
+bool ControlsDescending(QAbstractButton *LHS, QAbstractButton *RHS)
+{
+ return LHS->text() > RHS->text();
+}
+
+
+bool PagesAscending(QGroupBox *LHS, QGroupBox *RHS)
+{
+ return LHS->title() < RHS->title();
+}
+
+
+bool PagesDescending(QGroupBox *LHS, QGroupBox *RHS)
+{
+ return LHS->title() > RHS->title();
+}
+
+
+FomodInstallerDialog::FomodInstallerDialog(const QString &modName, bool nameWasGuessed, const QString &fomodPath, QWidget *parent)
+ : QDialog(parent), ui(new Ui::FomodInstallerDialog), m_NameWasGuessed(nameWasGuessed), m_FomodPath(fomodPath), m_Manual(false)
+{
+ ui->setupUi(this);
+ ui->nameEdit->setText(modName);
+}
+
+FomodInstallerDialog::~FomodInstallerDialog()
+{
+ delete ui;
+}
+
+
+int FomodInstallerDialog::bomOffset(const QByteArray &buffer)
+{
+ static const unsigned char BOM_UTF8[] = { 0xEF, 0xBB, 0xBF };
+ static const unsigned char BOM_UTF16BE[] = { 0xFE, 0xFF };
+ static const unsigned char BOM_UTF16LE[] = { 0xFF, 0xFE };
+
+ if (buffer.startsWith(reinterpret_cast<const char*>(BOM_UTF8))) return 3;
+ if (buffer.startsWith(reinterpret_cast<const char*>(BOM_UTF16BE)) ||
+ buffer.startsWith(reinterpret_cast<const char*>(BOM_UTF16LE))) return 2;
+
+ return 0;
+}
+
+#pragma message("implement module dependencies->file dependencies")
+
+void FomodInstallerDialog::initData()
+{
+ { // parse provided package information
+ QFile file(QDir::tempPath().append("/info.xml"));
+ if (file.open(QIODevice::ReadOnly)) {
+ // nmm allows files with wrong encoding and of course there are now files with broken
+ // so, let's do as nmm does and ignore the standard. yay
+ QByteArray header = file.readLine();
+ if (strncmp(header.constData() + bomOffset(header), "<?", 2) != 0) {
+ // not a header, rewind
+ file.seek(0);
+ }
+ parseInfo(file.readAll());
+ }
+ file.close();
+ }
+
+ QImage screenshot(QDir::tempPath().append("/screenshot.png"));
+ if (!screenshot.isNull()) {
+ screenshot = screenshot.scaledToWidth(ui->screenshotLabel->width());
+ ui->screenshotLabel->setPixmap(QPixmap::fromImage(screenshot));
+ }
+
+ { // parse xml installer file
+ QFile file(QDir::tempPath().append("/ModuleConfig.xml"));
+ if (!file.open(QIODevice::ReadOnly)) {
+ throw MyException(tr("ModuleConfig.xml missing"));
+ }
+ // nmm allows files with wrong encoding and of course there are now files that are broken
+ QByteArray header = file.readLine();
+
+ if (strncmp(header.constData() + bomOffset(header), "<?", 2) != 0) {
+ // not a header, rewind
+ if (!file.seek(0)) {
+ qCritical("failed to rewind file");
+ }
+ }
+ parseModuleConfig(file.readAll());
+ file.close();
+ }
+}
+
+
+QString FomodInstallerDialog::getName() const
+{
+ return ui->nameEdit->text();
+}
+
+
+void FomodInstallerDialog::moveTree(DirectoryTree::Node *target, DirectoryTree::Node *source)
+{
+ for (DirectoryTree::const_node_iterator iter = source->nodesBegin(); iter != source->nodesEnd();) {
+ target->addNode(*iter, true);
+ iter = source->detach(iter);
+ }
+
+ for (DirectoryTree::const_leaf_reverse_iterator iter = source->leafsRBegin();
+ iter != source->leafsREnd(); ++iter) {
+ target->addLeaf(*iter);
+ }
+}
+
+
+DirectoryTree::Node *FomodInstallerDialog::findNode(DirectoryTree::Node *node, const QString &path, bool create)
+{
+ if (path.length() == 0) {
+ return node;
+ }
+
+// static QRegExp pathSeparator("[/\\]");
+ int pos = path.indexOf('\\');
+ if (pos == -1) {
+ pos = path.indexOf('/');
+ }
+ QString subPath = path;
+ if (pos > 0) {
+ subPath = path.mid(0, pos);
+ }
+ for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) {
+ if ((*iter)->getData().name.compare(subPath, Qt::CaseInsensitive) == 0) {
+ if (pos <= 0) {
+ return *iter;
+ } else {
+ return findNode(*iter, path.mid(pos + 1), create);
+ }
+ }
+ }
+ if (create) {
+ DirectoryTree::Node *newNode = new DirectoryTree::Node;
+ newNode->setData(subPath);
+ node->addNode(newNode, false);
+ if (pos <= 0) {
+ return newNode;
+ } else {
+ return findNode(newNode, path.mid(pos + 1), create);
+ }
+ } else {
+ throw MyException(QString("%1 not found in archive").arg(path));
+ }
+}
+
+void FomodInstallerDialog::copyLeaf(DirectoryTree::Node *sourceTree, const QString &sourcePath,
+ DirectoryTree::Node *destinationTree, const QString &destinationPath)
+{
+ int sourceFileIndex = sourcePath.lastIndexOf('\\');
+ if (sourceFileIndex == -1) {
+ sourceFileIndex = sourcePath.lastIndexOf('/');
+ if (sourceFileIndex == -1) {
+ sourceFileIndex = 0;
+ }
+ }
+ DirectoryTree::Node *sourceNode = sourceFileIndex == 0 ? sourceTree : findNode(sourceTree, sourcePath.mid(0, sourceFileIndex), false);
+
+ int destinationFileIndex = destinationPath.lastIndexOf('\\');
+ if (destinationFileIndex == -1) {
+ destinationFileIndex = destinationPath.lastIndexOf('/');
+ if (destinationFileIndex == -1) {
+ destinationFileIndex = 0;
+ }
+ }
+
+ DirectoryTree::Node *destinationNode =
+ destinationFileIndex == 0 ? destinationTree
+ : findNode(destinationTree, destinationPath.mid(0, destinationFileIndex), true);
+
+ QString sourceName = sourcePath.mid((sourceFileIndex != 0) ? sourceFileIndex + 1 : 0);
+ QString destinationName = (destinationFileIndex != 0) ? destinationPath.mid(destinationFileIndex + 1) : destinationPath;
+ if (destinationName.length() == 0) {
+ destinationName = sourceName;
+ }
+
+ bool found = false;
+ for (DirectoryTree::const_leaf_reverse_iterator iter = sourceNode->leafsRBegin();
+ iter != sourceNode->leafsREnd(); ++iter) {
+ if (iter->getName().compare(sourceName, Qt::CaseInsensitive) == 0) {
+ destinationNode->addLeaf(*iter);
+ found = true;
+ }
+ }
+ if (!found) {
+ qCritical("%s not found!", sourceName.toUtf8().constData());
+ }
+}
+
+
+void dumpTree(DirectoryTree::Node *node, int indent)
+{
+ for (DirectoryTree::const_leaf_reverse_iterator iter = node->leafsRBegin();
+ iter != node->leafsREnd(); ++iter) {
+ qDebug("%.*s%s", indent, " ", iter->getName().toUtf8().constData());
+ }
+
+ for (DirectoryTree::const_node_iterator iter = node->nodesBegin(); iter != node->nodesEnd(); ++iter) {
+ qDebug("%.*s-- %s", indent, " ", (*iter)->getData().name.toUtf8().constData());
+ dumpTree(*iter, indent + 2);
+ }
+}
+
+
+bool FomodInstallerDialog::copyFileIterator(DirectoryTree *sourceTree, DirectoryTree *destinationTree, FileDescriptor *descriptor)
+{
+ QString source = (m_FomodPath.length() != 0) ? m_FomodPath.mid(0).append("\\").append(descriptor->m_Source)
+ : descriptor->m_Source;
+ QString destination = descriptor->m_Destination;
+ try {
+ if (descriptor->m_IsFolder) {
+ DirectoryTree::Node *sourceNode = findNode(sourceTree, source, false);
+ DirectoryTree::Node *targetNode = findNode(destinationTree, destination, true);
+ moveTree(targetNode, sourceNode);
+ } else {
+ copyLeaf(sourceTree, source, destinationTree, destination);
+ }
+ return true;
+ } catch (const MyException &e) {
+ qCritical("failed to extract %s to %s: %s",
+ source.toUtf8().constData(), destination.toUtf8().constData(), e.what());
+ return false;
+ }
+}
+
+
+DirectoryTree *FomodInstallerDialog::updateTree(DirectoryTree *tree)
+{
+ DirectoryTree *newTree = new DirectoryTree;
+
+ for (std::vector<FileDescriptor*>::iterator iter = m_RequiredFiles.begin(); iter != m_RequiredFiles.end(); ++iter) {
+ copyFileIterator(tree, newTree, *iter);
+ }
+
+ for (std::vector<ConditionalInstall>::iterator installIter = m_ConditionalInstalls.begin();
+ installIter != m_ConditionalInstalls.end(); ++installIter) {
+ bool match = installIter->m_Operator == ConditionalInstall::OP_AND;
+ for (std::vector<Condition>::iterator conditionIter = installIter->m_Conditions.begin();
+ conditionIter != installIter->m_Conditions.end(); ++conditionIter) {
+ bool conditionMatches = testCondition(ui->stepsStack->count(), conditionIter->m_Name, conditionIter->m_Value);
+ if (conditionMatches && (installIter->m_Operator == ConditionalInstall::OP_OR)) {
+ match = true;
+ break;
+ } else if (!conditionMatches && (installIter->m_Operator == ConditionalInstall::OP_AND)) {
+ match = false;
+ break;
+ }
+ }
+ if (match) {
+ for (std::vector<FileDescriptor*>::iterator fileIter = installIter->m_Files.begin();
+ fileIter != installIter->m_Files.end(); ++fileIter) {
+ copyFileIterator(tree, newTree, *fileIter);
+ }
+ }
+ }
+
+ QList<QAbstractButton*> choices = ui->stepsStack->findChildren<QAbstractButton*>("choice");
+ foreach (QAbstractButton* choice, choices) {
+ if (choice->isChecked()) {
+ QVariantList fileList = choice->property("files").toList();
+ foreach (QVariant fileVariant, fileList) {
+ copyFileIterator(tree, newTree, fileVariant.value<FileDescriptor*>());
+ }
+ }
+ }
+
+// dumpTree(newTree, 0);
+
+ return newTree;
+}
+
+
+void FomodInstallerDialog::highlightControl(QAbstractButton *button)
+{
+ QVariant screenshotName = button->property("screenshot");
+ if (screenshotName.isValid()) {
+ QString screenshotFileName = screenshotName.toString();
+ if (!screenshotFileName.isEmpty()) {
+ QString temp = QFileInfo(screenshotFileName).fileName();
+ QImage screenshot(QDir::tempPath().append("/").append(temp));
+ if (screenshot.isNull()) {
+ qWarning(">%s< is a null image", screenshotName.toString().toUtf8().constData());
+ }
+ screenshot = screenshot.scaledToWidth(ui->screenshotLabel->width());
+ ui->screenshotLabel->setPixmap(QPixmap::fromImage(screenshot));
+ } else {
+ ui->screenshotLabel->setPixmap(QPixmap());
+ }
+ }
+ ui->descriptionText->setText(button->property("description").toString());
+}
+
+
+bool FomodInstallerDialog::eventFilter(QObject *object, QEvent *event)
+{
+ QAbstractButton *button = qobject_cast<QAbstractButton*>(object);
+ if ((button != NULL) && (event->type() == QEvent::HoverEnter)) {
+ highlightControl(button);
+
+ }
+ return QDialog::eventFilter(object, event);
+}
+
+
+QString FomodInstallerDialog::readContent(QXmlStreamReader &reader)
+{
+ if (reader.readNext() == QXmlStreamReader::Characters) {
+ return reader.text().toString();
+ } else {
+ return QString();
+ }
+}
+
+
+void FomodInstallerDialog::parseInfo(const QByteArray &data)
+{
+ QXmlStreamReader reader(data);
+/* while (reader.readNext() != QXmlStreamReader::StartDocument) {}
+ QTextDecoder *decoder = QTextCodec::codecForName(reader.documentEncoding().toLocal8Bit())->makeDecoder(QTextCodec::ConvertInvalidToNull);
+ QString test(decoder->toUnicode(data));
+ qDebug("test: %d, %s", test.isNull(), test.toUtf8().constData());
+
+ qDebug(">%s<", reader.documentEncoding().toUtf8().constData());*/
+ while (!reader.atEnd() && !reader.hasError()) {
+ switch (reader.readNext()) {
+ case QXmlStreamReader::StartElement: {
+ if (reader.name() == "Name") {
+ if (ui->nameEdit->text().isEmpty() || m_NameWasGuessed) {
+ ui->nameEdit->setText(readContent(reader));
+ }
+ } else if (reader.name() == "Author") {
+ ui->authorLabel->setText(readContent(reader));
+ } else if (reader.name() == "Version") {
+ ui->versionLabel->setText(readContent(reader));
+ } else if (reader.name() == "Website") {
+ QString url = readContent(reader);
+ ui->websiteLabel->setText(tr("<a href=\"%1\">Link</a>").arg(url));
+ ui->websiteLabel->setToolTip(url);
+ }
+ } break;
+ default: {} break;
+ }
+ }
+ if (reader.hasError()) {
+ throw MyException(tr("failed to parse info.xml: %1 (%2) (line %3, column %4)")
+ .arg(reader.errorString())
+ .arg(reader.error())
+ .arg(reader.lineNumber())
+ .arg(reader.columnNumber()));
+
+ }
+}
+
+
+FomodInstallerDialog::ItemOrder FomodInstallerDialog::getItemOrder(const QString &orderString)
+{
+ if (orderString == "Ascending") {
+ return ORDER_ASCENDING;
+ } else if (orderString == "Descending") {
+ return ORDER_DESCENDING;
+ } else if (orderString == "Explicit") {
+ return ORDER_EXPLICIT;
+ } else {
+ throw MyException(tr("unsupported order type %1").arg(orderString));
+ }
+}
+
+
+FomodInstallerDialog::GroupType FomodInstallerDialog::getGroupType(const QString &typeString)
+{
+ if (typeString == "SelectAtLeastOne") {
+ return TYPE_SELECTATLEASTONE;
+ } else if (typeString == "SelectAtMostOne") {
+ return TYPE_SELECTATMOSTONE;
+ } else if (typeString == "SelectExactlyOne") {
+ return TYPE_SELECTEXACTLYONE;
+ } else if (typeString == "SelectAny") {
+ return TYPE_SELECTANY;
+ } else if (typeString == "SelectAll") {
+ return TYPE_SELECTALL;
+ } else {
+ throw MyException(tr("unsupported group type %1").arg(typeString));
+ }
+}
+
+
+FomodInstallerDialog::PluginType FomodInstallerDialog::getPluginType(const QString &typeString)
+{
+ if (typeString == "Required") {
+ return FomodInstallerDialog::TYPE_REQUIRED;
+ } else if (typeString == "Optional") {
+ return FomodInstallerDialog::TYPE_OPTIONAL;
+ } else if (typeString == "Recommended") {
+ return FomodInstallerDialog::TYPE_RECOMMENDED;
+ } else if (typeString == "NotUsable") {
+ return FomodInstallerDialog::TYPE_NOTUSABLE;
+ } else if (typeString == "CouldBeUsable") {
+ return FomodInstallerDialog::TYPE_COULDBEUSABLE;
+ } else {
+ qCritical("invalid plugin type %s", typeString.toUtf8().constData());
+ return FomodInstallerDialog::TYPE_OPTIONAL;
+ }
+}
+
+
+void FomodInstallerDialog::readFileList(QXmlStreamReader &reader, std::vector<FileDescriptor*> &fileList)
+{
+ QStringRef openTag = reader.name();
+ while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
+ (reader.name() == openTag))) {
+ if (reader.tokenType() == QXmlStreamReader::StartElement) {
+ if ((reader.name() == "folder") ||
+ (reader.name() == "file")) {
+ QXmlStreamAttributes attributes = reader.attributes();
+ FileDescriptor *file = new FileDescriptor(this);
+ file->m_Source = attributes.value("source").toString();
+ file->m_Destination = attributes.hasAttribute("destination") ? attributes.value("destination").toString()
+ : file->m_Source;
+ file->m_Priority = attributes.hasAttribute("priority") ? attributes.value("priority").string()->toInt()
+ : 0;
+ file->m_IsFolder = reader.name() == "folder";
+ file->m_InstallIfUsable = attributes.hasAttribute("installIfUsable") ? (attributes.value("installIfUsable").compare("true") == 0)
+ : false;
+ file->m_AlwaysInstall = attributes.hasAttribute("alwaysInstall") ? (attributes.value("alwaysInstall").compare("true") == 0)
+ : false;
+
+ fileList.push_back(file);
+ }
+ }
+ }
+}
+
+
+void FomodInstallerDialog::readPluginType(QXmlStreamReader &reader, Plugin &plugin)
+{
+ plugin.m_Type = TYPE_OPTIONAL;
+ while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
+ (reader.name() == "typeDescriptor"))) {
+ if (reader.tokenType() == QXmlStreamReader::StartElement) {
+ if (reader.name() == "type") {
+ plugin.m_Type = getPluginType(reader.attributes().value("name").toString());
+ }
+ }
+ }
+}
+
+
+void FomodInstallerDialog::readConditionFlags(QXmlStreamReader &reader, Plugin &plugin)
+{
+ while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
+ (reader.name() == "conditionFlags"))) {
+ if (reader.tokenType() == QXmlStreamReader::StartElement) {
+ if (reader.name() == "flag") {
+ QString name = reader.attributes().value("name").toString();
+ plugin.m_Conditions.push_back(Condition(name, readContent(reader)));
+ }
+ }
+ }
+}
+
+
+bool FomodInstallerDialog::byPriority(const FileDescriptor *LHS, const FileDescriptor *RHS)
+{
+ return LHS->m_Priority < RHS->m_Priority;
+}
+
+
+FomodInstallerDialog::Plugin FomodInstallerDialog::readPlugin(QXmlStreamReader &reader)
+{
+ Plugin result;
+ result.m_Name = reader.attributes().value("name").toString();
+
+ while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
+ (reader.name() == "plugin"))) {
+// QXmlStreamReader::TokenType type = reader.tokenType();
+// QString name = reader.name().toUtf8();
+ if (reader.tokenType() == QXmlStreamReader::StartElement) {
+ if (reader.name() == "description") {
+ result.m_Description = readContent(reader).trimmed();
+ } else if (reader.name() == "image") {
+ result.m_ImagePath = reader.attributes().value("path").toString();
+ } else if (reader.name() == "typeDescriptor") {
+ readPluginType(reader, result);
+ } else if (reader.name() == "conditionFlags") {
+ readConditionFlags(reader, result);
+ } else if (reader.name() == "files") {
+ readFileList(reader, result.m_Files);
+ }
+ }
+ }
+
+ std::sort(result.m_Files.begin(), result.m_Files.end(), byPriority);
+
+ return result;
+}
+
+
+void FomodInstallerDialog::readPlugins(QXmlStreamReader &reader, GroupType groupType, QLayout *layout)
+{
+ ItemOrder pluginOrder = reader.attributes().hasAttribute("order") ? getItemOrder(reader.attributes().value("order").toString())
+ : ORDER_ASCENDING;
+ bool first = true;
+ bool maySelectMore = true;
+
+ std::vector<QAbstractButton*> controls;
+
+ while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
+ (reader.name() == "plugins"))) {
+ if (reader.tokenType() == QXmlStreamReader::StartElement) {
+ if (reader.name() == "plugin") {
+ Plugin plugin = readPlugin(reader);
+ QAbstractButton *newControl = NULL;
+ switch (groupType) {
+ case TYPE_SELECTATLEASTONE:
+ case TYPE_SELECTANY: {
+ newControl = new QCheckBox(plugin.m_Name);
+ } break;
+ case TYPE_SELECTATMOSTONE: {
+ newControl = new QRadioButton(plugin.m_Name);
+ } break;
+ case TYPE_SELECTEXACTLYONE: {
+ newControl = new QRadioButton(plugin.m_Name);
+ if (first) {
+ newControl->setChecked(true);
+ }
+ } break;
+ case TYPE_SELECTALL: {
+ newControl = new QCheckBox(plugin.m_Name);
+ newControl->setChecked(true);
+ newControl->setEnabled(false);
+ } break;
+ }
+ newControl->setObjectName("choice");
+ switch (plugin.m_Type) {
+ case TYPE_REQUIRED: {
+ newControl->setChecked(true);
+ newControl->setEnabled(false);
+ newControl->setToolTip(tr("This component is required"));
+ } break;
+ case TYPE_RECOMMENDED: {
+ if (maySelectMore) {
+ newControl->setChecked(true);
+ }
+ newControl->setToolTip(tr("It is recommended you enable this component"));
+ if ((groupType == TYPE_SELECTATMOSTONE) || (groupType == TYPE_SELECTEXACTLYONE)) {
+ maySelectMore = false;
+ }
+ } break;
+ case TYPE_OPTIONAL: {
+ newControl->setToolTip(tr("Optional component"));
+ } break;
+ case TYPE_NOTUSABLE: {
+ newControl->setChecked(false);
+ newControl->setEnabled(false);
+ newControl->setToolTip(tr("This component is not usable in combination with other installed plugins"));
+ } break;
+ case TYPE_COULDBEUSABLE: {
+ newControl->setCheckable(true);
+ newControl->setIcon(QIcon(":/new/guiresources/resources/dialog-warning_16.png"));
+ newControl->setToolTip(tr("You may be experiencing instability in combination with other installed plugins"));
+ } break;
+ }
+
+ newControl->setProperty("plugintype", plugin.m_Type);
+ newControl->setProperty("screenshot", plugin.m_ImagePath);
+ newControl->setProperty("description", plugin.m_Description);
+ QVariantList fileList;
+ for (std::vector<FileDescriptor*>::iterator iter = plugin.m_Files.begin(); iter != plugin.m_Files.end(); ++iter) {
+ fileList.append(qVariantFromValue(*iter));
+ }
+ newControl->setProperty("files", fileList);
+ QVariantList conditionFlags;
+ for (std::vector<Condition>::const_iterator iter = plugin.m_Conditions.begin(); iter != plugin.m_Conditions.end(); ++iter) {
+ if (iter->m_Name.length() != 0) {
+ conditionFlags.append(qVariantFromValue(Condition(iter->m_Name, iter->m_Value)));
+ }
+ }
+ newControl->setProperty("conditionFlags", conditionFlags);
+ newControl->installEventFilter(this);
+ controls.push_back(newControl);
+ first = false;
+ }
+ }
+ }
+
+ if (pluginOrder == ORDER_ASCENDING) {
+ std::sort(controls.begin(), controls.end(), ControlsAscending);
+ } else if (pluginOrder == ORDER_DESCENDING) {
+ std::sort(controls.begin(), controls.end(), ControlsDescending);
+ }
+
+ for (std::vector<QAbstractButton*>::const_iterator iter = controls.begin(); iter != controls.end(); ++iter) {
+ layout->addWidget(*iter);
+ }
+
+ if (groupType == TYPE_SELECTATMOSTONE) {
+ QRadioButton *newButton = new QRadioButton(tr("None"));
+ if (maySelectMore) {
+ newButton->setChecked(true);
+ }
+ layout->addWidget(newButton);
+ }
+}
+
+
+void FomodInstallerDialog::readGroup(QXmlStreamReader &reader, QLayout *layout)
+{
+ //FileGroup result;
+ QString name = reader.attributes().value("name").toString();
+ GroupType type = getGroupType(reader.attributes().value("type").toString());
+
+ if (type == TYPE_SELECTATLEASTONE) {
+ QLabel *label = new QLabel(tr("Select one or more of these options:"));
+ layout->addWidget(label);
+ }
+
+ QGroupBox *groupBox = new QGroupBox(name);
+
+ QVBoxLayout *groupLayout = new QVBoxLayout;
+
+ while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
+ (reader.name() == "group"))) {
+ if (reader.tokenType() == QXmlStreamReader::StartElement) {
+ if (reader.name() == "plugins") {
+ readPlugins(reader, type, groupLayout);
+ }
+ }
+ }
+
+ groupBox->setLayout(groupLayout);
+ layout->addWidget(groupBox);
+}
+
+
+void FomodInstallerDialog::readGroups(QXmlStreamReader &reader, QLayout *layout)
+{
+ while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
+ (reader.name() == "optionalFileGroups"))) {
+ if (reader.tokenType() == QXmlStreamReader::StartElement) {
+ if (reader.name() == "group") {
+ readGroup(reader, layout);
+ }
+ }
+ }
+}
+
+
+void FomodInstallerDialog::readVisible(QXmlStreamReader &reader, QVariantList &conditions)
+{
+ while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
+ (reader.name() == "visible"))) {
+ if (reader.tokenType() == QXmlStreamReader::StartElement) {
+ if (reader.name() == "flagDependency") {
+ Condition condition(reader.attributes().value("flag").toString(),
+ reader.attributes().value("value").toString());
+ conditions.append(qVariantFromValue(condition));
+ }
+ }
+ }
+}
+
+QGroupBox *FomodInstallerDialog::readInstallerStep(QXmlStreamReader &reader)
+{
+ QString name = reader.attributes().value("name").toString();
+ QGroupBox *page = new QGroupBox(name);
+ QVBoxLayout *pageLayout = new QVBoxLayout;
+ QScrollArea *scrollArea = new QScrollArea;
+ QFrame *scrolledArea = new QFrame;
+ QVBoxLayout *scrollLayout = new QVBoxLayout;
+
+ QVariantList conditions;
+
+ while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
+ (reader.name() == "installStep"))) {
+ if (reader.tokenType() == QXmlStreamReader::StartElement) {
+ if (reader.name() == "optionalFileGroups") {
+ readGroups(reader, scrollLayout);
+ } else if (reader.name() == "visible") {
+ readVisible(reader, conditions);
+ }
+ }
+ }
+ if (conditions.length() != 0) {
+ page->setProperty("conditions", conditions);
+ }
+
+ scrolledArea->setLayout(scrollLayout);
+ scrollArea->setWidget(scrolledArea);
+ scrollArea->setWidgetResizable(true);
+ pageLayout->addWidget(scrollArea);
+ page->setLayout(pageLayout);
+ return page;
+}
+
+
+void FomodInstallerDialog::readInstallerSteps(QXmlStreamReader &reader)
+{
+ ItemOrder stepOrder = reader.attributes().hasAttribute("order") ? getItemOrder(reader.attributes().value("order").toString())
+ : ORDER_ASCENDING;
+
+ std::vector<QGroupBox*> pages;
+
+ while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
+ (reader.name() == "installSteps"))) {
+ if (reader.tokenType() == QXmlStreamReader::StartElement) {
+ if (reader.name() == "installStep") {
+ pages.push_back(readInstallerStep(reader));
+ }
+ }
+ }
+
+ if (stepOrder == ORDER_ASCENDING) {
+ std::sort(pages.begin(), pages.end(), PagesAscending);
+ } else if (stepOrder == ORDER_DESCENDING) {
+ std::sort(pages.begin(), pages.end(), PagesDescending);
+ }
+
+ for (std::vector<QGroupBox*>::const_iterator iter = pages.begin(); iter != pages.end(); ++iter) {
+ ui->stepsStack->addWidget(*iter);
+ }
+}
+
+
+FomodInstallerDialog::ConditionalInstall FomodInstallerDialog::readConditionalPattern(QXmlStreamReader &reader)
+{
+ ConditionalInstall result;
+ result.m_Operator = ConditionalInstall::OP_AND;
+ while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
+ (reader.name() == "pattern"))) {
+ if (reader.tokenType() == QXmlStreamReader::StartElement) {
+ if (reader.name() == "dependencies") {
+ QStringRef dependencyOperator = reader.attributes().value("operator");
+ if (dependencyOperator == "And") {
+ result.m_Operator = ConditionalInstall::OP_AND;
+ } else if (dependencyOperator == "Or") {
+ result.m_Operator = ConditionalInstall::OP_OR;
+ } // otherwise operator is not set (which we can ignore) or invalid (which we should report actually)
+
+ while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
+ (reader.name() == "dependencies"))) {
+ if (reader.tokenType() == QXmlStreamReader::StartElement) {
+ if (reader.name() == "flagDependency") {
+ result.m_Conditions.push_back(Condition(reader.attributes().value("flag").toString(),
+ reader.attributes().value("value").toString()));
+ }
+ }
+ }
+ } else if (reader.name() == "files") {
+ readFileList(reader, result.m_Files);
+ }
+ }
+ }
+ return result;
+}
+
+
+void FomodInstallerDialog::readConditionalFileInstalls(QXmlStreamReader &reader)
+{
+ while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
+ (reader.name() == "conditionalFileInstalls"))) {
+ if (reader.tokenType() == QXmlStreamReader::StartElement) {
+ if (reader.name() == "patterns") {
+ while (!((reader.readNext() == QXmlStreamReader::EndElement) &&
+ (reader.name() == "patterns"))) {
+ if (reader.tokenType() == QXmlStreamReader::StartElement) {
+ if (reader.name() == "pattern") {
+ m_ConditionalInstalls.push_back(readConditionalPattern(reader));
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+void FomodInstallerDialog::parseModuleConfig(const QByteArray &data)
+{
+ QXmlStreamReader reader(data);
+ while (!reader.atEnd() && !reader.hasError()) {
+ switch (reader.readNext()) {
+ case QXmlStreamReader::StartElement: {
+ if (reader.name() == "installSteps") {
+ readInstallerSteps(reader);
+ } else if (reader.name() == "requiredInstallFiles") {
+ readFileList(reader, m_RequiredFiles);
+ } else if (reader.name() == "conditionalFileInstalls") {
+ readConditionalFileInstalls(reader);
+ }
+ } break;
+ default: {} break;
+ }
+ }
+ if (reader.hasError()) {
+ reportError(tr("failed to parse ModuleConfig.xml: %1 - %2").arg(reader.errorString()).arg(reader.lineNumber()));
+ }
+ activateCurrentPage();
+}
+
+
+void FomodInstallerDialog::on_manualBtn_clicked()
+{
+ m_Manual = true;
+ this->reject();
+}
+
+void FomodInstallerDialog::on_cancelBtn_clicked()
+{
+ this->reject();
+}
+
+
+void FomodInstallerDialog::on_websiteLabel_linkActivated(const QString &link)
+{
+ ::ShellExecuteW(NULL, L"open", ToWString(link).c_str(), NULL, NULL, SW_SHOWNORMAL);
+}
+
+
+void FomodInstallerDialog::activateCurrentPage()
+{
+ QList<QAbstractButton*> choices = ui->stepsStack->currentWidget()->findChildren<QAbstractButton*>("choice");
+ if (choices.count() > 0) {
+ highlightControl(choices.at(0));
+ }
+}
+
+
+bool FomodInstallerDialog::testCondition(int maxIndex, const QString &flag, const QString &value)
+{
+ // iterate through all set condition flags on all activated controls on all visible pages if one of them matches the condition
+ for (int i = 0; i < maxIndex; ++i) {
+ if (testVisible(i)) {
+ QWidget *page = ui->stepsStack->widget(i);
+ QList<QAbstractButton*> choices = page->findChildren<QAbstractButton*>("choice");
+ foreach (QAbstractButton* choice, choices) {
+ if (choice->isChecked()) {
+ QVariant temp = choice->property("conditionFlags");
+ if (temp.isValid()) {
+ QVariantList conditionFlags = temp.toList();
+ for (QVariantList::const_iterator iter = conditionFlags.begin(); iter != conditionFlags.end(); ++iter) {
+ Condition condition = iter->value<Condition>();
+ if ((condition.m_Name == flag) && (condition.m_Value == value)) {
+ return true;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ return false;
+}
+
+
+bool FomodInstallerDialog::testVisible(int pageIndex)
+{
+ QWidget *page = ui->stepsStack->widget(pageIndex);
+ QVariant temp = page->property("conditions");
+ if (temp.isValid()) {
+ QVariantList conditions = temp.toList();
+ for (QVariantList::const_iterator iter = conditions.begin(); iter != conditions.end(); ++iter) {
+ Condition condition = iter->value<Condition>();
+ if (!testCondition(pageIndex, condition.m_Name, condition.m_Value)) {
+ return false;
+ }
+ }
+ return true;
+ } else {
+ return true;
+ }
+}
+
+
+bool FomodInstallerDialog::nextPage()
+{
+ int index = ui->stepsStack->currentIndex() + 1;
+ while (index < ui->stepsStack->count()) {
+ if (testVisible(index)) {
+ ui->stepsStack->setCurrentIndex(index);
+ return true;
+ }
+ ++index;
+ }
+ // no more visible pages -> install
+ return false;
+}
+
+
+void FomodInstallerDialog::on_nextBtn_clicked()
+{
+ if (ui->stepsStack->currentIndex() == ui->stepsStack->count() - 1) {
+ this->accept();
+ } else {
+ if (nextPage()) {
+ if (ui->stepsStack->currentIndex() == ui->stepsStack->count() - 1) {
+ ui->nextBtn->setText(tr("Install"));
+ }
+ ui->prevBtn->setEnabled(true);
+ activateCurrentPage();
+ } else {
+ this->accept();
+ }
+ }
+}
+
+void FomodInstallerDialog::on_prevBtn_clicked()
+{
+ if (ui->stepsStack->currentIndex() != 0) {
+ ui->stepsStack->setCurrentIndex(ui->stepsStack->currentIndex() - 1);
+ ui->nextBtn->setText(tr("Next"));
+ }
+ if (ui->stepsStack->currentIndex() == 0) {
+ ui->prevBtn->setEnabled(false);
+ }
+ activateCurrentPage();
+}
diff --git a/src/fomodinstallerdialog.h b/src/fomodinstallerdialog.h
index 0fb5a5ec..875146a6 100644
--- a/src/fomodinstallerdialog.h
+++ b/src/fomodinstallerdialog.h
@@ -17,194 +17,198 @@ 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 FOMODINSTALLERDIALOG_H
-#define FOMODINSTALLERDIALOG_H
-
-#include "installdialog.h"
-#include <QDialog>
-#include <QAbstractButton>
-#include <QXmlStreamReader>
-#include <QGroupBox>
-#include <QSharedPointer>
-
-namespace Ui {
-class FomodInstallerDialog;
-}
-
-
-class Condition : public QObject {
- Q_OBJECT
-public:
- Condition(QObject *parent = NULL) : QObject(parent) { }
- Condition(const Condition &reference) : QObject(reference.parent()), m_Name(reference.m_Name), m_Value(reference.m_Value) { }
- Condition(const QString &name, const QString &value) : QObject(), m_Name(name), m_Value(value) { }
- QString m_Name;
- QString m_Value;
-private:
- Condition &operator=(const Condition&);
-};
-
-Q_DECLARE_METATYPE(Condition)
-
-
-class FileDescriptor : public QObject {
- Q_OBJECT
-public:
- FileDescriptor(QObject *parent)
- : QObject(parent), m_Source(), m_Destination(), m_Priority(0), m_IsFolder(false), m_AlwaysInstall(false),
- m_InstallIfUsable(false) {}
- FileDescriptor(const FileDescriptor &reference)
- : QObject(reference.parent()), m_Source(reference.m_Source), m_Destination(reference.m_Destination),
- m_Priority(reference.m_Priority), m_IsFolder(reference.m_IsFolder), m_AlwaysInstall(reference.m_AlwaysInstall),
- m_InstallIfUsable(reference.m_InstallIfUsable) {}
- QString m_Source;
- QString m_Destination;
- int m_Priority;
- bool m_IsFolder;
- bool m_AlwaysInstall;
- bool m_InstallIfUsable;
-private:
- FileDescriptor &operator=(const FileDescriptor&);
-};
-
-Q_DECLARE_METATYPE(FileDescriptor*)
-
-
-class FomodInstallerDialog : public QDialog
-{
- Q_OBJECT
-
-public:
- explicit FomodInstallerDialog(const QString &modName, const QString &fomodPath, QWidget *parent = 0);
- ~FomodInstallerDialog();
-
- void initData();
-
- /**
- * @return bool true if the user requested the manual dialog
- **/
- bool manualRequested() const { return m_Manual; }
-
- /**
- * @return QString the (user-modified) name to be used for the mod
- **/
- QString getName() const;
-
- /**
- * @brief retrieve the updated archive tree from the dialog. The caller is responsible to delete the returned tree.
- *
- * @note This call is destructive on the input tree!
- *
- * @param tree input tree. (TODO isn't this the same as the tree passed in the constructor?)
- * @return DataTree* a new tree with only the selected options and directories arranged correctly. The caller takes custody of this pointer!
- **/
- DirectoryTree *updateTree(DirectoryTree *tree);
-
-protected:
-
- virtual bool eventFilter(QObject *object, QEvent *event);
-
-private slots:
-
- void on_cancelBtn_clicked();
-
- void on_manualBtn_clicked();
-
- void on_websiteLabel_linkActivated(const QString &link);
-
- void on_nextBtn_clicked();
-
- void on_prevBtn_clicked();
-
-private:
-
- enum ItemOrder {
- ORDER_ASCENDING,
- ORDER_DESCENDING,
- ORDER_EXPLICIT
- };
-
- enum GroupType {
- TYPE_SELECTATLEASTONE,
- TYPE_SELECTATMOSTONE,
- TYPE_SELECTEXACTLYONE,
- TYPE_SELECTANY,
- TYPE_SELECTALL
- };
-
- enum PluginType {
- TYPE_REQUIRED,
- TYPE_RECOMMENDED,
- TYPE_OPTIONAL,
- TYPE_NOTUSABLE,
- TYPE_COULDBEUSABLE
- };
-
- struct Plugin {
- QString m_Name;
- QString m_Description;
- QString m_ImagePath;
- PluginType m_Type;
- std::vector<Condition> m_Conditions;
- std::vector<FileDescriptor*> m_Files;
- };
-
- struct ConditionalInstall {
- enum {
- OP_AND,
- OP_OR
- } m_Operator;
- std::vector<Condition> m_Conditions;
- std::vector<FileDescriptor*> m_Files;
- };
-
-private:
-
- QString readContent(QXmlStreamReader &reader);
- void parseInfo(const QByteArray &data);
-
- static ItemOrder getItemOrder(const QString &orderString);
- static GroupType getGroupType(const QString &typeString);
- static PluginType getPluginType(const QString &typeString);
- static bool byPriority(const FileDescriptor *LHS, const FileDescriptor *RHS);
-
- bool copyFileIterator(DirectoryTree *sourceTree, DirectoryTree *destinationTree, FileDescriptor *descriptor);
- void readFileList(QXmlStreamReader &reader, std::vector<FileDescriptor*> &fileList);
- void readPluginType(QXmlStreamReader &reader, Plugin &plugin);
- void readConditionFlags(QXmlStreamReader &reader, Plugin &plugin);
- FomodInstallerDialog::Plugin readPlugin(QXmlStreamReader &reader);
- void readPlugins(QXmlStreamReader &reader, GroupType groupType, QLayout *layout);
- void readGroup(QXmlStreamReader &reader, QLayout *layout);
- void readGroups(QXmlStreamReader &reader, QLayout *layout);
- void readVisible(QXmlStreamReader &reader, QVariantList &conditions);
- QGroupBox *readInstallerStep(QXmlStreamReader &reader);
- ConditionalInstall readConditionalPattern(QXmlStreamReader &reader);
- void readConditionalFileInstalls(QXmlStreamReader &reader);
- void readInstallerSteps(QXmlStreamReader &reader);
- void parseModuleConfig(const QByteArray &data);
- void highlightControl(QAbstractButton *button);
-
- bool testCondition(int maxIndex, const QString &flag, const QString &value);
- bool testVisible(int pageIndex);
- bool nextPage();
- void activateCurrentPage();
- void moveTree(DirectoryTree::Node *target, DirectoryTree::Node *source);
- DirectoryTree::Node *findNode(DirectoryTree::Node *node, const QString &path, bool create);
- void copyLeaf(DirectoryTree::Node *sourceTree, const QString &sourcePath,
- DirectoryTree::Node *destinationTree, const QString &destinationPath);
-
-private:
-
- Ui::FomodInstallerDialog *ui;
-
- QString m_FomodPath;
- bool m_Manual;
-
-// ItemOrder m_StepOrder;
-// std::vector<InstallationStep> m_Steps;
- std::vector<FileDescriptor*> m_RequiredFiles;
- std::vector<ConditionalInstall> m_ConditionalInstalls;
-
-};
-
-#endif // FOMODINSTALLERDIALOG_H
+#ifndef FOMODINSTALLERDIALOG_H
+#define FOMODINSTALLERDIALOG_H
+
+#include "installdialog.h"
+#include <QDialog>
+#include <QAbstractButton>
+#include <QXmlStreamReader>
+#include <QGroupBox>
+#include <QSharedPointer>
+
+namespace Ui {
+class FomodInstallerDialog;
+}
+
+
+class Condition : public QObject {
+ Q_OBJECT
+public:
+ Condition(QObject *parent = NULL) : QObject(parent) { }
+ Condition(const Condition &reference) : QObject(reference.parent()), m_Name(reference.m_Name), m_Value(reference.m_Value) { }
+ Condition(const QString &name, const QString &value) : QObject(), m_Name(name), m_Value(value) { }
+ QString m_Name;
+ QString m_Value;
+private:
+ Condition &operator=(const Condition&);
+};
+
+Q_DECLARE_METATYPE(Condition)
+
+
+class FileDescriptor : public QObject {
+ Q_OBJECT
+public:
+ FileDescriptor(QObject *parent)
+ : QObject(parent), m_Source(), m_Destination(), m_Priority(0), m_IsFolder(false), m_AlwaysInstall(false),
+ m_InstallIfUsable(false) {}
+ FileDescriptor(const FileDescriptor &reference)
+ : QObject(reference.parent()), m_Source(reference.m_Source), m_Destination(reference.m_Destination),
+ m_Priority(reference.m_Priority), m_IsFolder(reference.m_IsFolder), m_AlwaysInstall(reference.m_AlwaysInstall),
+ m_InstallIfUsable(reference.m_InstallIfUsable) {}
+ QString m_Source;
+ QString m_Destination;
+ int m_Priority;
+ bool m_IsFolder;
+ bool m_AlwaysInstall;
+ bool m_InstallIfUsable;
+private:
+ FileDescriptor &operator=(const FileDescriptor&);
+};
+
+Q_DECLARE_METATYPE(FileDescriptor*)
+
+
+class FomodInstallerDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit FomodInstallerDialog(const QString &modName, bool nameWasGuessed, const QString &fomodPath, QWidget *parent = 0);
+ ~FomodInstallerDialog();
+
+ void initData();
+
+ /**
+ * @return bool true if the user requested the manual dialog
+ **/
+ bool manualRequested() const { return m_Manual; }
+
+ /**
+ * @return QString the (user-modified) name to be used for the mod
+ **/
+ QString getName() const;
+
+ /**
+ * @brief retrieve the updated archive tree from the dialog. The caller is responsible to delete the returned tree.
+ *
+ * @note This call is destructive on the input tree!
+ *
+ * @param tree input tree. (TODO isn't this the same as the tree passed in the constructor?)
+ * @return DataTree* a new tree with only the selected options and directories arranged correctly. The caller takes custody of this pointer!
+ **/
+ DirectoryTree *updateTree(DirectoryTree *tree);
+
+protected:
+
+ virtual bool eventFilter(QObject *object, QEvent *event);
+
+private slots:
+
+ void on_cancelBtn_clicked();
+
+ void on_manualBtn_clicked();
+
+ void on_websiteLabel_linkActivated(const QString &link);
+
+ void on_nextBtn_clicked();
+
+ void on_prevBtn_clicked();
+
+private:
+
+ enum ItemOrder {
+ ORDER_ASCENDING,
+ ORDER_DESCENDING,
+ ORDER_EXPLICIT
+ };
+
+ enum GroupType {
+ TYPE_SELECTATLEASTONE,
+ TYPE_SELECTATMOSTONE,
+ TYPE_SELECTEXACTLYONE,
+ TYPE_SELECTANY,
+ TYPE_SELECTALL
+ };
+
+ enum PluginType {
+ TYPE_REQUIRED,
+ TYPE_RECOMMENDED,
+ TYPE_OPTIONAL,
+ TYPE_NOTUSABLE,
+ TYPE_COULDBEUSABLE
+ };
+
+ struct Plugin {
+ QString m_Name;
+ QString m_Description;
+ QString m_ImagePath;
+ PluginType m_Type;
+ std::vector<Condition> m_Conditions;
+ std::vector<FileDescriptor*> m_Files;
+ };
+
+ struct ConditionalInstall {
+ enum {
+ OP_AND,
+ OP_OR
+ } m_Operator;
+ std::vector<Condition> m_Conditions;
+ std::vector<FileDescriptor*> m_Files;
+ };
+
+private:
+
+ static int bomOffset(const QByteArray &buffer);
+
+ QString readContent(QXmlStreamReader &reader);
+ void parseInfo(const QByteArray &data);
+
+ static ItemOrder getItemOrder(const QString &orderString);
+ static GroupType getGroupType(const QString &typeString);
+ static PluginType getPluginType(const QString &typeString);
+ static bool byPriority(const FileDescriptor *LHS, const FileDescriptor *RHS);
+
+ bool copyFileIterator(DirectoryTree *sourceTree, DirectoryTree *destinationTree, FileDescriptor *descriptor);
+ void readFileList(QXmlStreamReader &reader, std::vector<FileDescriptor*> &fileList);
+ void readPluginType(QXmlStreamReader &reader, Plugin &plugin);
+ void readConditionFlags(QXmlStreamReader &reader, Plugin &plugin);
+ FomodInstallerDialog::Plugin readPlugin(QXmlStreamReader &reader);
+ void readPlugins(QXmlStreamReader &reader, GroupType groupType, QLayout *layout);
+ void readGroup(QXmlStreamReader &reader, QLayout *layout);
+ void readGroups(QXmlStreamReader &reader, QLayout *layout);
+ void readVisible(QXmlStreamReader &reader, QVariantList &conditions);
+ QGroupBox *readInstallerStep(QXmlStreamReader &reader);
+ ConditionalInstall readConditionalPattern(QXmlStreamReader &reader);
+ void readConditionalFileInstalls(QXmlStreamReader &reader);
+ void readInstallerSteps(QXmlStreamReader &reader);
+ void parseModuleConfig(const QByteArray &data);
+ void highlightControl(QAbstractButton *button);
+
+ bool testCondition(int maxIndex, const QString &flag, const QString &value);
+ bool testVisible(int pageIndex);
+ bool nextPage();
+ void activateCurrentPage();
+ void moveTree(DirectoryTree::Node *target, DirectoryTree::Node *source);
+ DirectoryTree::Node *findNode(DirectoryTree::Node *node, const QString &path, bool create);
+ void copyLeaf(DirectoryTree::Node *sourceTree, const QString &sourcePath,
+ DirectoryTree::Node *destinationTree, const QString &destinationPath);
+
+private:
+
+ Ui::FomodInstallerDialog *ui;
+
+ bool m_NameWasGuessed;
+
+ QString m_FomodPath;
+ bool m_Manual;
+
+// ItemOrder m_StepOrder;
+// std::vector<InstallationStep> m_Steps;
+ std::vector<FileDescriptor*> m_RequiredFiles;
+ std::vector<ConditionalInstall> m_ConditionalInstalls;
+
+};
+
+#endif // FOMODINSTALLERDIALOG_H
diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp
index e7784771..a6f062de 100644
--- a/src/installationmanager.cpp
+++ b/src/installationmanager.cpp
@@ -499,9 +499,9 @@ bool InstallationManager::testOverwrite(const QString &modsDirectory, QString &m
QLineEdit::Normal, modName, &ok);
if (ok && !name.isEmpty()) {
modName = name;
- if (!ensureValidModName(modName)) {
- return false;
- }
+ if (!ensureValidModName(modName)) {
+ return false;
+ }
targetDirectory = QDir::fromNativeSeparators(modsDirectory.mid(0).append("\\").append(modName));
}
} else if (overwriteDialog.action() == QueryOverwriteDialog::ACT_REPLACE) {
@@ -547,46 +547,46 @@ void InstallationManager::fixModName(QString &name)
{
// name = name.remove("^[ ]*").trimmed();
name = name.simplified();
- while (name.endsWith('.')) name.chop(1);
-
- name.replace(QRegExp("[<>:\"/\\|?*]"), "");
-
- static QString invalidNames[] = { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
- "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" };
- for (int i = 0; i < sizeof(invalidNames) / sizeof(QString); ++i) {
- if (name == invalidNames[i]) {
- name = "";
- break;
- }
- }
+ while (name.endsWith('.')) name.chop(1);
+
+ name.replace(QRegExp("[<>:\"/\\|?*]"), "");
+
+ static QString invalidNames[] = { "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", "COM5", "COM6", "COM7", "COM8", "COM9",
+ "LPT1", "LPT2", "LPT3", "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9" };
+ for (int i = 0; i < sizeof(invalidNames) / sizeof(QString); ++i) {
+ if (name == invalidNames[i]) {
+ name = "";
+ break;
+ }
+ }
}
-bool InstallationManager::ensureValidModName(QString &name)
-{
- fixModName(name);
-
- while (name.isEmpty()) {
- bool ok;
- name = QInputDialog::getText(m_ParentWidget, tr("Invalid name"),
- tr("The name you entered is invalid, please enter a different one."),
- QLineEdit::Normal, "", &ok);
- if (!ok) {
- return false;
- }
- fixModName(name);
- }
- return true;
-}
-
-
-bool InstallationManager::doInstall(const QString &modsDirectory, QString &modName, int modID,
+bool InstallationManager::ensureValidModName(QString &name)
+{
+ fixModName(name);
+
+ while (name.isEmpty()) {
+ bool ok;
+ name = QInputDialog::getText(m_ParentWidget, tr("Invalid name"),
+ tr("The name you entered is invalid, please enter a different one."),
+ QLineEdit::Normal, "", &ok);
+ if (!ok) {
+ return false;
+ }
+ fixModName(name);
+ }
+ return true;
+}
+
+
+bool InstallationManager::doInstall(const QString &modsDirectory, QString &modName, int modID,
const QString &version, const QString &newestVersion, int categoryID)
{
- if (!ensureValidModName(modName)) {
- return false;
- }
-
+ if (!ensureValidModName(modName)) {
+ return false;
+ }
+
// determine target directory
if (!testOverwrite(modsDirectory, modName)) {
return false;
@@ -679,7 +679,7 @@ bool EndsWith(LPCWSTR string, LPCWSTR subString)
bool InstallationManager::installFomodInternal(DirectoryTree *&baseNode, const QString &fomodPath, const QString &modsDirectory,
int modID, const QString &version, const QString &newestVersion, int categoryID,
- QString &modName, bool &manualRequest)
+ QString &modName, bool nameGuessed, bool &manualRequest)
{
qDebug("treating as fomod archive");
@@ -740,7 +740,7 @@ bool InstallationManager::installFomodInternal(DirectoryTree *&baseNode, const Q
bool success = false;
try {
- FomodInstallerDialog dialog(modName, fomodPath);
+ FomodInstallerDialog dialog(modName, nameGuessed, fomodPath);
FileData* const *data;
size_t size;
@@ -924,6 +924,7 @@ bool InstallationManager::install(const QString &fileName, const QString &plugin
QString version = "";
QString newestVersion = "";
int categoryID = 0;
+ bool nameGuessed = false;
QString metaName = fileName.mid(0).append(".meta");
if (QFile(metaName).exists()) {
@@ -945,9 +946,9 @@ bool InstallationManager::install(const QString &fileName, const QString &plugin
{ // 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)) {
+ 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);
@@ -955,6 +956,7 @@ bool InstallationManager::install(const QString &fileName, const QString &plugin
if (modName.isEmpty()) {
modName = guessedModName;
+ nameGuessed = true;
}
}
fixModName(modName);
@@ -1090,8 +1092,8 @@ bool InstallationManager::install(const QString &fileName, const QString &plugin
if (xmlInstaller || nmmInstaller) {
if (!xmlInstaller || (nmmInstaller && !preferIntegrated)) {
- if (!ensureValidModName(modName) ||
- !testOverwrite(modsDirectory, modName)) {
+ if (!ensureValidModName(modName) ||
+ !testOverwrite(modsDirectory, modName)) {
return false;
}
@@ -1120,7 +1122,7 @@ bool InstallationManager::install(const QString &fileName, const QString &plugin
} else {
if (installFomodInternal(baseNode, fomodPath, modsDirectory,
modID, version, newestVersion, categoryID,
- modName, manualRequest)) {
+ modName, nameGuessed, manualRequest)) {
success = true;
}
}
diff --git a/src/installationmanager.h b/src/installationmanager.h
index 9ac97812..054f584a 100644
--- a/src/installationmanager.h
+++ b/src/installationmanager.h
@@ -17,194 +17,194 @@ 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 "installdialog.h"
-
-#include <iinstallationmanager.h>
-#include <iplugininstaller.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 IInstallationManager
-{
- Q_OBJECT
-
-public:
-
- /**
- * @brief constructor
- *
- * @param parent parent object.
- **/
- explicit InstallationManager(QWidget *parent);
-
- ~InstallationManager();
-
- /**
- * @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
- * @param preferIntegrated if true, integrated installers are chosen over external installers
- * @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, const QString &pluginsFileName, const QString &modsDirectory, bool preferIntegrated, bool enableQuickInstall, 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);
-
- void installManual(DirectoryTree::Node *baseNode, DirectoryTree *filesTree, bool &hasIniTweaks, QString &modName, int categoryID, const QString &modsDirectory, QString newestVersion, QString version, int modID, bool success, bool manualRequest);
-
- /**
- * @brief register an installer-plugin
- * @param the installer to register
- */
- void registerInstaller(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);
-
- /**
- * @brief installs an archive
- * @param modName suggested name of the mod
- * @param archiveFile path to the archive to install
- * @return the installation result
- */
- virtual IPluginInstaller::EInstallResult installArchive(const QString &modName, const QString &archiveName);
-
-private:
-
- void queryPassword(LPSTR password);
- void updateProgress(float percentage);
- void updateProgressFile(LPCWSTR fileName);
- void report7ZipError(LPCWSTR errorMessage);
-
- void dummyProgressFile(LPCWSTR) {}
-
- 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 DirectoryTree::Node *baseNode);
-
- // recursive worker function for mapToArchive
- void mapToArchive(const DirectoryTree::Node *node, std::wstring path, FileData * const *data);
- bool unpackPackageTXT();
- bool unpackSingleFile(const QString &fileName);
-
-
- bool isSimpleArchiveTopLayer(const DirectoryTree::Node *node, bool bainStyle);
- DirectoryTree::Node *getSimpleArchiveBase(DirectoryTree *dataTree);
- bool checkBainPackage(DirectoryTree *dataTree);
- bool checkFomodPackage(DirectoryTree *dataTree, QString &offset, bool &xmlInstaller);
- bool checkNMMInstaller();
-
- void fixModName(QString &name);
-
- bool testOverwrite(const QString &modsDirectory, QString &modName);
-
- bool doInstall(const QString &modsDirectory, QString &modName,
- int modID, const QString &version, const QString &newestVersion, int categoryID);
-
- bool installFomodExternal(const QString &fileName, const QString &pluginsFileName, const QString &modDirectory);
- bool installFomodInternal(DirectoryTree *&baseNode, const QString &fomodPath, const QString &modsDirectory,
- int modID, const QString &version, const QString &newestVersion,
- int categoryID, QString &modName, bool &manualRequest);
- QString generateBackupName(const QString &directoryName);
-
- bool ensureValidModName(QString &name);
-
-private slots:
-
- void openFile(const QString &fileName);
-
-private:
-
- struct ByPriority {
- bool operator()(IPluginInstaller *LHS, 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;
-
- std::vector<IPluginInstaller*> m_Installers;
- std::set<QString, CaseInsensitive> m_SupportedExtensions;
-
- QString m_NCCPath;
-
- Archive *m_CurrentArchive;
- QString m_CurrentFile;
-
- QProgressDialog m_InstallationProgress;
-
- std::set<QString> m_FilesToDelete;
- std::set<QString> m_TempFilesToDelete;
-
-};
-
-
-#endif // INSTALLATIONMANAGER_H
+#ifndef INSTALLATIONMANAGER_H
+#define INSTALLATIONMANAGER_H
+
+#include "installdialog.h"
+
+#include <iinstallationmanager.h>
+#include <iplugininstaller.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 IInstallationManager
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * @brief constructor
+ *
+ * @param parent parent object.
+ **/
+ explicit InstallationManager(QWidget *parent);
+
+ ~InstallationManager();
+
+ /**
+ * @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
+ * @param preferIntegrated if true, integrated installers are chosen over external installers
+ * @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, const QString &pluginsFileName, const QString &modsDirectory, bool preferIntegrated, bool enableQuickInstall, 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);
+
+ void installManual(DirectoryTree::Node *baseNode, DirectoryTree *filesTree, bool &hasIniTweaks, QString &modName, int categoryID, const QString &modsDirectory, QString newestVersion, QString version, int modID, bool success, bool manualRequest);
+
+ /**
+ * @brief register an installer-plugin
+ * @param the installer to register
+ */
+ void registerInstaller(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);
+
+ /**
+ * @brief installs an archive
+ * @param modName suggested name of the mod
+ * @param archiveFile path to the archive to install
+ * @return the installation result
+ */
+ virtual IPluginInstaller::EInstallResult installArchive(const QString &modName, const QString &archiveName);
+
+private:
+
+ void queryPassword(LPSTR password);
+ void updateProgress(float percentage);
+ void updateProgressFile(LPCWSTR fileName);
+ void report7ZipError(LPCWSTR errorMessage);
+
+ void dummyProgressFile(LPCWSTR) {}
+
+ 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 DirectoryTree::Node *baseNode);
+
+ // recursive worker function for mapToArchive
+ void mapToArchive(const DirectoryTree::Node *node, std::wstring path, FileData * const *data);
+ bool unpackPackageTXT();
+ bool unpackSingleFile(const QString &fileName);
+
+
+ bool isSimpleArchiveTopLayer(const DirectoryTree::Node *node, bool bainStyle);
+ DirectoryTree::Node *getSimpleArchiveBase(DirectoryTree *dataTree);
+ bool checkBainPackage(DirectoryTree *dataTree);
+ bool checkFomodPackage(DirectoryTree *dataTree, QString &offset, bool &xmlInstaller);
+ bool checkNMMInstaller();
+
+ void fixModName(QString &name);
+
+ bool testOverwrite(const QString &modsDirectory, QString &modName);
+
+ bool doInstall(const QString &modsDirectory, QString &modName,
+ int modID, const QString &version, const QString &newestVersion, int categoryID);
+
+ bool installFomodExternal(const QString &fileName, const QString &pluginsFileName, const QString &modDirectory);
+ bool installFomodInternal(DirectoryTree *&baseNode, const QString &fomodPath, const QString &modsDirectory,
+ int modID, const QString &version, const QString &newestVersion,
+ int categoryID, QString &modName, bool nameGuessed, bool &manualRequest);
+ QString generateBackupName(const QString &directoryName);
+
+ bool ensureValidModName(QString &name);
+
+private slots:
+
+ void openFile(const QString &fileName);
+
+private:
+
+ struct ByPriority {
+ bool operator()(IPluginInstaller *LHS, 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;
+
+ std::vector<IPluginInstaller*> m_Installers;
+ std::set<QString, CaseInsensitive> m_SupportedExtensions;
+
+ QString m_NCCPath;
+
+ Archive *m_CurrentArchive;
+ QString m_CurrentFile;
+
+ QProgressDialog m_InstallationProgress;
+
+ std::set<QString> m_FilesToDelete;
+ std::set<QString> m_TempFilesToDelete;
+
+};
+
+
+#endif // INSTALLATIONMANAGER_H
diff --git a/src/main.cpp b/src/main.cpp
index 0f1fb7c3..d452c3b8 100644
--- a/src/main.cpp
+++ b/src/main.cpp
@@ -17,424 +17,426 @@ 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 <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 <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 <QLibrary>
-#include <iostream>
-#include <QMessageBox>
-#include <QSharedMemory>
-#include <QBuffer>
-#include <QSplashScreen>
-#include <QDirIterator>
-#include <eh.h>
-#include <windows_error.h>
-
-#pragma comment(linker, "/manifestDependency:\"name='dlls' processorArchitecture='x86' version='1.0.0.0' type='win32' \"")
-
-
-void removeOldLogfiles()
-{
- QFileInfoList files = QDir(ToQString(GameInfo::instance().getLogDir())).entryInfoList(QStringList("ModOrganizer*.log"),
- QDir::Files, QDir::Name);
-
- if (files.count() > 5) {
- for (int i = 0; i < files.count() - 5; ++i) {
- QFile::remove(files.at(i).absoluteFilePath());
- }
- }
-}
-
-
-// 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()) {
- removeDir(backupDirectory);
- }
-
- // cycle logfile
- removeOldLogfiles();
-
- // 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());
- 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"
- };
-
- static const int NUM_FILES = sizeof(fileNames) / sizeof(QString);
-
- 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 "
- "by email (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"),
- 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();
-}
-
-
-int main(int argc, char *argv[])
-{
- MOApplication application(argc, argv);
- SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
-
- LogBuffer::init(20, QtDebugMsg, application.applicationDirPath().append("/logs/mo_interface.log"));
- QPixmap pixmap(":/MO/gui/splash");
- QSplashScreen splash(pixmap);
- splash.show();
-
- registerMetaTypes();
-
- QStringList arguments = application.arguments();
-
- bool update = false;
- if (arguments.contains("update")) {
- arguments.removeAll("update");
- update = true;
- }
-
- try {
- SingleInstance instance(update);
- if (!instance.primaryInstance()) {
- if ((arguments.size() == 2) &&
- isNxmLink(arguments.at(1))) {
- 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 omoPath[MAX_PATH];
- memset(omoPath, 0, sizeof(TCHAR) * MAX_PATH);
- ::GetModuleFileNameW(NULL, omoPath, MAX_PATH);
- wchar_t *lastBSlash = wcsrchr(omoPath, TEXT('\\'));
- if (lastBSlash != NULL) {
- *lastBSlash = TEXT('\0');
- }
- QSettings settings(ToQString(std::wstring(omoPath).append(L"\\ModOrganizer.ini")), QSettings::IniFormat);
-
- QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray());
-
- bool done = false;
- while (!done) {
- if (!GameInfo::init(omoPath, ToWString(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());
- }
- ExecutablesList executablesList;
-
- executablesList.init();
-
- if (!bootstrap()) { // requires gameinfo to be initialised!
- return -1;
- }
-
- cleanupDir();
-
- 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.endArray();
-
- TutorialManager::init(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getTutorialDir())).append("/"));
-
- application.setStyleFile(settings.value("Settings/style", "").toString());
-
- // 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)) {
- selectedProfileName = arguments.at(profileIndex + 1);
- }
- arguments.removeAt(profileIndex);
- arguments.removeAt(profileIndex);
- }
-
- // 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);
- arguments.removeFirst(); // remove application name (ModOrganizer.exe)
- arguments.removeFirst(); // remove binary name
- // pass the remaining parameters to the binary
- mainWindow.spawnProgram(exeName, arguments.join(" "), selectedProfileName, QDir());
- 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);
- }
-
- mainWindow.show();
-
- if ((arguments.size() > 1) &&
- (isNxmLink(arguments.at(1)))) {
- mainWindow.externalMessage(arguments.at(1));
- }
- splash.finish(&mainWindow);
- return application.exec();
- } catch (const std::exception &e) {
- reportError(e.what());
- return 1;
- }
-}
+#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 <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 <QLibrary>
+#include <iostream>
+#include <QMessageBox>
+#include <QSharedMemory>
+#include <QBuffer>
+#include <QSplashScreen>
+#include <QDirIterator>
+#include <eh.h>
+#include <windows_error.h>
+
+#pragma comment(linker, "/manifestDependency:\"name='dlls' processorArchitecture='x86' version='1.0.0.0' type='win32' \"")
+
+
+void removeOldLogfiles()
+{
+ QFileInfoList files = QDir(ToQString(GameInfo::instance().getLogDir())).entryInfoList(QStringList("ModOrganizer*.log"),
+ QDir::Files, QDir::Name);
+
+ if (files.count() > 5) {
+ for (int i = 0; i < files.count() - 5; ++i) {
+ QFile::remove(files.at(i).absoluteFilePath());
+ }
+ }
+}
+
+
+// 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()) {
+ removeDir(backupDirectory);
+ }
+
+ // cycle logfile
+ removeOldLogfiles();
+
+ // 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());
+ 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"
+ };
+
+ static const int NUM_FILES = sizeof(fileNames) / sizeof(QString);
+
+ 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 "
+ "by email (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"),
+ 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();
+}
+
+
+int main(int argc, char *argv[])
+{
+ MOApplication application(argc, argv);
+ SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
+
+ LogBuffer::init(20, QtDebugMsg, application.applicationDirPath().append("/logs/mo_interface.log"));
+ QPixmap pixmap(":/MO/gui/splash");
+ QSplashScreen splash(pixmap);
+ splash.show();
+
+ registerMetaTypes();
+
+ QStringList arguments = application.arguments();
+
+ bool update = false;
+ if (arguments.contains("update")) {
+ arguments.removeAll("update");
+ update = true;
+ }
+
+ try {
+ SingleInstance instance(update);
+ if (!instance.primaryInstance()) {
+ if ((arguments.size() == 2) &&
+ isNxmLink(arguments.at(1))) {
+ 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 omoPath[MAX_PATH];
+ memset(omoPath, 0, sizeof(TCHAR) * MAX_PATH);
+ ::GetModuleFileNameW(NULL, omoPath, MAX_PATH);
+ wchar_t *lastBSlash = wcsrchr(omoPath, TEXT('\\'));
+ if (lastBSlash != NULL) {
+ *lastBSlash = TEXT('\0');
+ }
+ QSettings settings(ToQString(std::wstring(omoPath).append(L"\\ModOrganizer.ini")), QSettings::IniFormat);
+
+ QString gamePath = QString::fromUtf8(settings.value("gamePath", "").toByteArray());
+
+ bool done = false;
+ while (!done) {
+ if (!GameInfo::init(omoPath, ToWString(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());
+ }
+ ExecutablesList executablesList;
+
+ executablesList.init();
+
+ if (!bootstrap()) { // requires gameinfo to be initialised!
+ return -1;
+ }
+
+ cleanupDir();
+
+ 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();
+
+ TutorialManager::init(QDir::fromNativeSeparators(ToQString(GameInfo::instance().getTutorialDir())).append("/"));
+
+ application.setStyleFile(settings.value("Settings/style", "").toString());
+
+ // 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)) {
+ selectedProfileName = arguments.at(profileIndex + 1);
+ }
+ arguments.removeAt(profileIndex);
+ arguments.removeAt(profileIndex);
+ }
+
+ // 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);
+ arguments.removeFirst(); // remove application name (ModOrganizer.exe)
+ arguments.removeFirst(); // remove binary name
+ // pass the remaining parameters to the binary
+ mainWindow.spawnProgram(exeName, arguments.join(" "), selectedProfileName, QDir());
+ 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);
+ }
+
+ mainWindow.show();
+
+ if ((arguments.size() > 1) &&
+ (isNxmLink(arguments.at(1)))) {
+ mainWindow.externalMessage(arguments.at(1));
+ }
+ splash.finish(&mainWindow);
+ return application.exec();
+ } catch (const std::exception &e) {
+ reportError(e.what());
+ return 1;
+ }
+}
diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp
index 7493505c..dd217447 100644
--- a/src/mainwindow.cpp
+++ b/src/mainwindow.cpp
@@ -17,3735 +17,3887 @@ 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 "profile.h"
-#include "pluginlist.h"
-#include "installdialog.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 "textviewer.h"
-#include "lockeddialog.h"
-#include "syncoverwritedialog.h"
-#include "logbuffer.h"
-#include "downloadlistsortproxy.h"
-#include "modlistsortproxy.h"
-#include "motddialog.h"
-#include "filedialogmemory.h"
-#include "questionboxmemory.h"
-#include "tutorialmanager.h"
-#include "icondelegate.h"
-#include "credentialsdialog.h"
-#include "gameinfoimpl.h"
-#include <gameinfo.h>
-#include <appconfig.h>
-#include <utility.h>
-#include <map>
-#include <ctime>
-#include <QTime>
-#include <util.h>
-#include <QInputDialog>
-#include <QSettings>
-#include <QWhatsThis>
-#include <wchar.h>
-#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 <boost/bind.hpp>
-#include <Psapi.h>
-#include <shlobj.h>
-#include <TlHelp32.h>
-
-//TODO factor out functionality and structure the rest!
-//TODO name slots more consistently
-//TODO turn operations on various trees/lists into actions for easier support in QMenus?
-//TODO verify slots don't throw exceptions
-
-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(NexusInterface::instance()),
- m_OldExecutableIndex(-1), m_GamePath(ToQString(GameInfo::instance().getGameDirectory())),
- m_NexusDialog(NexusInterface::instance()->getAccessManager(), NULL),
- m_DownloadManager(NexusInterface::instance(), this),
- m_InstallationManager(this), m_Translator(NULL), m_TranslatorQt(NULL),
- m_Updater(NexusInterface::instance(), this), m_CategoryFactory(CategoryFactory::instance()),
- m_CurrentProfile(NULL), m_AskForNexusPW(false), m_LoginAttempted(false),
- m_ArchivesInit(false), m_ContextItem(NULL), m_CurrentSaveView(NULL),
- m_GameInfo(new GameInfoImpl())
-{
- ui->setupUi(this);
-
- this->setWindowTitle(ToQString(GameInfo::instance().getGameName()).append(" Mod Organizer v").append(m_Updater.getVersion().canonicalString()));
-
- 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);
-
- updateProblemsButton();
-
- updateToolBar();
- ui->toolBar->blockSignals(true);
- createHelpWidget();
-
- ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure);
-
- // set up mod list
- m_ModListSortProxy = new ModListSortProxy(m_CurrentProfile, this);
- m_ModListSortProxy->setSourceModel(&m_ModList);
- ui->modList->setModel(m_ModListSortProxy);
-
- ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder);
- ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new IconDelegate(m_ModListSortProxy));
- ui->modList->header()->installEventFilter(&m_ModList);
- ui->modList->header()->restoreState(initSettings.value("mod_list_state").toByteArray());
- ui->modList->installEventFilter(&m_ModList);
- // restoreState also seems to restores the resize mode from previous session,
- // I don't really like that
-#if QT_VERSION >= 0x50000
- 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
-
- // 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->header()->restoreState(initSettings.value("plugin_list_state").toByteArray());
- ui->espList->installEventFilter(&m_PluginList);
-#if QT_VERSION >= 0x50000
- 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
-
- QMenu *linkMenu = new QMenu(this);
- 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);
-
- m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory());
-
- NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory());
- NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion());
-
- updateDownloadListDelegate();
- connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), this, SLOT(displayColumnSelection(QPoint)));
-
- connect(&m_DownloadManager, SIGNAL(showMessage(QString)), this, SLOT(showMessage(QString)));
-
- connect(NexusInterface::instance(), SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString)));
- connect(&m_NexusDialog, SIGNAL(requestDownload(QNetworkReply*, int, QString)), this, SLOT(downloadRequested(QNetworkReply*, int, QString)));
-
- ui->savegameList->installEventFilter(this);
- ui->savegameList->setMouseTracking(true);
- connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)),
- this, SLOT(saveSelectionChanged(QListWidgetItem*)));
-
- connect(&m_PluginList, SIGNAL(esplist_changed()), this, SLOT(esplist_changed()));
- 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(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString)));
- connect(&m_ModList, SIGNAL(modlist_changed(int)), m_ModListSortProxy, SLOT(invalidate()));
- connect(&m_ModList, SIGNAL(removeSelectedMods()), this, SLOT(removeMod_clicked()));
-
- 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_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(NexusInterface::instance()->getAccessManager(), SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool)));
- connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString)));
-
- connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString)));
-
- m_DirectoryRefresher.moveToThread(&m_RefresherThread);
- m_RefresherThread.start();
-
- setCompactDownloads(initSettings.value("compact_downloads", false).toBool());
- m_AskForNexusPW = initSettings.value("ask_for_nexuspw", true).toBool();
- setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool());
- FileDialogMemory::restore(initSettings);
-
- fixCategories();
- m_Updater.testForUpdate();
- m_StartTime = QTime::currentTime();
-
- m_Tutorial.expose("modList", &m_ModList);
- m_Tutorial.expose("espList", &m_PluginList);
- loadPlugins();
-}
-
-
-MainWindow::~MainWindow()
-{
- m_RefresherThread.exit();
- m_RefresherThread.wait();
- m_NexusDialog.close();
- delete ui;
- delete m_GameInfo;
-}
-
-
-void MainWindow::resizeEvent(QResizeEvent *event)
-{
- m_Tutorial.resize(event->size());
- QMainWindow::resizeEvent(event);
-}
-
-
-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->setMenu(buttonMenu);
- QAction *newAction = ui->toolBar->insertWidget(sourceAction, button);
- newAction->setObjectName(sourceAction->objectName());
- ui->toolBar->removeAction(sourceAction);
- sourceAction->deleteLater();
- sourceAction = newAction;
-}
-
-
-void MainWindow::updateToolBar()
-{
- QWidget *spacer = new QWidget(ui->toolBar);
- spacer->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred);
-
- actionToToolButton(ui->actionTool);
- actionToToolButton(ui->actionHelp);
-
- foreach (QAction *action, ui->toolBar->actions()) {
- if (action->objectName().length() == 0) {
- // insert spacers
- ui->toolBar->insertWidget(action, spacer);
- }
- }
-}
-
-
-void MainWindow::updateProblemsButton()
-{
- QString problemDescription;
- if (checkForProblems(problemDescription)) {
- ui->actionProblems->setEnabled(true);
- ui->actionProblems->setIconText(tr("Problems"));
- ui->actionProblems->setToolTip(tr("There are potential problems with your setup"));
- } else {
- ui->actionProblems->setIconText(tr("No Problems"));
- ui->actionProblems->setToolTip(tr("Everything seems to be in order"));
- }
-}
-
-
-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;
-}
-
-
-bool MainWindow::checkForProblems(QString &problemDescription)
-{
- problemDescription = "";
-
- foreach (IPluginDiagnose *diagnose, m_DiagnosisPlugins) {
- std::vector<unsigned int> activeProblems = diagnose->activeProblems();
- foreach (unsigned int key, activeProblems) {
- problemDescription.append(tr("<li>%1</li>").arg(diagnose->shortDescription(key)));
- }
- }
-
- QString NCCBinary = QCoreApplication::applicationDirPath().mid(0).append("/NCC/NexusClientCLI.exe");
- if (!QFile::exists(NCCBinary)) {
- problemDescription.append(tr("<li>NCC not installed. You won't be able to install some scripted mod-installers. Get NCC from <a href=\"http://skyrim.nexusmods.com/downloads/file.php?id=1334\">the MO page on nexus</a></li>"));
- } else {
- VS_FIXEDFILEINFO versionInfo = GetFileVersion(ToWString(QDir::toNativeSeparators(NCCBinary)));
- if ((versionInfo.dwFileVersionMS & 0xFFFF) != 0x02) {
- problemDescription.append(tr("<li>NCC version may be incompatible.</li>"));
- }
- }
-
- if (QSettings("HKEY_LOCAL_MACHINE\\Software\\Microsoft\\NET Framework Setup\\NDP\\v3.5",
- QSettings::NativeFormat).value("Install", 0) != 1) {
- QString dotNetUrl = "http://www.microsoft.com/en-us/download/details.aspx?id=17851";
- problemDescription.append(tr("<li>dotNet is not installed or outdated. This is required to use NCC. "
- "Get it from here: <a href=\"%1\">%1</a></li>").arg(dotNetUrl));
- }
-
- QString logFile;
- if (errorReported(logFile)) {
- problemDescription.append(tr("<li>There was an error reported in your last log. Please see %1</li>").arg(logFile));
- }
-
- bool res = problemDescription.length() != 0;
- if (res) {
- problemDescription.prepend("<ul>").append("</ul>");
-
- ui->actionProblems->setEnabled(true);
- ui->actionProblems->setIconText(tr("Problems"));
- ui->actionProblems->setToolTip(tr("There are potential problems with your setup"));
- } else {
- ui->actionProblems->setToolTip(tr("Everything seems to be in order"));
- }
- return res;
-}
-
-
-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("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);
-}
-
-
-bool MainWindow::saveCurrentLists()
-{
- if (m_DirectoryUpdate) {
- qWarning("not saving lists during directory update");
- return false;
- }
-
- // save plugin list
-
- try {
- m_PluginList.saveTo(m_CurrentProfile->getPluginsFileName(),
- m_CurrentProfile->getLoadOrderFileName(),
- m_CurrentProfile->getLockedOrderFileName(),
- m_CurrentProfile->getDeleterFileName(),
- m_Settings.hideUncheckedPlugins());
-
- if (!m_PluginList.saveLoadOrder(*m_DirectoryStructure)) {
- MessageDialog::showMessage(tr("load order could not be saved"), this);
- } else {
- ui->btnSave->setEnabled(false);
- }
- } catch (const std::exception &e) {
- reportError(tr("failed to save load order: %1").arg(e.what()));
- }
-
- if (m_ArchivesInit) {
- QFile archiveFile(m_CurrentProfile->getArchivesFileName());
- if (archiveFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
- for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) {
- QTreeWidgetItem *item = ui->bsaList->topLevelItem(i);
- if ((item != NULL) && (item->checkState(0) == Qt::Checked)) {
- archiveFile.write(item->text(0).toUtf8().append("\r\n"));
- }
- }
- } else {
- reportError(tr("failed to save archives order, do you have write access "
- "to \"%1\"?").arg(m_CurrentProfile->getArchivesFileName()));
- }
- archiveFile.close();
- } else {
- qWarning("archive list not initialised");
- }
- 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("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);
- }
-}
-
-
-void MainWindow::closeEvent(QCloseEvent* event)
-{
- if (m_DownloadManager.downloadsInProgress() &&
- 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;
- }
-
- setCursor(Qt::WaitCursor);
-
- m_NexusDialog.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)) {
- Profile newProf("Default", false);
- refreshProfiles(false);
- }
-}
-
-
-void MainWindow::setBrowserGeometry(const QByteArray &geometry)
-{
- m_NexusDialog.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(this)) {
- 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()));
- }
-}
-
-
-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()));
- QToolButton *toolBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionTool));
- toolBtn->menu()->addAction(action);
-}
-
-
-bool MainWindow::registerPlugin(QObject *plugin)
-{
- { // generic treatment for all plugins
- IPlugin *pluginObj = qobject_cast<IPlugin*>(plugin);
- if (pluginObj == NULL) {
- return false;
- }
- m_Settings.registerPlugin(pluginObj);
- }
-
- { // diagnosis plugins
- IPluginDiagnose *diagnose = qobject_cast<IPluginDiagnose*>(plugin);
- if (diagnose != NULL) {
- m_DiagnosisPlugins.push_back(diagnose);
- }
- }
-
- { // 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;
- }
- }
- return false;
-}
-
-
-void MainWindow::loadPlugins()
-{
- m_DiagnosisPlugins.clear();
-
- m_Settings.clearPlugins();
-
- foreach (QObject *plugin, QPluginLoader::staticInstances()) {
- registerPlugin(plugin);
- }
-
- QString pluginPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())) + "/" + ToQString(AppConfig::pluginPath());
- qDebug("looking for plugins in %s", pluginPath.toUtf8().constData());
- QDirIterator iter(pluginPath, QDir::Files | QDir::NoDotAndDotDot);
- while (iter.hasNext()) {
- iter.next();
- QString pluginName = iter.filePath();
- if (QLibrary::isLibrary(pluginName)) {
- QPluginLoader pluginLoader(pluginName);
- if (pluginLoader.instance() == NULL) {
- qCritical("failed to load plugin %s: %s",
- pluginName.toUtf8().constData(), pluginLoader.errorString().toUtf8().constData());
- } else {
- if (registerPlugin(pluginLoader.instance())) {
- qDebug("loaded plugin \"%s\"", pluginName.toUtf8().constData());
- } else {
- qWarning("plugin \"%s\" failed to load", pluginName.toUtf8().constData());
- }
- }
- }
- }
-}
-
-IGameInfo &MainWindow::gameInfo() const
-{
- return *m_GameInfo;
-}
-
-
-QString MainWindow::profileName() const
-{
- return m_CurrentProfile->getName();
-}
-
-QString MainWindow::profilePath() const
-{
- return m_CurrentProfile->getPath();
-}
-
-VersionInfo MainWindow::appVersion() const
-{
- return m_Updater.getVersion();
-}
-
-
-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(const QString &name)
-{
- unsigned int index = ModInfo::getIndex(name);
- if (index != UINT_MAX) {
- throw MyException(tr("The mod \"%1\" already exists!").arg(name));
- }
-
- QString targetDirectory = QDir::fromNativeSeparators(m_Settings.getModDirectory()).append("/").append(name.trimmed());
-
- QSettings settingsFile(targetDirectory.mid(0).append("/meta.ini"), QSettings::IniFormat);
-
- settingsFile.setValue("modid", 0);
- settingsFile.setValue("version", 0);
- settingsFile.setValue("newestVersion", 0);
- settingsFile.setValue("category", 0);
- settingsFile.setValue("installationFile", 0);
- return ModInfo::createFrom(QDir(targetDirectory), &m_DirectoryStructure).data();
-}
-
-bool MainWindow::removeMod(IModInterface *mod)
-{
- return ModInfo::removeMod(ModInfo::getIndex(mod->name()));
-}
-
-
-void MainWindow::modDataChanged(IModInterface*)
-{
- refreshModList();
-}
-
-QVariant MainWindow::pluginSetting(const QString &pluginName, const QString &key) const
-{
- return m_Settings.pluginSetting(pluginName, key);
-}
-
-
-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("\"%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,
- m_Settings.directInterface(), "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();
- }
- }
- }
-
- return startBinary(binary, arguments, profileName, m_Settings.logLevel(), currentDirectory, true);
-}
-
-
-void MainWindow::spawnProgram(const QString &fileName, const QString &argumentsArg,
- const QString &profileName, const QDir &currentDirectory)
-{
- QFileInfo binary;
- QString arguments = argumentsArg;
- QString steamAppID;
- try {
- const Executable &exe = m_ExecutablesList.find(fileName);
- steamAppID = exe.m_SteamAppID;
- if (arguments == "") {
- arguments = exe.m_Arguments;
- }
- binary = exe.m_BinaryInfo;
- } catch (const std::runtime_error&) {
- qWarning("\"%s\" not set up as executable", fileName.toUtf8().constData());
- binary = QFileInfo(fileName);
- }
- spawnBinaryDirect(binary, arguments, profileName, currentDirectory, steamAppID);
-}
-
-
-void MainWindow::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir &currentDirectory, bool closeAfterStart, const QString &steamAppID)
-{
- storeSettings();
-
- HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->getName(), currentDirectory, steamAppID);
- if (processHandle != INVALID_HANDLE_VALUE) {
- if (closeAfterStart) {
- close();
- } else {
- this->setEnabled(false);
-
- LockedDialog *dialog = new LockedDialog(this);
- dialog->show();
-
- QCoreApplication::processEvents();
-
- while ((::WaitForSingleObject(processHandle, 1000) == WAIT_TIMEOUT) &&
- !dialog->unlockClicked()) {
- // keep processing events so the app doesn't appear dead
- QCoreApplication::processEvents();
- }
-
- this->setEnabled(true);
- refreshDirectoryStructure();
- refreshDataTree();
- if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) {
- QFile::remove(m_CurrentProfile->getLoadOrderFileName());
- }
- refreshLists();
- dialog->hide();
- }
- }
-}
-
-
-void MainWindow::refreshModList()
-{
- ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure);
- m_CurrentProfile->refreshModStatus();
-
- m_ModList.notifyChange(-1);
-
- refreshDirectoryStructure();
-}
-
-
-void MainWindow::setExecutablesList(const ExecutablesList &executablesList)
-{
- m_ExecutablesList = executablesList;
- refreshExecutablesList();
-}
-
-void MainWindow::setExecutableIndex(int index)
-{
- QComboBox *executableBox = findChild<QComboBox*>("executablesListBox");
-
- if ((index != 0) && (executableBox->count() > index)) {
- executableBox->setCurrentIndex(index);
- } else {
- executableBox->setCurrentIndex(1);
- }
-
- const Executable &selectedExecutable = executableBox->itemData(executableBox->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(linkDesktopFile.exists() ? removeIcon : addIcon);
- ui->linkButton->menu()->actions().at(1)->setIcon(linkMenuFile.exists() ? removeIcon : addIcon);
-}
-
-
-void MainWindow::activateSelectedProfile()
-{
- QString profileName = ui->profileBox->currentText();
- qDebug() << "activate profile " << 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)));
-
- refreshModList();
- refreshSaveList();
-}
-
-
-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();
- }
-
- if (ui->profileBox->currentIndex() == 0) {
- ui->profileBox->setCurrentIndex(previousIndex);
- ProfilesDialog(m_GamePath).exec();
- while (!refreshProfiles()) {
- ProfilesDialog(m_GamePath).exec();
- }
- } else {
- activateSelectedProfile();
- }
- }
-}
-
-
-void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const DirectoryEntry &directoryEntry, bool conflictsOnly)
-{
- {
- std::vector<FileEntry*> files = directoryEntry.getFiles();
- for (auto iter = files.begin(); iter != files.end(); ++iter) {
- FileEntry *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) {
- QStringList columns(ToQString((*current)->getName()));
- columns.append("");
- QTreeWidgetItem *directoryChild = new QTreeWidgetItem(columns);
- updateTo(directoryChild, temp.str(), **current, conflictsOnly);
- if (directoryChild->childCount() != 0) {
- subTree->addChild(directoryChild);
- }
- }
- }
- subTree->sortChildren(0, Qt::AscendingOrder);
-}
-
-
-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;
- }
-}
-
-
-void MainWindow::refreshDirectoryStructure()
-{
- m_DirectoryUpdate = true;
- std::vector<std::tuple<QString, QString, int> > activeModList = m_CurrentProfile->getActiveMods();
- m_DirectoryRefresher.setMods(activeModList);
-
- statusBar()->show();
-// m_RefreshProgress->setVisible(true);
- m_RefreshProgress->setRange(0, 100);
-
- QTimer::singleShot(0, &m_DirectoryRefresher, SLOT(refresh()));
-}
-
-
-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) {
- QIcon icon(":/MO/gui/executable");
- HICON winIcon;
- QVariant temp;
- temp.setValue(*current);
- UINT res = ::ExtractIconExW(ToWString(current->m_BinaryInfo.filePath()).c_str(),
- 0, &winIcon, NULL, 1);
- if (res == 1) {
-// icon = pixmapFromHICON(winIcon);
- icon = QIcon(QPixmap::fromWinHICON(winIcon));
- ::DestroyIcon(winIcon);
- }
- 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::refreshSaveList()
-{
- refreshLists();
- 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)));
- }
-
- 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_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
- m_PluginList.refresh(m_CurrentProfile->getName(), *m_DirectoryStructure,
- m_CurrentProfile->getPluginsFileName(),
- m_CurrentProfile->getLoadOrderFileName(),
- m_CurrentProfile->getLockedOrderFileName());
- //m_PluginList.readFrom(m_CurrentProfile->getPluginsFileName());
-
- findChild<QPushButton*>("btnSave")->setEnabled(false);
-}
-
-
-static bool BySortValue(const std::pair<UINT32, QTreeWidgetItem*> &LHS, const std::pair<UINT32, QTreeWidgetItem*> &RHS)
-{
- return LHS.first < RHS.first;
-}
-
-
-void MainWindow::refreshBSAList()
-{
- m_ArchivesInit = false;
- ui->bsaList->clear();
-#if QT_VERSION >= 0x50000
- 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(',');
- }
-
- 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();
-
- QFile archiveFile(m_CurrentProfile->getArchivesFileName());
- if (archiveFile.open(QIODevice::ReadOnly)) {
- while (!archiveFile.atEnd()) {
- m_ActiveArchives.append(QString::fromUtf8(archiveFile.readLine()));
- }
- archiveFile.close();
-
- for (int i = 0; i < m_ActiveArchives.count(); ++i) {
- m_ActiveArchives[i] = m_ActiveArchives[i].trimmed();
- }
- } else {
- m_ActiveArchives = m_DefaultArchives;
- }
-
- std::vector<std::pair<UINT32, QTreeWidgetItem*> > items;
-
- std::vector<FileEntry*> files = m_DirectoryStructure->getFiles();
- for (auto iter = files.begin(); iter != files.end(); ++iter) {
- FileEntry *current = *iter;
-
- QString filename = ToQString(current->getName().c_str());
- QString extension = filename.right(3).toLower();
-
- if (extension == "bsa") {
- if (filename.compare("update.bsa", Qt::CaseInsensitive) == 0) {
- // hack: ignore update.bsa because it confuses people
- continue;
- }
- int index = m_ActiveArchives.indexOf(filename);
- 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);
- if (m_Settings.forceEnableCoreFiles() && m_DefaultArchives.contains(filename)) {
- newItem->setCheckState(0, Qt::Checked);
- newItem->setDisabled(true);
- } else {
- newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked);
- }
-
- 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) {
- ui->bsaList->addTopLevelItem(iter->second);
- }
-
- checkBSAList();
- m_ArchivesInit = true;
-}
-
-
-void MainWindow::checkBSAList()
-{
- ui->bsaList->blockSignals(true);
-
- bool warning = false;
-
- for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) {
- QTreeWidgetItem *item = ui->bsaList->topLevelItem(i);
- 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/resources/dialog-warning.png"));
- item->setToolTip(0, tr("This bsa is enabled in the ini file so it may be required!"));
- warning = true;
- } else {
- QString espName = filename.mid(0, filename.length() - 3).append("esp").toLower();
- QString esmName = filename.mid(0, filename.length() - 3).append("esm").toLower();
- if (m_PluginList.isEnabled(espName) || m_PluginList.isEnabled(esmName)) {
- item->setIcon(0, QIcon(":/MO/gui/resources/dialog-warning.png"));
- item->setToolTip(0, tr("This archive will still be loaded since there is a plugin of the same name but "
- "its files will not follow installation order!"));
- warning = true;
- }
- }
- }
- }
-
- if (warning) {
- ui->tabWidget->setTabIcon(1, QIcon(":/MO/gui/resources/dialog-warning.png"));
- } else {
- ui->tabWidget->setTabIcon(1, QIcon());
- }
-
- ui->bsaList->blockSignals(false);
-}
-
-
-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::readSettings()
-{
- QSettings settings(ToQString(GameInfo::instance().getIniFilename()), QSettings::IniFormat);
-
- if (settings.contains("window_geometry")) {
- restoreGeometry(settings.value("window_geometry").toByteArray());
- }
-
- if (settings.contains("browser_geometry")) {
- setBrowserGeometry(settings.value("browser_geometry").toByteArray());
- }
-
- bool filtersVisible = settings.value("filters_visible", false).toBool();
- setCategoryListVisible(filtersVisible);
- ui->displayCategoriesBtn->setChecked(filtersVisible);
-
-
- QString language = settings.value("Settings/language", QLocale::system().name()).toString();
- languageChange(language);
- int selectedExecutable = settings.value("selected_executable").toInt();
- setExecutableIndex(selectedExecutable);
-}
-
-
-void MainWindow::storeSettings()
-{
- if (m_CurrentProfile == NULL) {
- return;
- }
- m_CurrentProfile->writeModlist();
- m_CurrentProfile->createTweakedIniFile();
- saveCurrentLists();
- m_Settings.setupLoadMechanism();
- QSettings settings(ToQString(GameInfo::instance().getIniFilename()), 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("compact_downloads", ui->compactBox->isChecked());
- settings.setValue("ask_for_nexuspw", m_AskForNexusPW);
-
- settings.setValue("window_geometry", this->saveGeometry());
- settings.setValue("browser_geometry", m_NexusDialog.saveGeometry());
-
- settings.setValue("filters_visible", ui->displayCategoriesBtn->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) {
- 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.endArray();
-
- QComboBox *executableBox = findChild<QComboBox*>("executablesListBox");
- settings.setValue("selected_executable", executableBox->currentIndex());
-}
-
-
-void MainWindow::on_btnRefreshData_clicked()
-{
- if (!m_DirectoryUpdate) {
- refreshDirectoryStructure();
- refreshDataTree();
- } else {
- qDebug("directory update");
- }
-}
-
-void MainWindow::on_tabWidget_currentChanged(int index)
-{
- saveCurrentLists();
- 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();
- }
-}
-
-
-static QString guessModName(const QString &fileName)
-{
- return QFileInfo(fileName).baseName();
-}
-
-
-void MainWindow::installMod(const QString &fileName)
-{
- bool hasIniTweaks = false;
- QString modName;
- m_CurrentProfile->writeModlistNow();
- if (m_InstallationManager.install(fileName, m_CurrentProfile->getPluginsFileName(), m_Settings.getModDirectory(), m_Settings.preferIntegratedInstallers(),
- m_Settings.enableQuickInstaller(), modName, hasIniTweaks)) {
- MessageDialog::showMessage(tr("Installation successful"), this);
- refreshModList();
-
- QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, 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);
- }
- testExtractBSA(modIndex);
- } 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);
- }
-}
-
-
-void MainWindow::installMod()
-{
- try {
- QStringList extensions = m_InstallationManager.getSupportedExtensions();
- for (auto iter = extensions.begin(); iter != extensions.end(); ++iter) {
- *iter = "*." + *iter;
- }
-
- QString fileName = QFileDialog::getOpenFileName(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());
- }
-}
-
-
-void MainWindow::on_btnSave_clicked()
-{
- saveCurrentLists();
-}
-
-
-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;
- }
-}
-
-
-void MainWindow::setCompactDownloads(bool compact)
-{
- ui->compactBox->setChecked(compact);
-}
-
-
-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::esplist_changed()
-{
- findChild<QPushButton*>("btnSave")->setEnabled(true);
-}
-
-
-void MainWindow::refresher_progress(int percent)
-{
- m_RefreshProgress->setValue(percent);
-}
-
-
-void MainWindow::directory_refreshed()
-{
- DirectoryEntry *newStructure = m_DirectoryRefresher.getDirectoryStructure();
- if (newStructure != NULL) {
- delete m_DirectoryStructure;
- m_DirectoryStructure = newStructure;
- } else {
- // TODO: don't know why this happens, this slot seems to get called twice with only one emit
- return;
- }
- m_DirectoryUpdate = false;
- refreshLists();
-// m_RefreshProgress->setVisible(false);
- statusBar()->hide();
-}
-
-
-void MainWindow::externalMessage(const QString &message)
-{
- if (message.left(6).toLower() == "nxm://") {
- MessageDialog::showMessage(tr("Download started"), this);
- downloadRequestedNXM(message);
- }
-}
-
-
-void MainWindow::modStatusChanged(unsigned int index)
-{
- try {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
- if (m_CurrentProfile->modEnabled(index)) {
- DirectoryRefresher::addModToStructure(m_DirectoryStructure, modInfo->name(), m_CurrentProfile->getModPriority(index), modInfo->absolutePath());
-/* m_DirectoryStructure->addFromOrigin(ToWString(modInfo->name()),
- ToWString(QDir::toNativeSeparators(modInfo->absolutePath())),
- m_CurrentProfile->getModPriority(index));*/
- DirectoryRefresher::cleanStructure(m_DirectoryStructure);
- } else {
- if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) {
- FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name()));
- origin.enable(false);
- }
- }
- 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);
- m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())).setPriority(priority);
- }
- }
- 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::on_profileRefreshBtn_clicked()
-{
- m_CurrentProfile->writeModlist();
-
-// m_ModList.updateModCollection();
- 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) {
- break;
- }
- if (line.at(0) == '#') {
- outBuffer.write(line);
- continue;
- }
- QString modName = QString::fromUtf8(line.constData()).trimmed();
- if (modName.isEmpty()) {
- break;
- }
-
- bool disabled = false;
- if (modName.at(0) == '-') {
- disabled = true;
- modName = modName.mid(1);
- } else if (modName.at(0) == '+') {
- modName = modName.mid(1);
- }
-
- if (modName == oldName) {
- modName = newName;
- }
-
- if (disabled) {
- outBuffer.write("-");
- } else {
- outBuffer.write("+");
- }
- 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::addFilterItem(const QString &name, int categoryID)
-{
- QListWidgetItem *item = new QListWidgetItem(name);
- item->setData(Qt::UserRole, categoryID);
- ui->categoriesList->addItem(item);
-}
-
-
-void MainWindow::refreshFilters()
-{
- ui->modList->setCurrentIndex(QModelIndex());
-
- // save previous filter text so we can restore it later, in case the filter still exists then
- QListWidgetItem *currentItem = ui->categoriesList->currentItem();
- QString previousFilter = currentItem != NULL ? currentItem->text() : tr("<All>");
-
- ui->categoriesList->clear();
- addFilterItem(tr("<All>"), CategoryFactory::CATEGORY_NONE);
- addFilterItem(tr("<Checked>"), CategoryFactory::CATEGORY_SPECIAL_CHECKED);
- addFilterItem(tr("<Unchecked>"), CategoryFactory::CATEGORY_SPECIAL_UNCHECKED);
- addFilterItem(tr("<Update>"), CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE);
- addFilterItem(tr("<No category>"), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY);
- addFilterItem(tr("<Conflicted>"), CategoryFactory::CATEGORY_SPECIAL_CONFLICT);
-
- for (unsigned int i = 1; i < m_CategoryFactory.numCategories(); ++i) {
- bool categoryUsed = false;
- int categoryId = m_CategoryFactory.getCategoryID(i);
- for (unsigned int modIdx = 0; modIdx < ModInfo::getNumMods(); ++modIdx) {
- ModInfo::Ptr modInfo = ModInfo::getByIndex(modIdx);
- if (modInfo->categorySet(categoryId)) {
- categoryUsed = true;
- }
- }
-
- if (categoryUsed) {
- addFilterItem(m_CategoryFactory.getCategoryName(i), categoryId);
- }
- }
-
- for (int i = 0; i < ui->categoriesList->count(); ++i) {
- if (ui->categoriesList->item(i)->text() == previousFilter) {
- ui->categoriesList->setCurrentRow(i);
- break;
- }
- }
-}
-
-
-void MainWindow::renameMod_clicked()
-{
- try {
- QModelIndex treeIdx = m_ModListSortProxy->mapFromSource(m_ModList.index(m_ContextRow, 0));
- ui->modList->setCurrentIndex(treeIdx);
- ui->modList->edit(treeIdx);
- } 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) && !removeDir(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::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_ModListSortProxy->mapToSource(idx).row())->name();
- 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::reinstallMod_clicked()
-{
- ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
- QString installationFile = modInfo->getInstallationFile();
- if (installationFile.length() != 0) {
- // there was a bug where mods installed through NCC had the absolute download path stored
- if (QFileInfo(installationFile).isAbsolute()) {
- installationFile = QFileInfo(installationFile).fileName();
- }
- QString 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::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));
- m_NexusDialog.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::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));
- m_NexusDialog.login(username, password);
- } else {
- MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this);
- }
- }
-}
-
-
-void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab)
-{
- std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) {
- OverwriteInfoDialog dialog(modInfo, this);
- dialog.exec();
- } else {
- ModInfoDialog dialog(modInfo, m_DirectoryStructure, 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)));
- connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int)));
- connect(&dialog, SIGNAL(endorseMod(ModInfo::Ptr)), this, SLOT(endorseMod(ModInfo::Ptr)));
-
- dialog.openTab(tab);
- dialog.exec();
-
- modInfo->saveMeta();
- emit modInfoDisplayed();
- }
-
- 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_DirectoryStructure->addFromOrigin(ToWString(modInfo->name()),
- ToWString(QDir::toNativeSeparators(modInfo->absolutePath())),
- m_CurrentProfile->getModPriority(index));*/
- DirectoryRefresher::addModToStructure(m_DirectoryStructure,
- modInfo->name(), m_CurrentProfile->getModPriority(index),
- modInfo->absolutePath());
- DirectoryRefresher::cleanStructure(m_DirectoryStructure);
- refreshLists();
- }
- }
-}
-
-
-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::testExtractBSA(int modIndex)
-{
- ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
- QDir dir(modInfo->absolutePath());
-
- QFileInfoList archives = dir.entryInfoList(QStringList("*.bsa"));
- if (archives.length() != 0 &&
- (QuestionBoxMemory::query(this, m_Settings.directInterface(), "unpackBSA", tr("Extract BSA"),
- tr("This mod contains at least one BSA. Do you want to unpack it?\n"
- "(This removes the BSA after completion. If you don't know about BSAs, just select no)"),
- QDialogButtonBox::Yes | QDialogButtonBox::No, QDialogButtonBox::No) == QMessageBox::Yes)) {
-
-
- foreach (QFileInfo archiveInfo, archives) {
- BSA::Archive archive;
-
- BSA::EErrorCode result = archive.read(archiveInfo.absoluteFilePath().toLocal8Bit().constData());
- if ((result != BSA::ERROR_NONE) && (result != BSA::ERROR_INVALIDHASHES)) {
- reportError(tr("failed to read %1: %2").arg(archiveInfo.fileName()).arg(result));
- return;
- }
-
- QProgressDialog progress(this);
- progress.setMaximum(100);
- progress.setValue(0);
- progress.show();
-
- archive.extractAll(modInfo->absolutePath().toUtf8().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."));
- }
-
- archive.close();
-
- if (!QFile::remove(archiveInfo.absoluteFilePath())) {
- qCritical("failed to remove archive %s", archiveInfo.absoluteFilePath().toUtf8().constData());
- } else {
- m_DirectoryStructure->removeFile(ToWString(archiveInfo.fileName()));
- }
- }
-
- refreshBSAList();
- }
-}
-
-
-void MainWindow::visitOnNexus_clicked()
-{
- int modID = m_ModList.data(m_ModList.index(m_ContextRow, 0), Qt::UserRole).toInt();
- if (modID > 0) {
- nexusLinkActivated(QString("%1/downloads/file.php?id=%2").arg(ToQString(GameInfo::instance().getNexusPage())).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::setPriorityMax()
-{
- int newPriority = m_CurrentProfile->numMods() - 1;
- m_CurrentProfile->setModPriority(m_ContextRow, newPriority);
-}
-
-
-void MainWindow::setPriorityManually()
-{
-// m_CurrentProfile->setModPriority(m_ContextRow, m_CurrentProfile->numMods() - 1);
-
- int current = m_CurrentProfile->getModPriority(m_ContextRow);
- int newPriority = QInputDialog::getInt(this, tr("Priority"), tr("Choose Priority"), current, 0, m_ModList.rowCount() - 1);
- m_ModList.changeModPriority(m_ContextRow, newPriority);
-// m_CurrentProfile->setModPriority(m_ContextRow, newPriority);
-}
-
-
-void MainWindow::setPriorityMin()
-{
- int newPriority = 0;
- m_CurrentProfile->setModPriority(m_ContextRow, newPriority);
-}
-
-
-void MainWindow::cancelModListEditor()
-{
- ui->modList->setEnabled(false);
- ui->modList->setEnabled(true);
-}
-
-
-void MainWindow::on_modList_doubleClicked(const QModelIndex &index)
-{
- try {
- displayModInformation(m_ModListSortProxy->mapToSource(index).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::addCategories(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 (addCategories(targetMenu, m_CategoryFactory.getCategoryID(i)) || enabled) {
- targetMenu->setIcon(QIcon(":/MO/gui/resources/check.png"));
- }
- }
- }
- }
- return childEnabled;
-}
-
-
-void MainWindow::saveCategoriesFromMenu(QMenu *menu, int modRow)
-{
- ModInfo::Ptr modInfo = ModInfo::getByIndex(modRow);
- foreach (QAction* action, menu->actions()) {
- if (action->menu() != NULL) {
- saveCategoriesFromMenu(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());
-// m_ModList.setModCategory(modRow, widgetAction->data().toInt(), checkbox->isChecked());
- }
- }
- }
-}
-
-
-void MainWindow::saveCategories()
-{
- QMenu *menu = qobject_cast<QMenu*>(sender());
- if (menu == NULL) {
- qCritical("not a menu?");
- return;
- }
-// m_ModList.resetCategories(m_ContextRow);
- saveCategoriesFromMenu(menu, m_ContextRow);
-
- refreshFilters();
-}
-
-
-void MainWindow::savePrimaryCategory()
-{
- QMenu *menu = qobject_cast<QMenu*>(sender());
- if (menu == NULL) {
- qCritical("not a menu?");
- return;
- }
-
- ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
- foreach (QAction* action, menu->actions()) {
- QWidgetAction *widgetAction = qobject_cast<QWidgetAction*>(action);
- if (widgetAction != NULL) {
- QRadioButton *btn = qobject_cast<QRadioButton*>(widgetAction->defaultWidget());
- if (btn->isChecked()) {
- modInfo->setPrimaryCategory(widgetAction->data().toInt());
- break;
- }
- }
- }
-}
-
-
-void MainWindow::checkModsForUpdates()
-{
- if (NexusInterface::instance()->getAccessManager()->loggedIn()) {
- m_ModsToUpdate = ModInfo::checkAllForUpdate(this);
- statusBar()->show();
- 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));
- m_NexusDialog.login(username, password);
- } else { // otherwise there will be no endorsement info
- m_ModsToUpdate = ModInfo::checkAllForUpdate(this);
- }
- }
-}
-
-
-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::on_modList_customContextMenuRequested(const QPoint &pos)
-{
- try {
- QTreeView *modList = findChild<QTreeView*>("modList");
-
- m_ContextRow = m_ModListSortProxy->mapToSource(modList->indexAt(pos)).row();
-
- QMenu menu;
- 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(on_profileRefreshBtn_clicked()));
-
- if (m_ContextRow != -1) {
- menu.addSeparator();
- 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()) {
- menu.addAction(tr("Sync to Mods..."), this, SLOT(syncOverwrite()));
- } 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 {
- QMenu *addCategoryMenu = menu.addMenu(tr("Set Category"));
- addCategories(addCategoryMenu, 0);
- connect(addCategoryMenu, SIGNAL(aboutToHide()), this, SLOT(saveCategories()));
-
- QMenu *primaryCategoryMenu = menu.addMenu(tr("Primary Category"));
- connect(primaryCategoryMenu, SIGNAL(aboutToShow()), this, SLOT(addPrimaryCategoryCandidates()));
- connect(primaryCategoryMenu, SIGNAL(aboutToHide()), this, SLOT(savePrimaryCategory()));
-
- 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()));
- } break;
- default: {
- QAction *action = new QAction(tr("Endorsement state unknown"), &menu);
- action->setEnabled(false);
- menu.addAction(action);
- } break;
- }
-
- menu.addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked()));
- menu.addAction(tr("Open in explorer"), this, SLOT(openExplorer_clicked()));
- }
-
- 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_currentItemChanged(QListWidgetItem *current, QListWidgetItem *)
-{
- if (current != NULL) {
- m_ModListSortProxy->setCategoryFilter(current->data(Qt::UserRole).toInt());
- ui->currentCategoryLabel->setText(QString("(%1)").arg(current->text()));
- }
-}
-
-
-void MainWindow::fixMods_clicked()
-{
- QListWidgetItem *selectedItem = ui->savegameList->item(m_SelectedSaveGame);
- 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 = qvariant_cast<SaveGameGamebryo*>(selectedItem->data(Qt::UserRole));
-
- // 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");
-
- {
- 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>");
- }
- }
- }
-
- 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());
- }
- }
- }
-
- 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>") {
- 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)
-{
- QListWidgetItem *selectedItem = ui->savegameList->itemAt(pos);
- if (selectedItem == NULL) {
- return;
- }
-
- m_SelectedSaveGame = ui->savegameList->row(selectedItem);
-
- QMenu menu;
- menu.addAction(tr("Fix Mods..."), this, SLOT(fixMods_clicked()));
-
- menu.exec(ui->savegameList->mapToGlobal(pos));
-}
-
-void MainWindow::linkDesktop()
-{
- QComboBox* executablesList = findChild<QComboBox*>("executablesListBox");
-// QPushButton *linkButton = findChild<QPushButton*>("linkDesktopButton");
-
- 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(m_ExeName);
- // create link
- std::wstring targetFile = ToWString(exeInfo.absoluteFilePath());
- std::wstring parameter = ToWString(QString("\"") + selectedExecutable.m_BinaryInfo.absoluteFilePath() + "\" " + selectedExecutable.m_Arguments);
- std::wstring description = ToWString(selectedExecutable.m_BinaryInfo.fileName());
- std::wstring currentDirectory = ToWString(selectedExecutable.m_BinaryInfo.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(m_ExeName);
- // create link
- std::wstring targetFile = ToWString(exeInfo.absoluteFilePath());
- std::wstring parameter = ToWString(QString("\"") + selectedExecutable.m_BinaryInfo.absoluteFilePath() + "\" " + selectedExecutable.m_Arguments);
- std::wstring description = ToWString(selectedExecutable.m_BinaryInfo.fileName());
- std::wstring currentDirectory = ToWString(selectedExecutable.m_BinaryInfo.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::on_actionSettings_triggered()
-{
- QString oldModDirectory(m_Settings.getModDirectory());
- QString oldCacheDirectory(m_Settings.getCacheDirectory());
- m_Settings.query(this);
- 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());
- }
- }
-
- if (m_Settings.getModDirectory() != oldModDirectory) {
- refreshModList();
- refreshLists();
- }
-
- if (m_Settings.getCacheDirectory() != oldCacheDirectory) {
- NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory());
- }
-
- NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion());
-}
-
-void MainWindow::on_actionNexus_triggered()
-{
- QString username, password;
- m_NexusDialog.openUrl(ToQString(GameInfo::instance().getNexusPage()));
-
- if (m_Settings.getNexusLogin(username, password)) {
- m_NexusDialog.login(username, password);
- } else {
- m_NexusDialog.loadNexus();
- }
- m_NexusDialog.show();
- m_NexusDialog.activateWindow();
-
- QTabWidget *tabWidget = findChild<QTabWidget*>("tabWidget");
- tabWidget->setCurrentIndex(4);
-}
-
-
-void MainWindow::nexusLinkActivated(const QString &link)
-{
- if (m_Settings.preferExternalBrowser()) {
- ::ShellExecuteW(NULL, L"open", ToWString(link).c_str(), NULL, NULL, SW_SHOWNORMAL);
- } else {
- QString username, password;
- m_NexusDialog.openUrl(link);
- if (m_Settings.getNexusLogin(username, password)) {
- m_NexusDialog.login(username, password);
- m_LoginAttempted = true;
- } else {
- m_NexusDialog.loadNexus();
- }
- m_NexusDialog.show();
-
- QTabWidget *tabWidget = findChild<QTabWidget*>("tabWidget");
- tabWidget->setCurrentIndex(4);
- }
-}
-
-
-void MainWindow::downloadRequestedNXM(const QString &url)
-{
- QString username, password;
-
- if (!m_LoginAttempted && !NexusInterface::instance()->getAccessManager()->loggedIn() &&
- (m_Settings.getNexusLogin(username, password) ||
- (m_AskForNexusPW && queryLogin(username, password)))) {
- m_PendingDownloads.append(url);
- NexusInterface::instance()->getAccessManager()->login(username, password);
- m_LoginAttempted = true;
- } else {
- m_DownloadManager.addNXMDownload(url);
- }
-}
-
-
-void MainWindow::downloadRequested(QNetworkReply *reply, int modID, const QString &fileName)
-{
- try {
- if (m_DownloadManager.addDownload(reply, fileName, 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::languageChange(const QString &newLanguage)
-{
- if (m_Translator != NULL) {
- QCoreApplication::removeTranslator(m_Translator);
- delete m_Translator;
- m_Translator = NULL;
- }
- if (m_TranslatorQt != NULL) {
- QCoreApplication::removeTranslator(m_TranslatorQt);
- delete m_TranslatorQt;
- m_TranslatorQt = NULL;
- }
-
- if (newLanguage != "en_US") {
- // add our own translations
- m_Translator = new QTranslator(this);
- QString locFile = ToQString(AppConfig::translationPrefix()) + "_" + newLanguage;
- if (!m_Translator->load(locFile, QCoreApplication::applicationDirPath() + "/translations")) {
- qDebug("localization %s not found", locFile.toUtf8().constData());
- }
- QCoreApplication::installTranslator(m_Translator);
-
- // also add the translations for qt default strings
- m_TranslatorQt = new QTranslator(this);
- locFile = QString("qt_") + newLanguage;
- if (!m_TranslatorQt->load(locFile, QCoreApplication::applicationDirPath() + "/translations")) {
- qDebug("localization %s not found", locFile.toUtf8().constData());
- }
- QCoreApplication::installTranslator(m_TranslatorQt);
- }
- 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);
- QString modName;
- // see if there already are mods with the specified mod id
- if (modID != 0) {
- ModInfo::Ptr modInfo = ModInfo::getByModID(modID, true);
- if (!modInfo.isNull()) {
- std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
- if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end()) {
- modName = modInfo->name();
- modInfo->saveMeta();
- }
- }
- // TODO there may be multiple mods with the same id!
-// modName = m_ModList.getModByModID(modID);
- }
-
- m_CurrentProfile->writeModlistNow();
-
- bool hasIniTweaks = false;
- if (m_InstallationManager.install(fileName, m_CurrentProfile->getPluginsFileName(), m_Settings.getModDirectory(), m_Settings.preferIntegratedInstallers(),
- m_Settings.enableQuickInstaller(), modName, hasIniTweaks)) {
- MessageDialog::showMessage(tr("Installation successful"), this);
-
- refreshModList();
-
- QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, 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);
- }
- testExtractBSA(modIndex);
- } 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*> files = directoryEntry.getFiles();
- for (auto iter = files.begin(); iter != files.end(); ++iter) {
- FileEntry *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());
- 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::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()));
- // 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()
-{
- QString username, password;
-
- if (!m_LoginAttempted && !NexusInterface::instance()->getAccessManager()->loggedIn() &&
- (m_Settings.getNexusLogin(username, password) ||
- (m_AskForNexusPW && queryLogin(username, password)))) {
- NexusInterface::instance()->getAccessManager()->login(username, password);
- m_LoginAttempted = true;
- } 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());
- }
-}
-
-
-void MainWindow::updateDownloadListDelegate()
-{
- if (ui->compactBox->isChecked()) {
- ui->downloadView->setItemDelegate(new DownloadListWidgetCompactDelegate(&m_DownloadManager, ui->downloadView));
- } else {
- ui->downloadView->setItemDelegate(new DownloadListWidgetDelegate(&m_DownloadManager, 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)));
-
- ui->downloadView->setModel(sortProxy);
- ui->downloadView->sortByColumn(1, Qt::AscendingOrder);
- ui->downloadView->header()->resizeSections(QHeaderView::Fixed);
-// ui->downloadView->setFirstColumnSpanned(0, QModelIndex(), true);
-
- 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(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)), &m_DownloadManager, SLOT(resumeDownload(int)));
-}
-
-
-void MainWindow::on_compactBox_toggled(bool)
-{
- updateDownloadListDelegate();
-}
-
-
-void MainWindow::modDetailsUpdated(bool)
-{
- --m_ModsToUpdate;
- if (m_ModsToUpdate == 0) {
- statusBar()->hide();
- m_ModListSortProxy->setCategoryFilter(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE);
- for (int i = 0; i < ui->categoriesList->count(); ++i) {
- if (ui->categoriesList->item(i)->data(Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) {
- ui->categoriesList->setCurrentRow(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 {
- ModInfo::Ptr info = ModInfo::getByModID(result["id"].toInt(), true);
- if (!info.isNull()) {
- info->setNewestVersion(VersionInfo(result["version"].toString()));
- info->setNexusDescription(result["description"].toString());
- if (NexusInterface::instance()->getAccessManager()->loggedIn()) {
- // don't use endorsement info if we're not logged in
- info->setIsEndorsed(result["voted_by_user"].toBool());
- }
- }
- }
- }
-
- if (m_ModsToUpdate <= 0) {
- statusBar()->hide();
- m_ModListSortProxy->setCategoryFilter(CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE);
- for (int i = 0; i < ui->categoriesList->count(); ++i) {
- if (ui->categoriesList->item(i)->data(Qt::UserRole) == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) {
- ui->categoriesList->setCurrentRow(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::nxmRequestFailed(int modID, 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();
-}
-
-
-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();
-}
-
-
-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 = ui->bsaList->topLevelItem(m_ContextRow);
-
- 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());
- 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).toUtf8().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 = 0; 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 = 0;
- 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_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::on_bsaList_itemChanged(QTreeWidgetItem*, int)
-{
- checkBSAList();
-}
-
-void MainWindow::on_actionProblems_triggered()
-{
- QString problemDescription;
- checkForProblems(problemDescription);
- QMessageBox::information(this, tr("Problems"), problemDescription);
-}
-
-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::on_categoriesList_customContextMenuRequested(const QPoint &pos)
-{
- QMenu menu;
- menu.addAction(tr("Edit Categories..."), this, SLOT(editCategories()));
-
- menu.exec(ui->categoriesList->mapToGlobal(pos));
-}
-
-
-void MainWindow::lockESPIndex()
-{
- m_PluginList.lockESPIndex(m_ContextRow, true);
-}
-
-void MainWindow::unlockESPIndex()
-{
- m_PluginList.lockESPIndex(m_ContextRow, false);
-}
-
-
-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()));
-
- if ((m_ContextRow != -1) && m_PluginList.isEnabled(m_ContextRow)) {
- if (m_PluginList.isESPLocked(m_ContextRow)) {
- menu.addAction(tr("Unlock index"), this, SLOT(unlockESPIndex()));
- } else {
- menu.addAction(tr("Lock index"), 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"));
- }
-}
+#include "mainwindow.h"
+#include "ui_mainwindow.h"
+#include <archive.h>
+#include "spawn.h"
+#include "report.h"
+#include "modlist.h"
+#include "profile.h"
+#include "pluginlist.h"
+#include "installdialog.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 "modlistsortproxy.h"
+#include "motddialog.h"
+#include "filedialogmemory.h"
+#include "questionboxmemory.h"
+#include "tutorialmanager.h"
+#include "icondelegate.h"
+#include "credentialsdialog.h"
+#include "selectiondialog.h"
+#include "csvbuilder.h"
+#include "gameinfoimpl.h"
+#include "savetextasdialog.h"
+#include <gameinfo.h>
+#include <appconfig.h>
+#include <utility.h>
+#include <map>
+#include <ctime>
+#include <QTime>
+#include <util.h>
+#include <QInputDialog>
+#include <QSettings>
+#include <QWhatsThis>
+#include <wchar.h>
+#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 <boost/bind.hpp>
+#include <boost/foreach.hpp>
+#include <Psapi.h>
+#include <shlobj.h>
+#include <TlHelp32.h>
+
+
+
+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(NexusInterface::instance()),
+ m_OldExecutableIndex(-1), m_GamePath(ToQString(GameInfo::instance().getGameDirectory())),
+ m_NexusDialog(NexusInterface::instance()->getAccessManager(), NULL),
+ m_DownloadManager(NexusInterface::instance(), this),
+ m_InstallationManager(this), m_Translator(NULL), m_TranslatorQt(NULL),
+ m_Updater(NexusInterface::instance(), this), m_CategoryFactory(CategoryFactory::instance()),
+ m_CurrentProfile(NULL), m_AskForNexusPW(false), m_LoginAttempted(false),
+ m_ArchivesInit(false), m_ContextItem(NULL), m_CurrentSaveView(NULL),
+ m_GameInfo(new GameInfoImpl())
+{
+ ui->setupUi(this);
+
+ this->setWindowTitle(ToQString(GameInfo::instance().getGameName()).append(" Mod Organizer v").append(m_Updater.getVersion().canonicalString()));
+
+ 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);
+
+ updateProblemsButton();
+
+ updateToolBar();
+ ui->toolBar->blockSignals(true);
+ createHelpWidget();
+
+ ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure);
+
+ // set up mod list
+ m_ModListSortProxy = new ModListSortProxy(m_CurrentProfile, this);
+ m_ModListSortProxy->setSourceModel(&m_ModList);
+ ui->modList->setModel(m_ModListSortProxy);
+
+ ui->modList->sortByColumn(ModList::COL_PRIORITY, Qt::AscendingOrder);
+ ui->modList->setItemDelegateForColumn(ModList::COL_FLAGS, new IconDelegate(m_ModListSortProxy));
+ ui->modList->header()->installEventFilter(&m_ModList);
+ ui->modList->header()->restoreState(initSettings.value("mod_list_state").toByteArray());
+ ui->modList->installEventFilter(&m_ModList);
+ // restoreState also seems to restores the resize mode from previous session,
+ // I don't really like that
+#if QT_VERSION >= 0x50000
+ 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
+
+ // 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->header()->restoreState(initSettings.value("plugin_list_state").toByteArray());
+ ui->espList->installEventFilter(&m_PluginList);
+#if QT_VERSION >= 0x50000
+ 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
+
+ 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);
+
+ m_DownloadManager.setOutputDirectory(m_Settings.getDownloadDirectory());
+
+ NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory());
+ NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion());
+
+ updateDownloadListDelegate();
+ connect(&m_ModList, SIGNAL(requestColumnSelect(QPoint)), this, SLOT(displayColumnSelection(QPoint)));
+
+ connect(&m_DownloadManager, SIGNAL(showMessage(QString)), this, SLOT(showMessage(QString)));
+
+ connect(NexusInterface::instance(), SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString)));
+ connect(&m_NexusDialog, SIGNAL(requestDownload(QNetworkReply*, int, QString)), this, SLOT(downloadRequested(QNetworkReply*, int, QString)));
+
+ ui->savegameList->installEventFilter(this);
+ ui->savegameList->setMouseTracking(true);
+ connect(ui->savegameList, SIGNAL(itemEntered(QListWidgetItem*)),
+ this, SLOT(saveSelectionChanged(QListWidgetItem*)));
+
+ connect(&m_PluginList, SIGNAL(esplist_changed()), this, SLOT(esplist_changed()));
+ 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(ui->modFilterEdit, SIGNAL(textChanged(QString)), m_ModListSortProxy, SLOT(updateFilter(QString)));
+ connect(&m_ModList, SIGNAL(modlist_changed(int)), m_ModListSortProxy, SLOT(invalidate()));
+ connect(&m_ModList, SIGNAL(removeSelectedMods()), this, SLOT(removeMod_clicked()));
+
+ 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_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(NexusInterface::instance()->getAccessManager(), SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool)));
+ connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString)));
+
+ connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString)));
+
+ m_DirectoryRefresher.moveToThread(&m_RefresherThread);
+ m_RefresherThread.start();
+
+ setCompactDownloads(initSettings.value("compact_downloads", false).toBool());
+ m_AskForNexusPW = initSettings.value("ask_for_nexuspw", true).toBool();
+ setCategoryListVisible(initSettings.value("categorylist_visible", true).toBool());
+ FileDialogMemory::restore(initSettings);
+
+ fixCategories();
+ m_Updater.testForUpdate();
+ m_StartTime = QTime::currentTime();
+
+ m_Tutorial.expose("modList", &m_ModList);
+ m_Tutorial.expose("espList", &m_PluginList);
+ loadPlugins();
+}
+
+
+MainWindow::~MainWindow()
+{
+ m_RefresherThread.exit();
+ m_RefresherThread.wait();
+ m_NexusDialog.close();
+ delete ui;
+ delete m_GameInfo;
+}
+
+
+void MainWindow::resizeEvent(QResizeEvent *event)
+{
+ m_Tutorial.resize(event->size());
+ QMainWindow::resizeEvent(event);
+}
+
+
+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->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);
+
+ 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::updateProblemsButton()
+{
+ QString problemDescription;
+ if (checkForProblems(problemDescription)) {
+ ui->actionProblems->setEnabled(true);
+ ui->actionProblems->setIconText(tr("Problems"));
+ ui->actionProblems->setToolTip(tr("There are potential problems with your setup"));
+ } else {
+ ui->actionProblems->setIconText(tr("No Problems"));
+ ui->actionProblems->setToolTip(tr("Everything seems to be in order"));
+ }
+}
+
+
+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;
+}
+
+
+bool MainWindow::checkForProblems(QString &problemDescription)
+{
+ problemDescription = "";
+
+ foreach (IPluginDiagnose *diagnose, m_DiagnosisPlugins) {
+ std::vector<unsigned int> activeProblems = diagnose->activeProblems();
+ foreach (unsigned int key, activeProblems) {
+ problemDescription.append(tr("<li>%1</li>").arg(diagnose->shortDescription(key)));
+ }
+ }
+
+ QString NCCBinary = QCoreApplication::applicationDirPath().mid(0).append("/NCC/NexusClientCLI.exe");
+ if (!QFile::exists(NCCBinary)) {
+ problemDescription.append(tr("<li>NCC not installed. You won't be able to install some scripted mod-installers. Get NCC from <a href=\"http://skyrim.nexusmods.com/downloads/file.php?id=1334\">the MO page on nexus</a></li>"));
+ } else {
+ VS_FIXEDFILEINFO versionInfo = GetFileVersion(ToWString(QDir::toNativeSeparators(NCCBinary)));
+ if ((versionInfo.dwFileVersionMS & 0xFFFF) != 0x02) {
+ problemDescription.append(tr("<li>NCC version may be incompatible.</li>"));
+ }
+ }
+
+ if (QSettings("HKEY_LOCAL_MACHINE\\Software\\Microsoft\\NET Framework Setup\\NDP\\v3.5",
+ QSettings::NativeFormat).value("Install", 0) != 1) {
+ QString dotNetUrl = "http://www.microsoft.com/en-us/download/details.aspx?id=17851";
+ problemDescription.append(tr("<li>dotNet is not installed or outdated. This is required to use NCC. "
+ "Get it from here: <a href=\"%1\">%1</a></li>").arg(dotNetUrl));
+ }
+
+ QString logFile;
+ if (errorReported(logFile)) {
+ problemDescription.append(tr("<li>There was an error reported in your last log. Please see %1</li>").arg(logFile));
+ }
+
+ bool res = problemDescription.length() != 0;
+ if (res) {
+ problemDescription.prepend("<ul>").append("</ul>");
+
+ ui->actionProblems->setEnabled(true);
+ ui->actionProblems->setIconText(tr("Problems"));
+ ui->actionProblems->setToolTip(tr("There are potential problems with your setup"));
+ } else {
+ ui->actionProblems->setToolTip(tr("Everything seems to be in order"));
+ }
+ return res;
+}
+
+
+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("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);
+}
+
+
+bool MainWindow::saveCurrentLists()
+{
+ if (m_DirectoryUpdate) {
+ qWarning("not saving lists during directory update");
+ return false;
+ }
+
+ // save plugin list
+
+ try {
+ m_PluginList.saveTo(m_CurrentProfile->getPluginsFileName(),
+ m_CurrentProfile->getLoadOrderFileName(),
+ m_CurrentProfile->getLockedOrderFileName(),
+ m_CurrentProfile->getDeleterFileName(),
+ m_Settings.hideUncheckedPlugins());
+
+ if (!m_PluginList.saveLoadOrder(*m_DirectoryStructure)) {
+ MessageDialog::showMessage(tr("load order could not be saved"), this);
+ } else {
+ ui->btnSave->setEnabled(false);
+ }
+ } catch (const std::exception &e) {
+ reportError(tr("failed to save load order: %1").arg(e.what()));
+ }
+
+ if (m_ArchivesInit) {
+ QFile archiveFile(m_CurrentProfile->getArchivesFileName());
+ if (archiveFile.open(QIODevice::WriteOnly | QIODevice::Truncate)) {
+ for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) {
+ QTreeWidgetItem *item = ui->bsaList->topLevelItem(i);
+ if ((item != NULL) && (item->checkState(0) == Qt::Checked)) {
+ archiveFile.write(item->text(0).toUtf8().append("\r\n"));
+ }
+ }
+ } else {
+ reportError(tr("failed to save archives order, do you have write access "
+ "to \"%1\"?").arg(m_CurrentProfile->getArchivesFileName()));
+ }
+ archiveFile.close();
+ } else {
+ qWarning("archive list not initialised");
+ }
+ 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("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);
+ }
+}
+
+
+void MainWindow::closeEvent(QCloseEvent* event)
+{
+ if (m_DownloadManager.downloadsInProgress() &&
+ 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;
+ }
+
+ setCursor(Qt::WaitCursor);
+
+ m_NexusDialog.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)) {
+ Profile newProf("Default", false);
+ refreshProfiles(false);
+ }
+}
+
+
+void MainWindow::setBrowserGeometry(const QByteArray &geometry)
+{
+ m_NexusDialog.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(this)) {
+ 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()));
+ }
+}
+
+
+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()));
+ QToolButton *toolBtn = qobject_cast<QToolButton*>(ui->toolBar->widgetForAction(ui->actionTool));
+ toolBtn->menu()->addAction(action);
+}
+
+
+bool MainWindow::registerPlugin(QObject *plugin)
+{
+ { // generic treatment for all plugins
+ IPlugin *pluginObj = qobject_cast<IPlugin*>(plugin);
+ if (pluginObj == NULL) {
+ return false;
+ }
+ m_Settings.registerPlugin(pluginObj);
+ }
+
+ { // diagnosis plugins
+ IPluginDiagnose *diagnose = qobject_cast<IPluginDiagnose*>(plugin);
+ if (diagnose != NULL) {
+ m_DiagnosisPlugins.push_back(diagnose);
+ }
+ }
+
+ { // 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;
+ }
+ }
+ return false;
+}
+
+
+void MainWindow::loadPlugins()
+{
+ m_DiagnosisPlugins.clear();
+
+ m_Settings.clearPlugins();
+
+ foreach (QObject *plugin, QPluginLoader::staticInstances()) {
+ registerPlugin(plugin);
+ }
+
+ QString pluginPath = QDir::fromNativeSeparators(ToQString(GameInfo::instance().getOrganizerDirectory())) + "/" + ToQString(AppConfig::pluginPath());
+ qDebug("looking for plugins in %s", pluginPath.toUtf8().constData());
+ QDirIterator iter(pluginPath, QDir::Files | QDir::NoDotAndDotDot);
+ while (iter.hasNext()) {
+ iter.next();
+ QString pluginName = iter.filePath();
+ if (QLibrary::isLibrary(pluginName)) {
+ QPluginLoader pluginLoader(pluginName);
+ if (pluginLoader.instance() == NULL) {
+ qCritical("failed to load plugin %s: %s",
+ pluginName.toUtf8().constData(), pluginLoader.errorString().toUtf8().constData());
+ } else {
+ if (registerPlugin(pluginLoader.instance())) {
+ qDebug("loaded plugin \"%s\"", pluginName.toUtf8().constData());
+ } else {
+ qWarning("plugin \"%s\" failed to load", pluginName.toUtf8().constData());
+ }
+ }
+ }
+ }
+}
+
+IGameInfo &MainWindow::gameInfo() const
+{
+ return *m_GameInfo;
+}
+
+
+QString MainWindow::profileName() const
+{
+ return m_CurrentProfile->getName();
+}
+
+QString MainWindow::profilePath() const
+{
+ return m_CurrentProfile->getPath();
+}
+
+VersionInfo MainWindow::appVersion() const
+{
+ return m_Updater.getVersion();
+}
+
+
+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(const QString &name)
+{
+ unsigned int index = ModInfo::getIndex(name);
+ if (index != UINT_MAX) {
+ throw MyException(tr("The mod \"%1\" already exists!").arg(name));
+ }
+
+ QString targetDirectory = QDir::fromNativeSeparators(m_Settings.getModDirectory()).append("/").append(name.trimmed());
+
+ QSettings settingsFile(targetDirectory.mid(0).append("/meta.ini"), QSettings::IniFormat);
+
+ settingsFile.setValue("modid", 0);
+ settingsFile.setValue("version", 0);
+ settingsFile.setValue("newestVersion", 0);
+ settingsFile.setValue("category", 0);
+ settingsFile.setValue("installationFile", 0);
+ return ModInfo::createFrom(QDir(targetDirectory), &m_DirectoryStructure).data();
+}
+
+bool MainWindow::removeMod(IModInterface *mod)
+{
+ return ModInfo::removeMod(ModInfo::getIndex(mod->name()));
+}
+
+
+void MainWindow::modDataChanged(IModInterface*)
+{
+ refreshModList();
+}
+
+QVariant MainWindow::pluginSetting(const QString &pluginName, const QString &key) const
+{
+ return m_Settings.pluginSetting(pluginName, key);
+}
+
+
+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("\"%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,
+ m_Settings.directInterface(), "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();
+ }
+ }
+ }
+
+ return startBinary(binary, arguments, profileName, m_Settings.logLevel(), currentDirectory, true);
+}
+
+
+void MainWindow::spawnProgram(const QString &fileName, const QString &argumentsArg,
+ const QString &profileName, const QDir &currentDirectory)
+{
+ QFileInfo binary;
+ QString arguments = argumentsArg;
+ QString steamAppID;
+ try {
+ const Executable &exe = m_ExecutablesList.find(fileName);
+ steamAppID = exe.m_SteamAppID;
+ if (arguments == "") {
+ arguments = exe.m_Arguments;
+ }
+ binary = exe.m_BinaryInfo;
+ } catch (const std::runtime_error&) {
+ qWarning("\"%s\" not set up as executable", fileName.toUtf8().constData());
+ binary = QFileInfo(fileName);
+ }
+ spawnBinaryDirect(binary, arguments, profileName, currentDirectory, steamAppID);
+}
+
+
+void MainWindow::spawnBinary(const QFileInfo &binary, const QString &arguments, const QDir &currentDirectory, bool closeAfterStart, const QString &steamAppID)
+{
+ storeSettings();
+
+ HANDLE processHandle = spawnBinaryDirect(binary, arguments, m_CurrentProfile->getName(), currentDirectory, steamAppID);
+ if (processHandle != INVALID_HANDLE_VALUE) {
+ if (closeAfterStart) {
+ close();
+ } else {
+ this->setEnabled(false);
+
+ LockedDialog *dialog = new LockedDialog(this);
+ dialog->show();
+
+ QCoreApplication::processEvents();
+
+ while ((::WaitForSingleObject(processHandle, 1000) == WAIT_TIMEOUT) &&
+ !dialog->unlockClicked()) {
+ // keep processing events so the app doesn't appear dead
+ QCoreApplication::processEvents();
+ }
+
+ this->setEnabled(true);
+ refreshDirectoryStructure();
+ refreshDataTree();
+ if (GameInfo::instance().getLoadOrderMechanism() == GameInfo::TYPE_FILETIME) {
+ QFile::remove(m_CurrentProfile->getLoadOrderFileName());
+ }
+ refreshLists();
+ dialog->hide();
+ }
+ }
+}
+
+
+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::refreshModList()
+{
+ ModInfo::updateFromDisc(m_Settings.getModDirectory(), &m_DirectoryStructure);
+ m_CurrentProfile->refreshModStatus();
+
+ m_ModList.notifyChange(-1);
+
+ refreshDirectoryStructure();
+}
+
+
+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);
+ }
+
+ const Executable &selectedExecutable = executableBox->itemData(executableBox->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::activateSelectedProfile()
+{
+ QString profileName = ui->profileBox->currentText();
+ qDebug() << "activate profile " << 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)));
+
+ refreshModList();
+ refreshSaveList();
+}
+
+
+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();
+ }
+
+ if (ui->profileBox->currentIndex() == 0) {
+ ui->profileBox->setCurrentIndex(previousIndex);
+ ProfilesDialog(m_GamePath).exec();
+ while (!refreshProfiles()) {
+ ProfilesDialog(m_GamePath).exec();
+ }
+ } else {
+ activateSelectedProfile();
+ }
+ }
+}
+
+
+void MainWindow::updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const DirectoryEntry &directoryEntry, bool conflictsOnly)
+{
+ {
+ std::vector<FileEntry*> files = directoryEntry.getFiles();
+ for (auto iter = files.begin(); iter != files.end(); ++iter) {
+ FileEntry *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) {
+ QStringList columns(ToQString((*current)->getName()));
+ columns.append("");
+ QTreeWidgetItem *directoryChild = new QTreeWidgetItem(columns);
+ updateTo(directoryChild, temp.str(), **current, conflictsOnly);
+ if (directoryChild->childCount() != 0) {
+ subTree->addChild(directoryChild);
+ }
+ }
+ }
+ subTree->sortChildren(0, Qt::AscendingOrder);
+}
+
+
+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;
+ }
+}
+
+
+void MainWindow::refreshDirectoryStructure()
+{
+ m_DirectoryUpdate = true;
+ std::vector<std::tuple<QString, QString, int> > activeModList = m_CurrentProfile->getActiveMods();
+ m_DirectoryRefresher.setMods(activeModList);
+
+ statusBar()->show();
+// m_RefreshProgress->setVisible(true);
+ m_RefreshProgress->setRange(0, 100);
+
+ QTimer::singleShot(0, &m_DirectoryRefresher, SLOT(refresh()));
+}
+
+
+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(QPixmap::fromWinHICON(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::refreshSaveList()
+{
+ refreshLists();
+ 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)));
+ }
+
+ 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_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
+ m_PluginList.refresh(m_CurrentProfile->getName(), *m_DirectoryStructure,
+ m_CurrentProfile->getPluginsFileName(),
+ m_CurrentProfile->getLoadOrderFileName(),
+ m_CurrentProfile->getLockedOrderFileName());
+ //m_PluginList.readFrom(m_CurrentProfile->getPluginsFileName());
+
+ findChild<QPushButton*>("btnSave")->setEnabled(false);
+}
+
+
+static bool BySortValue(const std::pair<UINT32, QTreeWidgetItem*> &LHS, const std::pair<UINT32, QTreeWidgetItem*> &RHS)
+{
+ return LHS.first < RHS.first;
+}
+
+
+void MainWindow::refreshBSAList()
+{
+ m_ArchivesInit = false;
+ ui->bsaList->clear();
+#if QT_VERSION >= 0x50000
+ 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(',');
+ }
+
+ 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();
+
+ QFile archiveFile(m_CurrentProfile->getArchivesFileName());
+ if (archiveFile.open(QIODevice::ReadOnly)) {
+ while (!archiveFile.atEnd()) {
+ m_ActiveArchives.append(QString::fromUtf8(archiveFile.readLine()));
+ }
+ archiveFile.close();
+
+ for (int i = 0; i < m_ActiveArchives.count(); ++i) {
+ m_ActiveArchives[i] = m_ActiveArchives[i].trimmed();
+ }
+ } else {
+ m_ActiveArchives = m_DefaultArchives;
+ }
+
+ std::vector<std::pair<UINT32, QTreeWidgetItem*> > items;
+
+ std::vector<FileEntry*> files = m_DirectoryStructure->getFiles();
+ for (auto iter = files.begin(); iter != files.end(); ++iter) {
+ FileEntry *current = *iter;
+
+ QString filename = ToQString(current->getName().c_str());
+ QString extension = filename.right(3).toLower();
+
+ if (extension == "bsa") {
+ if (filename.compare("update.bsa", Qt::CaseInsensitive) == 0) {
+ // hack: ignore update.bsa because it confuses people
+ continue;
+ }
+ int index = m_ActiveArchives.indexOf(filename);
+ 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);
+ if (m_Settings.forceEnableCoreFiles() && m_DefaultArchives.contains(filename)) {
+ newItem->setCheckState(0, Qt::Checked);
+ newItem->setDisabled(true);
+ } else {
+ newItem->setCheckState(0, (index != -1) ? Qt::Checked : Qt::Unchecked);
+ }
+
+ 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) {
+ ui->bsaList->addTopLevelItem(iter->second);
+ }
+
+ checkBSAList();
+ m_ArchivesInit = true;
+}
+
+
+void MainWindow::checkBSAList()
+{
+ ui->bsaList->blockSignals(true);
+
+ bool warning = false;
+
+ for (int i = 0; i < ui->bsaList->topLevelItemCount(); ++i) {
+ QTreeWidgetItem *item = ui->bsaList->topLevelItem(i);
+ 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/resources/dialog-warning.png"));
+ item->setToolTip(0, tr("This bsa is enabled in the ini file so it may be required!"));
+ warning = true;
+ } else {
+ QString espName = filename.mid(0, filename.length() - 3).append("esp").toLower();
+ QString esmName = filename.mid(0, filename.length() - 3).append("esm").toLower();
+ if (m_PluginList.isEnabled(espName) || m_PluginList.isEnabled(esmName)) {
+ item->setIcon(0, QIcon(":/MO/gui/resources/dialog-warning.png"));
+ item->setToolTip(0, tr("This archive will still be loaded since there is a plugin of the same name but "
+ "its files will not follow installation order!"));
+ warning = true;
+ }
+ }
+ }
+ }
+
+ if (warning) {
+ ui->tabWidget->setTabIcon(1, QIcon(":/MO/gui/resources/dialog-warning.png"));
+ } else {
+ ui->tabWidget->setTabIcon(1, QIcon());
+ }
+
+ ui->bsaList->blockSignals(false);
+}
+
+
+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::readSettings()
+{
+ QSettings settings(ToQString(GameInfo::instance().getIniFilename()), QSettings::IniFormat);
+
+ if (settings.contains("window_geometry")) {
+ restoreGeometry(settings.value("window_geometry").toByteArray());
+ }
+
+ if (settings.contains("browser_geometry")) {
+ setBrowserGeometry(settings.value("browser_geometry").toByteArray());
+ }
+
+ bool filtersVisible = settings.value("filters_visible", false).toBool();
+ setCategoryListVisible(filtersVisible);
+ ui->displayCategoriesBtn->setChecked(filtersVisible);
+
+
+ QString language = settings.value("Settings/language", QLocale::system().name()).toString();
+ languageChange(language);
+ int selectedExecutable = settings.value("selected_executable").toInt();
+ setExecutableIndex(selectedExecutable);
+}
+
+
+void MainWindow::storeSettings()
+{
+ if (m_CurrentProfile == NULL) {
+ return;
+ }
+ m_CurrentProfile->writeModlist();
+ m_CurrentProfile->createTweakedIniFile();
+ saveCurrentLists();
+ m_Settings.setupLoadMechanism();
+ QSettings settings(ToQString(GameInfo::instance().getIniFilename()), 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("compact_downloads", ui->compactBox->isChecked());
+ settings.setValue("ask_for_nexuspw", m_AskForNexusPW);
+
+ settings.setValue("window_geometry", this->saveGeometry());
+ settings.setValue("browser_geometry", m_NexusDialog.saveGeometry());
+
+ settings.setValue("filters_visible", ui->displayCategoriesBtn->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());
+}
+
+
+void MainWindow::on_btnRefreshData_clicked()
+{
+ if (!m_DirectoryUpdate) {
+ refreshDirectoryStructure();
+ refreshDataTree();
+ } else {
+ qDebug("directory update");
+ }
+}
+
+void MainWindow::on_tabWidget_currentChanged(int index)
+{
+ saveCurrentLists();
+ 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();
+ }
+}
+
+
+static QString guessModName(const QString &fileName)
+{
+ return QFileInfo(fileName).baseName();
+}
+
+
+void MainWindow::installMod(const QString &fileName)
+{
+ bool hasIniTweaks = false;
+ QString modName;
+ m_CurrentProfile->writeModlistNow();
+ if (m_InstallationManager.install(fileName, m_CurrentProfile->getPluginsFileName(), m_Settings.getModDirectory(), m_Settings.preferIntegratedInstallers(),
+ m_Settings.enableQuickInstaller(), modName, hasIniTweaks)) {
+ MessageDialog::showMessage(tr("Installation successful"), this);
+ refreshModList();
+
+ QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, 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);
+ }
+ testExtractBSA(modIndex);
+ } 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);
+ }
+}
+
+
+void MainWindow::installMod()
+{
+ try {
+ QStringList extensions = m_InstallationManager.getSupportedExtensions();
+ for (auto iter = extensions.begin(); iter != extensions.end(); ++iter) {
+ *iter = "*." + *iter;
+ }
+
+ QString fileName = QFileDialog::getOpenFileName(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());
+ }
+}
+
+
+void MainWindow::on_btnSave_clicked()
+{
+ saveCurrentLists();
+}
+
+
+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;
+ }
+}
+
+
+void MainWindow::setCompactDownloads(bool compact)
+{
+ ui->compactBox->setChecked(compact);
+}
+
+
+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::esplist_changed()
+{
+ findChild<QPushButton*>("btnSave")->setEnabled(true);
+}
+
+
+void MainWindow::refresher_progress(int percent)
+{
+ m_RefreshProgress->setValue(percent);
+}
+
+
+void MainWindow::directory_refreshed()
+{
+ DirectoryEntry *newStructure = m_DirectoryRefresher.getDirectoryStructure();
+ if (newStructure != NULL) {
+ delete m_DirectoryStructure;
+ m_DirectoryStructure = newStructure;
+ } else {
+ // TODO: don't know why this happens, this slot seems to get called twice with only one emit
+ return;
+ }
+ m_DirectoryUpdate = false;
+ refreshLists();
+// m_RefreshProgress->setVisible(false);
+ statusBar()->hide();
+}
+
+
+void MainWindow::externalMessage(const QString &message)
+{
+ if (message.left(6).toLower() == "nxm://") {
+ MessageDialog::showMessage(tr("Download started"), this);
+ downloadRequestedNXM(message);
+ }
+}
+
+
+void MainWindow::modStatusChanged(unsigned int index)
+{
+ try {
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(index);
+ if (m_CurrentProfile->modEnabled(index)) {
+ DirectoryRefresher::addModToStructure(m_DirectoryStructure, modInfo->name(), m_CurrentProfile->getModPriority(index), modInfo->absolutePath());
+/* m_DirectoryStructure->addFromOrigin(ToWString(modInfo->name()),
+ ToWString(QDir::toNativeSeparators(modInfo->absolutePath())),
+ m_CurrentProfile->getModPriority(index));*/
+ DirectoryRefresher::cleanStructure(m_DirectoryStructure);
+ } else {
+ if (m_DirectoryStructure->originExists(ToWString(modInfo->name()))) {
+ FilesOrigin &origin = m_DirectoryStructure->getOriginByName(ToWString(modInfo->name()));
+ origin.enable(false);
+ }
+ }
+ 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);
+ m_DirectoryStructure->getOriginByName(ToWString(modInfo->name())).setPriority(priority);
+ }
+ }
+ 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::on_profileRefreshBtn_clicked()
+{
+ m_CurrentProfile->writeModlist();
+
+// m_ModList.updateModCollection();
+ 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) {
+ break;
+ }
+ if (line.at(0) == '#') {
+ outBuffer.write(line);
+ continue;
+ }
+ QString modName = QString::fromUtf8(line.constData()).trimmed();
+ if (modName.isEmpty()) {
+ break;
+ }
+
+ bool disabled = false;
+ if (modName.at(0) == '-') {
+ disabled = true;
+ modName = modName.mid(1);
+ } else if (modName.at(0) == '+') {
+ modName = modName.mid(1);
+ }
+
+ if (modName == oldName) {
+ modName = newName;
+ }
+
+ if (disabled) {
+ outBuffer.write("-");
+ } else {
+ outBuffer.write("+");
+ }
+ 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()));
+ }
+}
+
+
+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()
+{
+ ui->modList->setCurrentIndex(QModelIndex());
+
+ // save previous filter text so we can restore it later, in case the filter still exists then
+ QTreeWidgetItem *currentItem = ui->categoriesList->currentItem();
+ QString previousFilter = currentItem != NULL ? currentItem->text(0) : tr("<All>");
+
+ ui->categoriesList->clear();
+ addFilterItem(NULL, tr("<All>"), CategoryFactory::CATEGORY_NONE);
+ 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("<No category>"), CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY);
+ addFilterItem(NULL, tr("<Conflicted>"), CategoryFactory::CATEGORY_SPECIAL_CONFLICT);
+
+ 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);
+
+ QList<QTreeWidgetItem*> matches = ui->categoriesList->findItems(previousFilter, Qt::MatchFixedString | Qt::MatchRecursive);
+ if (matches.size() > 0) {
+ QTreeWidgetItem *currentItem = matches.at(0);
+ ui->categoriesList->setCurrentItem(currentItem);
+ while (currentItem != NULL) {
+ currentItem->setExpanded(true);
+ currentItem = currentItem->parent();
+ }
+ }
+}
+
+
+void MainWindow::renameMod_clicked()
+{
+ try {
+ QModelIndex treeIdx = m_ModListSortProxy->mapFromSource(m_ModList.index(m_ContextRow, 0));
+ ui->modList->setCurrentIndex(treeIdx);
+ ui->modList->edit(treeIdx);
+ } 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) && !removeDir(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::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_ModListSortProxy->mapToSource(idx).row())->name();
+ 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::reinstallMod_clicked()
+{
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
+ QString installationFile = modInfo->getInstallationFile();
+ if (installationFile.length() != 0) {
+ // there was a bug where mods installed through NCC had the absolute download path stored
+ if (QFileInfo(installationFile).isAbsolute()) {
+ installationFile = QFileInfo(installationFile).fileName();
+ }
+ QString 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::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));
+ m_NexusDialog.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::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));
+ m_NexusDialog.login(username, password);
+ } else {
+ MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this);
+ }
+ }
+}
+
+
+void MainWindow::displayModInformation(ModInfo::Ptr modInfo, unsigned int index, int tab)
+{
+ std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
+ if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) != flags.end()) {
+ OverwriteInfoDialog dialog(modInfo, this);
+ dialog.exec();
+ } else {
+ ModInfoDialog dialog(modInfo, m_DirectoryStructure, 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)));
+ connect(&dialog, SIGNAL(originModified(int)), this, SLOT(originModified(int)));
+ connect(&dialog, SIGNAL(endorseMod(ModInfo::Ptr)), this, SLOT(endorseMod(ModInfo::Ptr)));
+
+ dialog.openTab(tab);
+ dialog.exec();
+
+ modInfo->saveMeta();
+ emit modInfoDisplayed();
+ }
+
+ 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_DirectoryStructure->addFromOrigin(ToWString(modInfo->name()),
+ ToWString(QDir::toNativeSeparators(modInfo->absolutePath())),
+ m_CurrentProfile->getModPriority(index));*/
+ DirectoryRefresher::addModToStructure(m_DirectoryStructure,
+ modInfo->name(), m_CurrentProfile->getModPriority(index),
+ modInfo->absolutePath());
+ DirectoryRefresher::cleanStructure(m_DirectoryStructure);
+ refreshLists();
+ }
+ }
+}
+
+
+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::testExtractBSA(int modIndex)
+{
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modIndex);
+ QDir dir(modInfo->absolutePath());
+
+ QFileInfoList archives = dir.entryInfoList(QStringList("*.bsa"));
+ if (archives.length() != 0 &&
+ (QuestionBoxMemory::query(this, m_Settings.directInterface(), "unpackBSA", tr("Extract BSA"),
+ tr("This mod contains at least one BSA. Do you want to unpack it?\n"
+ "(This removes the BSA after completion. If you don't know about BSAs, just select no)"),
+ QDialogButtonBox::Yes | QDialogButtonBox::No, QDialogButtonBox::No) == QMessageBox::Yes)) {
+
+
+ foreach (QFileInfo archiveInfo, archives) {
+ BSA::Archive archive;
+
+ BSA::EErrorCode result = archive.read(archiveInfo.absoluteFilePath().toLocal8Bit().constData());
+ if ((result != BSA::ERROR_NONE) && (result != BSA::ERROR_INVALIDHASHES)) {
+ reportError(tr("failed to read %1: %2").arg(archiveInfo.fileName()).arg(result));
+ return;
+ }
+
+ QProgressDialog progress(this);
+ progress.setMaximum(100);
+ progress.setValue(0);
+ progress.show();
+
+ archive.extractAll(modInfo->absolutePath().toUtf8().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."));
+ }
+
+ archive.close();
+
+ if (!QFile::remove(archiveInfo.absoluteFilePath())) {
+ qCritical("failed to remove archive %s", archiveInfo.absoluteFilePath().toUtf8().constData());
+ } else {
+ m_DirectoryStructure->removeFile(ToWString(archiveInfo.fileName()));
+ }
+ }
+
+ refreshBSAList();
+ }
+}
+
+
+void MainWindow::visitOnNexus_clicked()
+{
+ int modID = m_ModList.data(m_ModList.index(m_ContextRow, 0), Qt::UserRole).toInt();
+ if (modID > 0) {
+ nexusLinkActivated(QString("%1/downloads/file.php?id=%2").arg(ToQString(GameInfo::instance().getNexusPage())).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::setPriorityMax()
+{
+ int newPriority = m_CurrentProfile->numMods() - 1;
+ m_CurrentProfile->setModPriority(m_ContextRow, newPriority);
+}
+
+
+void MainWindow::setPriorityManually()
+{
+// m_CurrentProfile->setModPriority(m_ContextRow, m_CurrentProfile->numMods() - 1);
+
+ int current = m_CurrentProfile->getModPriority(m_ContextRow);
+ int newPriority = QInputDialog::getInt(this, tr("Priority"), tr("Choose Priority"), current, 0, m_ModList.rowCount() - 1);
+ m_ModList.changeModPriority(m_ContextRow, newPriority);
+// m_CurrentProfile->setModPriority(m_ContextRow, newPriority);
+}
+
+
+void MainWindow::setPriorityMin()
+{
+ int newPriority = 0;
+ m_CurrentProfile->setModPriority(m_ContextRow, newPriority);
+}
+
+
+void MainWindow::cancelModListEditor()
+{
+ ui->modList->setEnabled(false);
+ ui->modList->setEnabled(true);
+}
+
+
+void MainWindow::on_modList_doubleClicked(const QModelIndex &index)
+{
+ try {
+ displayModInformation(m_ModListSortProxy->mapToSource(index).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::addCategories(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 (addCategories(targetMenu, m_CategoryFactory.getCategoryID(i)) || enabled) {
+ targetMenu->setIcon(QIcon(":/MO/gui/resources/check.png"));
+ }
+ }
+ }
+ }
+ return childEnabled;
+}
+
+
+void MainWindow::saveCategoriesFromMenu(QMenu *menu, int modRow)
+{
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(modRow);
+ foreach (QAction* action, menu->actions()) {
+ if (action->menu() != NULL) {
+ saveCategoriesFromMenu(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());
+// m_ModList.setModCategory(modRow, widgetAction->data().toInt(), checkbox->isChecked());
+ }
+ }
+ }
+}
+
+
+void MainWindow::saveCategories()
+{
+ QMenu *menu = qobject_cast<QMenu*>(sender());
+ if (menu == NULL) {
+ qCritical("not a menu?");
+ return;
+ }
+// m_ModList.resetCategories(m_ContextRow);
+ saveCategoriesFromMenu(menu, m_ContextRow);
+
+ refreshFilters();
+}
+
+
+void MainWindow::savePrimaryCategory()
+{
+ QMenu *menu = qobject_cast<QMenu*>(sender());
+ if (menu == NULL) {
+ qCritical("not a menu?");
+ return;
+ }
+
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(m_ContextRow);
+ foreach (QAction* action, menu->actions()) {
+ QWidgetAction *widgetAction = qobject_cast<QWidgetAction*>(action);
+ if (widgetAction != NULL) {
+ QRadioButton *btn = qobject_cast<QRadioButton*>(widgetAction->defaultWidget());
+ if (btn->isChecked()) {
+ modInfo->setPrimaryCategory(widgetAction->data().toInt());
+ break;
+ }
+ }
+ }
+}
+
+
+void MainWindow::checkModsForUpdates()
+{
+ if (NexusInterface::instance()->getAccessManager()->loggedIn()) {
+ m_ModsToUpdate = ModInfo::checkAllForUpdate(this);
+ statusBar()->show();
+ 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));
+ m_NexusDialog.login(username, password);
+ } else { // otherwise there will be no endorsement info
+ m_ModsToUpdate = ModInfo::checkAllForUpdate(this);
+ }
+ }
+}
+
+
+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));
+ 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->filterMatches(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 MainWindow::on_modList_customContextMenuRequested(const QPoint &pos)
+{
+ try {
+ QTreeView *modList = findChild<QTreeView*>("modList");
+
+ m_ContextRow = m_ModListSortProxy->mapToSource(modList->indexAt(pos)).row();
+
+ QMenu menu;
+ 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(on_profileRefreshBtn_clicked()));
+
+ menu.addAction(tr("Export to csv..."), this, SLOT(exportModListCSV()));
+
+ if (m_ContextRow != -1) {
+ menu.addSeparator();
+ 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()) {
+ menu.addAction(tr("Sync to Mods..."), this, SLOT(syncOverwrite()));
+ } 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 {
+ QMenu *addCategoryMenu = menu.addMenu(tr("Set Category"));
+ addCategories(addCategoryMenu, 0);
+ connect(addCategoryMenu, SIGNAL(aboutToHide()), this, SLOT(saveCategories()));
+
+ QMenu *primaryCategoryMenu = menu.addMenu(tr("Primary Category"));
+ connect(primaryCategoryMenu, SIGNAL(aboutToShow()), this, SLOT(addPrimaryCategoryCandidates()));
+ connect(primaryCategoryMenu, SIGNAL(aboutToHide()), this, SLOT(savePrimaryCategory()));
+
+ 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()));
+ } break;
+ default: {
+ QAction *action = new QAction(tr("Endorsement state unknown"), &menu);
+ action->setEnabled(false);
+ menu.addAction(action);
+ } break;
+ }
+
+ menu.addAction(tr("Visit on Nexus"), this, SLOT(visitOnNexus_clicked()));
+ menu.addAction(tr("Open in explorer"), this, SLOT(openExplorer_clicked()));
+ }
+
+ 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_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *)
+{
+ if (current != NULL) {
+ m_ModListSortProxy->setCategoryFilter(current->data(0, Qt::UserRole).toInt());
+ ui->currentCategoryLabel->setText(QString("(%1)").arg(current->text(0)));
+ }
+}
+
+
+void MainWindow::fixMods_clicked()
+{
+ QListWidgetItem *selectedItem = ui->savegameList->item(m_SelectedSaveGame);
+ 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 = qvariant_cast<SaveGameGamebryo*>(selectedItem->data(Qt::UserRole));
+
+ // 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");
+
+ {
+ 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>");
+ }
+ }
+ }
+
+ 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());
+ }
+ }
+ }
+
+ 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>") {
+ 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)
+{
+ QListWidgetItem *selectedItem = ui->savegameList->itemAt(pos);
+ if (selectedItem == NULL) {
+ return;
+ }
+
+ m_SelectedSaveGame = ui->savegameList->row(selectedItem);
+
+ QMenu menu;
+ menu.addAction(tr("Fix Mods..."), this, SLOT(fixMods_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;
+ updateToolBar();
+}
+
+void MainWindow::linkDesktop()
+{
+ QComboBox* executablesList = findChild<QComboBox*>("executablesListBox");
+// QPushButton *linkButton = findChild<QPushButton*>("linkDesktopButton");
+
+ 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(m_ExeName);
+ // create link
+ std::wstring targetFile = ToWString(exeInfo.absoluteFilePath());
+ std::wstring parameter = ToWString(QString("\"") + selectedExecutable.m_BinaryInfo.absoluteFilePath() + "\" " + selectedExecutable.m_Arguments);
+ std::wstring description = ToWString(selectedExecutable.m_BinaryInfo.fileName());
+ std::wstring currentDirectory = ToWString(selectedExecutable.m_BinaryInfo.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(m_ExeName);
+ // create link
+ std::wstring targetFile = ToWString(exeInfo.absoluteFilePath());
+ std::wstring parameter = ToWString(QString("\"") + selectedExecutable.m_BinaryInfo.absoluteFilePath() + "\" " + selectedExecutable.m_Arguments);
+ std::wstring description = ToWString(selectedExecutable.m_BinaryInfo.fileName());
+ std::wstring currentDirectory = ToWString(selectedExecutable.m_BinaryInfo.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::on_actionSettings_triggered()
+{
+ QString oldModDirectory(m_Settings.getModDirectory());
+ QString oldCacheDirectory(m_Settings.getCacheDirectory());
+ m_Settings.query(this);
+ 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());
+ }
+ }
+
+ if (m_Settings.getModDirectory() != oldModDirectory) {
+ refreshModList();
+ refreshLists();
+ }
+
+ if (m_Settings.getCacheDirectory() != oldCacheDirectory) {
+ NexusInterface::instance()->setCacheDirectory(m_Settings.getCacheDirectory());
+ }
+
+ NexusInterface::instance()->setNMMVersion(m_Settings.getNMMVersion());
+}
+
+void MainWindow::on_actionNexus_triggered()
+{
+ QString username, password;
+ m_NexusDialog.openUrl(ToQString(GameInfo::instance().getNexusPage()));
+
+ if (m_Settings.getNexusLogin(username, password)) {
+ m_NexusDialog.login(username, password);
+ } else {
+ m_NexusDialog.loadNexus();
+ }
+ m_NexusDialog.show();
+ m_NexusDialog.activateWindow();
+
+ QTabWidget *tabWidget = findChild<QTabWidget*>("tabWidget");
+ tabWidget->setCurrentIndex(4);
+}
+
+
+void MainWindow::nexusLinkActivated(const QString &link)
+{
+ if (m_Settings.preferExternalBrowser()) {
+ ::ShellExecuteW(NULL, L"open", ToWString(link).c_str(), NULL, NULL, SW_SHOWNORMAL);
+ } else {
+ QString username, password;
+ m_NexusDialog.openUrl(link);
+ if (m_Settings.getNexusLogin(username, password)) {
+ m_NexusDialog.login(username, password);
+ m_LoginAttempted = true;
+ } else {
+ m_NexusDialog.loadNexus();
+ }
+ m_NexusDialog.show();
+
+ QTabWidget *tabWidget = findChild<QTabWidget*>("tabWidget");
+ tabWidget->setCurrentIndex(4);
+ }
+}
+
+
+void MainWindow::downloadRequestedNXM(const QString &url)
+{
+ QString username, password;
+
+ if (!m_LoginAttempted && !NexusInterface::instance()->getAccessManager()->loggedIn() &&
+ (m_Settings.getNexusLogin(username, password) ||
+ (m_AskForNexusPW && queryLogin(username, password)))) {
+ m_PendingDownloads.append(url);
+ NexusInterface::instance()->getAccessManager()->login(username, password);
+ m_LoginAttempted = true;
+ } else {
+ m_DownloadManager.addNXMDownload(url);
+ }
+}
+
+
+void MainWindow::downloadRequested(QNetworkReply *reply, int modID, const QString &fileName)
+{
+ try {
+ if (m_DownloadManager.addDownload(reply, fileName, 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::languageChange(const QString &newLanguage)
+{
+ if (m_Translator != NULL) {
+ QCoreApplication::removeTranslator(m_Translator);
+ delete m_Translator;
+ m_Translator = NULL;
+ }
+ if (m_TranslatorQt != NULL) {
+ QCoreApplication::removeTranslator(m_TranslatorQt);
+ delete m_TranslatorQt;
+ m_TranslatorQt = NULL;
+ }
+
+ if (newLanguage != "en_US") {
+ // add our own translations
+ m_Translator = new QTranslator(this);
+ QString locFile = ToQString(AppConfig::translationPrefix()) + "_" + newLanguage;
+ if (!m_Translator->load(locFile, QCoreApplication::applicationDirPath() + "/translations")) {
+ qDebug("localization %s not found", locFile.toUtf8().constData());
+ }
+ QCoreApplication::installTranslator(m_Translator);
+
+ // also add the translations for qt default strings
+ m_TranslatorQt = new QTranslator(this);
+ locFile = QString("qt_") + newLanguage;
+ if (!m_TranslatorQt->load(locFile, QCoreApplication::applicationDirPath() + "/translations")) {
+ qDebug("localization %s not found", locFile.toUtf8().constData());
+ }
+ QCoreApplication::installTranslator(m_TranslatorQt);
+ }
+ 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);
+ QString modName;
+
+ // see if there already are mods with the specified mod id
+ if (modID != 0) {
+ ModInfo::Ptr modInfo = ModInfo::getByModID(modID, true);
+ if (!modInfo.isNull()) {
+ std::vector<ModInfo::EFlag> flags = modInfo->getFlags();
+ if (std::find(flags.begin(), flags.end(), ModInfo::FLAG_BACKUP) == flags.end()) {
+ modName = modInfo->name();
+ modInfo->saveMeta();
+ }
+ }
+ // TODO there may be multiple mods with the same id!
+// modName = m_ModList.getModByModID(modID);
+ }
+
+ m_CurrentProfile->writeModlistNow();
+
+ bool hasIniTweaks = false;
+ if (m_InstallationManager.install(fileName, m_CurrentProfile->getPluginsFileName(), m_Settings.getModDirectory(), m_Settings.preferIntegratedInstallers(),
+ m_Settings.enableQuickInstaller(), modName, hasIniTweaks)) {
+ MessageDialog::showMessage(tr("Installation successful"), this);
+
+ refreshModList();
+
+ QModelIndexList posList = m_ModList.match(m_ModList.index(0, 0), Qt::DisplayRole, 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);
+ }
+ testExtractBSA(modIndex);
+ } 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*> files = directoryEntry.getFiles();
+ for (auto iter = files.begin(); iter != files.end(); ++iter) {
+ FileEntry *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::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()));
+ // 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()
+{
+ QString username, password;
+
+ if (!m_LoginAttempted && !NexusInterface::instance()->getAccessManager()->loggedIn() &&
+ (m_Settings.getNexusLogin(username, password) ||
+ (m_AskForNexusPW && queryLogin(username, password)))) {
+ NexusInterface::instance()->getAccessManager()->login(username, password);
+ m_LoginAttempted = true;
+ } 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());
+ }
+}
+
+
+void MainWindow::updateDownloadListDelegate()
+{
+ if (ui->compactBox->isChecked()) {
+ ui->downloadView->setItemDelegate(new DownloadListWidgetCompactDelegate(&m_DownloadManager, ui->downloadView));
+ } else {
+ ui->downloadView->setItemDelegate(new DownloadListWidgetDelegate(&m_DownloadManager, 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)));
+
+ ui->downloadView->setModel(sortProxy);
+ ui->downloadView->sortByColumn(1, Qt::AscendingOrder);
+ ui->downloadView->header()->resizeSections(QHeaderView::Fixed);
+// ui->downloadView->setFirstColumnSpanned(0, QModelIndex(), true);
+
+ 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(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)), &m_DownloadManager, SLOT(resumeDownload(int)));
+}
+
+
+void MainWindow::on_compactBox_toggled(bool)
+{
+ updateDownloadListDelegate();
+}
+
+
+void MainWindow::modDetailsUpdated(bool)
+{
+ --m_ModsToUpdate;
+ if (m_ModsToUpdate == 0) {
+ statusBar()->hide();
+ m_ModListSortProxy->setCategoryFilter(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 {
+ ModInfo::Ptr info = ModInfo::getByModID(result["id"].toInt(), true);
+ if (!info.isNull()) {
+ info->setNewestVersion(VersionInfo(result["version"].toString()));
+ info->setNexusDescription(result["description"].toString());
+ if (NexusInterface::instance()->getAccessManager()->loggedIn()) {
+ // don't use endorsement info if we're not logged in
+ info->setIsEndorsed(result["voted_by_user"].toBool());
+ }
+ }
+ }
+ }
+
+ if (m_ModsToUpdate <= 0) {
+ statusBar()->hide();
+ m_ModListSortProxy->setCategoryFilter(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::nxmRequestFailed(int modID, 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();
+}
+
+
+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();
+}
+
+
+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 = ui->bsaList->topLevelItem(m_ContextRow);
+
+ 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());
+ 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).toUtf8().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 = 0; 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 = 0;
+ 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_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::on_bsaList_itemChanged(QTreeWidgetItem*, int)
+{
+ checkBSAList();
+}
+
+void MainWindow::on_actionProblems_triggered()
+{
+ QString problemDescription;
+ checkForProblems(problemDescription);
+ QMessageBox::information(this, tr("Problems"), problemDescription);
+}
+
+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::on_categoriesList_customContextMenuRequested(const QPoint &pos)
+{
+ QMenu menu;
+ menu.addAction(tr("Edit Categories..."), this, SLOT(editCategories()));
+
+ menu.exec(ui->categoriesList->mapToGlobal(pos));
+}
+
+
+void MainWindow::lockESPIndex()
+{
+ m_PluginList.lockESPIndex(m_ContextRow, true);
+}
+
+void MainWindow::unlockESPIndex()
+{
+ m_PluginList.lockESPIndex(m_ContextRow, false);
+}
+
+
+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()));
+
+ if ((m_ContextRow != -1) && m_PluginList.isEnabled(m_ContextRow)) {
+ if (m_PluginList.isESPLocked(m_ContextRow)) {
+ menu.addAction(tr("Unlock index"), this, SLOT(unlockESPIndex()));
+ } else {
+ menu.addAction(tr("Lock index"), 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"));
+ }
+}
diff --git a/src/mainwindow.h b/src/mainwindow.h
index 7aaa5dc3..1d6508be 100644
--- a/src/mainwindow.h
+++ b/src/mainwindow.h
@@ -17,407 +17,414 @@ 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 <directoryentry.h>
-#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 "settings.h"
-#include "nexusdialog.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"
-
-namespace Ui {
- class MainWindow;
-}
-
-class QToolButton;
-
-class MainWindow : public QMainWindow, public IOrganizer
-{
- Q_OBJECT
-
-public:
- explicit MainWindow(const QString &exeName, QSettings &initSettings, QWidget *parent = 0);
- ~MainWindow();
-
- void readSettings();
-
- bool addProfile();
- void refreshLists();
- void refreshESPList();
- void refreshBSAList();
- void refreshDataTree();
- void refreshSaveList();
- void refreshModList();
-
- void setExecutablesList(const ExecutablesList &executablesList);
-
- void setModListSorting(int index);
- void setESPListSorting(int index);
- void setCompactDownloads(bool compact);
-
- 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 IGameInfo &gameInfo() const;
- virtual QString profileName() const;
- virtual QString profilePath() const;
- virtual VersionInfo appVersion() const;
- virtual IModInterface *getMod(const QString &name);
- virtual IModInterface *createMod(const QString &name);
- virtual bool removeMod(IModInterface *mod);
- virtual void modDataChanged(IModInterface *mod);
- virtual QVariant pluginSetting(const QString &pluginName, const QString &key) const;
-
- void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info);
-
-public slots:
-
- void displayColumnSelection(const QPoint &pos);
-
- void externalMessage(const QString &message);
- void modorder_changed();
- void esplist_changed();
- void refresher_progress(int percent);
- void directory_refreshed();
-
- void toolPluginInvoke();
-
-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);
-
-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 actionToToolButton(QAction *&sourceAction);
- bool verifyPlugin(IPlugin *plugin);
- void registerPluginTool(IPluginTool *tool);
- bool registerPlugin(QObject *pluginObj);
-
- void updateToolBar();
- void activateSelectedProfile();
-
- void setBrowserGeometry(const QByteArray &geometry);
- void setExecutableIndex(int index);
-
- bool nexusLogin();
-
- void saveCurrentESPList();
-
- 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 DirectoryEntry &directoryEntry, bool conflictsOnly);
- void refreshDirectoryStructure();
- bool refreshProfiles(bool selectProfile = true);
- void refreshExecutablesList();
- void installMod(const QString &fileName);
- void installMod();
- 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 DirectoryEntry &directoryEntry);
-
- void renameModInList(QFile &modList, const QString &oldName, const QString &newName);
-
- void refreshFilters();
-
- void saveCategoriesFromMenu(QMenu *menu, int modRow);
-
- bool addCategories(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);
-
- void checkBSAList();
-
- bool checkForProblems(QString &problemDescription);
-
- int getBinaryExecuteInfo(const QFileInfo &targetInfo, QFileInfo &binaryInfo, QString &arguments);
- void addFilterItem(const QString &name, int categoryID);
-
- void setCategoryListVisible(bool visible);
-
- void updateProblemsButton();
-
- SaveGameGamebryo *getSaveGame(const QString &name);
- SaveGameGamebryo *getSaveGame(QListWidgetItem *item);
-
- void displaySaveGameInfo(const SaveGameGamebryo *save, QPoint pos);
-
- HANDLE nextChildProcess();
-
- bool errorReported(QString &logFile);
-
-private:
-
- Ui::MainWindow *ui;
-
- TutorialControl m_Tutorial;
-
- QString m_ExeName;
-
- int m_OldProfileIndex;
-
- QThread m_RefresherThread;
- DirectoryRefresher m_DirectoryRefresher;
- 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;
- ModListSortProxy *m_ModListSortProxy;
- PluginList m_PluginList;
- PluginListSortProxy *m_PluginListSortProxy;
-
- ExecutablesList m_ExecutablesList;
- int m_OldExecutableIndex;
-
- QString m_GamePath;
-
- int m_ContextRow;
- QTreeWidgetItem *m_ContextItem;
- int m_SelectedSaveGame;
-
- Settings m_Settings;
-
- NexusDialog m_NexusDialog;
- DownloadManager m_DownloadManager;
- InstallationManager m_InstallationManager;
-
- QTranslator *m_Translator;
- QTranslator *m_TranslatorQt;
-
- 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;
-
- QTime m_StartTime;
- SaveGameInfoWidget *m_CurrentSaveView;
-
- IGameInfo *m_GameInfo;
-
- std::vector<IPluginDiagnose*> m_DiagnosisPlugins;
-
-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 unendorse_clicked();
- void visitOnNexus_clicked();
- void openExplorer_clicked();
- void information_clicked();
- // savegame context menu
- void fixMods_clicked();
- // data-tree context menu
- void writeDataToFile();
- void openDataFile();
- void addAsExecutable();
- void hideFile();
- void unhideFile();
-
- 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 removeOrigin(const QString &name);
-
- void setPriorityMax();
- void setPriorityManually();
- void setPriorityMin();
-
- void procError(QProcess::ProcessError error);
- void procFinished(int exitCode, QProcess::ExitStatus exitStatus);
-
- // nexus related
- void checkModsForUpdates();
- void nexusLinkActivated(const QString &link);
-
- 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 saveCategories();
- void savePrimaryCategory();
- void addPrimaryCategoryCandidates();
-
- void modDetailsUpdated(bool success);
-
- void nxmUpdatesAvailable(const std::vector<int> &modIDs, QVariant userData, QVariant resultData, int requestID);
- void nxmEndorsementToggled(int, QVariant, QVariant resultData, int);
- void nxmRequestFailed(int modID, QVariant userData, int requestID, const QString &errorString);
-
- void editCategories();
-
- void displayModInformation(const QString &modName, int tab);
-
- void modRenamed(const QString &oldName, const QString &newName);
-
- void hideSaveGameInfo();
-
- void hookUpWindowTutorials();
-
- void endorseMod(ModInfo::Ptr mod);
- void cancelModListEditor();
-
- void lockESPIndex();
- void unlockESPIndex();
-
- void enableVisibleMods();
- void disableVisibleMods();
-
-private slots: // ui slots
- // actions
- void on_actionAdd_Profile_triggered();
- void on_actionInstallMod_triggered();
- void on_actionModify_Executables_triggered();
- void on_actionNexus_triggered();
- void on_actionProblems_triggered();
- void on_actionSettings_triggered();
- void on_actionUpdate_triggered();
- void on_actionEndorseMO_triggered();
-
- void on_bsaList_customContextMenuRequested(const QPoint &pos);
- void on_bsaList_itemChanged(QTreeWidgetItem *item, int column);
- void on_btnRefreshData_clicked();
- void on_btnSave_clicked();
- void on_categoriesList_currentItemChanged(QListWidgetItem *current, QListWidgetItem *previous);
- void on_categoriesList_customContextMenuRequested(const QPoint &pos);
- void on_compactBox_toggled(bool checked);
- 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_profileRefreshBtn_clicked();
- 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);
-};
-
-#endif // MAINWINDOW_H
+#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 <directoryentry.h>
+#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 "settings.h"
+#include "nexusdialog.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"
+
+namespace Ui {
+ class MainWindow;
+}
+
+class QToolButton;
+
+class MainWindow : public QMainWindow, public IOrganizer
+{
+ Q_OBJECT
+
+public:
+ explicit MainWindow(const QString &exeName, QSettings &initSettings, QWidget *parent = 0);
+ ~MainWindow();
+
+ void readSettings();
+
+ bool addProfile();
+ void refreshLists();
+ void refreshESPList();
+ void refreshBSAList();
+ void refreshDataTree();
+ void refreshSaveList();
+ void refreshModList();
+
+ void setExecutablesList(const ExecutablesList &executablesList);
+
+ void setModListSorting(int index);
+ void setESPListSorting(int index);
+ void setCompactDownloads(bool compact);
+
+ 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 IGameInfo &gameInfo() const;
+ virtual QString profileName() const;
+ virtual QString profilePath() const;
+ virtual VersionInfo appVersion() const;
+ virtual IModInterface *getMod(const QString &name);
+ virtual IModInterface *createMod(const QString &name);
+ virtual bool removeMod(IModInterface *mod);
+ virtual void modDataChanged(IModInterface *mod);
+ virtual QVariant pluginSetting(const QString &pluginName, const QString &key) const;
+
+ void addPrimaryCategoryCandidates(QMenu *primaryCategoryMenu, ModInfo::Ptr info);
+
+public slots:
+
+ void displayColumnSelection(const QPoint &pos);
+
+ void externalMessage(const QString &message);
+ void modorder_changed();
+ void esplist_changed();
+ void refresher_progress(int percent);
+ void directory_refreshed();
+
+ void toolPluginInvoke();
+
+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);
+
+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 actionToToolButton(QAction *&sourceAction);
+ bool verifyPlugin(IPlugin *plugin);
+ void registerPluginTool(IPluginTool *tool);
+ bool registerPlugin(QObject *pluginObj);
+
+ void updateToolBar();
+ void activateSelectedProfile();
+
+ void setBrowserGeometry(const QByteArray &geometry);
+ void setExecutableIndex(int index);
+
+ bool nexusLogin();
+
+ void saveCurrentESPList();
+
+ 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 DirectoryEntry &directoryEntry, bool conflictsOnly);
+ void refreshDirectoryStructure();
+ bool refreshProfiles(bool selectProfile = true);
+ void refreshExecutablesList();
+ void installMod(const QString &fileName);
+ void installMod();
+ 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 DirectoryEntry &directoryEntry);
+
+ void renameModInList(QFile &modList, const QString &oldName, const QString &newName);
+
+ void refreshFilters();
+
+ void saveCategoriesFromMenu(QMenu *menu, int modRow);
+
+ bool addCategories(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);
+
+ void checkBSAList();
+
+ bool checkForProblems(QString &problemDescription);
+
+ 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);
+
+ void updateProblemsButton();
+
+ 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);
+
+private:
+
+ Ui::MainWindow *ui;
+
+ TutorialControl m_Tutorial;
+
+ QString m_ExeName;
+
+ int m_OldProfileIndex;
+
+ QThread m_RefresherThread;
+ DirectoryRefresher m_DirectoryRefresher;
+ 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;
+ ModListSortProxy *m_ModListSortProxy;
+ PluginList m_PluginList;
+ PluginListSortProxy *m_PluginListSortProxy;
+
+ ExecutablesList m_ExecutablesList;
+ int m_OldExecutableIndex;
+
+ QString m_GamePath;
+
+ int m_ContextRow;
+ QTreeWidgetItem *m_ContextItem;
+ int m_SelectedSaveGame;
+
+ Settings m_Settings;
+
+ NexusDialog m_NexusDialog;
+ DownloadManager m_DownloadManager;
+ InstallationManager m_InstallationManager;
+
+ QTranslator *m_Translator;
+ QTranslator *m_TranslatorQt;
+
+ 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;
+
+ QTime m_StartTime;
+ SaveGameInfoWidget *m_CurrentSaveView;
+
+ IGameInfo *m_GameInfo;
+
+ std::vector<IPluginDiagnose*> m_DiagnosisPlugins;
+
+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 unendorse_clicked();
+ void visitOnNexus_clicked();
+ void openExplorer_clicked();
+ void information_clicked();
+ // savegame context menu
+ void fixMods_clicked();
+ // data-tree context menu
+ void writeDataToFile();
+ void openDataFile();
+ void addAsExecutable();
+ 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 removeOrigin(const QString &name);
+
+ void setPriorityMax();
+ void setPriorityManually();
+ void setPriorityMin();
+
+ void procError(QProcess::ProcessError error);
+ void procFinished(int exitCode, QProcess::ExitStatus exitStatus);
+
+ // nexus related
+ void checkModsForUpdates();
+ void nexusLinkActivated(const QString &link);
+
+ 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 saveCategories();
+ void savePrimaryCategory();
+ void addPrimaryCategoryCandidates();
+
+ void modDetailsUpdated(bool success);
+
+ void nxmUpdatesAvailable(const std::vector<int> &modIDs, QVariant userData, QVariant resultData, int requestID);
+ void nxmEndorsementToggled(int, QVariant, QVariant resultData, int);
+ void nxmRequestFailed(int modID, QVariant userData, int requestID, const QString &errorString);
+
+ void editCategories();
+
+ void displayModInformation(const QString &modName, int tab);
+
+ void modRenamed(const QString &oldName, const QString &newName);
+
+ void hideSaveGameInfo();
+
+ void hookUpWindowTutorials();
+
+ void endorseMod(ModInfo::Ptr mod);
+ void cancelModListEditor();
+
+ void lockESPIndex();
+ void unlockESPIndex();
+
+ void enableVisibleMods();
+ void disableVisibleMods();
+ void exportModListCSV();
+
+ void startExeAction();
+
+private slots: // ui slots
+ // actions
+ void on_actionAdd_Profile_triggered();
+ void on_actionInstallMod_triggered();
+ void on_actionModify_Executables_triggered();
+ void on_actionNexus_triggered();
+ void on_actionProblems_triggered();
+ void on_actionSettings_triggered();
+ void on_actionUpdate_triggered();
+ void on_actionEndorseMO_triggered();
+
+ void on_bsaList_customContextMenuRequested(const QPoint &pos);
+ void on_bsaList_itemChanged(QTreeWidgetItem *item, int column);
+ void on_btnRefreshData_clicked();
+ void on_btnSave_clicked();
+ void on_categoriesList_currentItemChanged(QTreeWidgetItem *current, QTreeWidgetItem *previous);
+ void on_categoriesList_customContextMenuRequested(const QPoint &pos);
+ void on_compactBox_toggled(bool checked);
+ 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_profileRefreshBtn_clicked();
+ 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);
+};
+
+#endif // MAINWINDOW_H
diff --git a/src/mainwindow.ui b/src/mainwindow.ui
index c6742dfd..517434a7 100644
--- a/src/mainwindow.ui
+++ b/src/mainwindow.ui
@@ -55,7 +55,7 @@
<number>1</number>
</property>
<item>
- <widget class="QListWidget" name="categoriesList">
+ <widget class="QTreeWidget" name="categoriesList">
<property name="minimumSize">
<size>
<width>100</width>
@@ -71,6 +71,14 @@
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
+ <attribute name="headerVisible">
+ <bool>false</bool>
+ </attribute>
+ <column>
+ <property name="text">
+ <string notr="true">1</string>
+ </property>
+ </column>
</widget>
</item>
</layout>
diff --git a/src/modinfo.cpp b/src/modinfo.cpp
index a46774c2..0a0cde68 100644
--- a/src/modinfo.cpp
+++ b/src/modinfo.cpp
@@ -17,828 +17,829 @@ 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>
-
-
-std::vector<ModInfo::Ptr> ModInfo::s_Collection;
-std::map<QString, unsigned int> ModInfo::s_ModsByName;
-std::map<int, 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::NexusFileInfo::NexusFileInfo(const QString &data)
-{
- QVariantList result = QtJson::Json::parse(data).toList();
-
- while (result.length() < 7) {
- result.append(QVariant());
- }
- id = result.at(0).toInt();
- name = result.at(1).toString();
- url = result.at(2).toString();
- version = result.at(3).toString();
- description = result.at(4).toString();
- category = result.at(5).toInt();
- size = result.at(6).toInt();
-}
-
-
-QString ModInfo::NexusFileInfo::toString() const
-{
- return QString("[ %1,\"%2\",\"%3\",\"%4\",\"%5\",%6,%7 ]")
- .arg(id)
- .arg(name)
- .arg(url)
- .arg(version)
- .arg(description.mid(0).replace("\"", "'"))
- .arg(category)
- .arg(size);
-}
-
-
-ModInfo::Ptr ModInfo::createFrom(const QDir &dir, DirectoryEntry **directoryStructure)
-{
- QMutexLocker locker(&s_Mutex);
-// int id = s_NextID++;
- static QRegExp backupExp(".*backup[0-9]*");
- ModInfo::Ptr result;
- if (backupExp.exactMatch(dir.dirName())) {
- result = ModInfo::Ptr(new ModInfoBackup(dir, directoryStructure));
- } else {
- result = ModInfo::Ptr(new ModInfoRegular(dir, directoryStructure));
- }
- s_Collection.push_back(result);
- return result;
-}
-
-
-void ModInfo::createFromOverwrite()
-{
- QMutexLocker locker(&s_Mutex);
-
- s_Collection.push_back(ModInfo::Ptr(new ModInfoOverwrite));
-}
-
-
-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];
-}
-
-
-ModInfo::Ptr ModInfo::getByModID(int modID, bool missingAcceptable)
-{
- QMutexLocker locker(&s_Mutex);
-
- std::map<int, unsigned int>::iterator iter = s_ModsByModID.find(modID);
- if (iter == s_ModsByModID.end()) {
- if (missingAcceptable) {
- return ModInfo::Ptr();
- } else {
- throw MyException(tr("invalid mod id %1").arg(modID));
- }
- }
-
- return getByIndex(iter->second);
-}
-
-
-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()));
-
- //TODO this is a bit more complicated since multiple mods may have the
- // same mod id but only one appears in the index
- std::map<int, unsigned int>::iterator iter = s_ModsByModID.find(modInfo->getNexusID()) ;
- if ((iter != s_ModsByModID.end()) &&
- (iter->second == index)) {
- s_ModsByModID.erase(iter);
- }
-
- // 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)
-{
- 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);
- }
-
- 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;
-
- // don't overwrite a modid-entry with a backup entry. This is a bit of a workaround
- if ((s_ModsByModID.find(modID) == s_ModsByModID.end()) ||
- !backupRegEx.exactMatch(modName)) {
- s_ModsByModID[modID] = 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());
- }
-}
-
-
-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::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();
- }
-}
-
-
-ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStructure)
- : ModInfo(), m_Name(path.dirName()), m_Path(path.absolutePath()), m_MetaInfoChanged(false),
- m_EndorsedState(ENDORSED_UNKNOWN), m_DirectoryStructure(directoryStructure)
-{
- testValid();
-
- // read out the meta-file for information
- QString metaFileName = path.absoluteFilePath("meta.ini");
- QSettings metaFile(metaFileName, 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_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")) {
- m_EndorsedState = metaFile.value("endorsed", false).toBool() ? ENDORSED_TRUE : ENDORSED_FALSE;
- }
-
- int numNexusFiles = metaFile.beginReadArray("nexusFiles");
- for (int i = 0; i < numNexusFiles; ++i) {
- metaFile.setArrayIndex(i);
- QString infoString = metaFile.value("info", "").toString();
- NexusFileInfo info(infoString);
- m_NexusFileInfos.push_back(info);
- }
- metaFile.endArray();
-
- 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;
- }
- }
- }
-
- connect(&m_NexusBridge, SIGNAL(nxmDescriptionAvailable(int,QVariant,QVariant)), this, SLOT(nxmDescriptionAvailable(int,QVariant,QVariant)));
- connect(&m_NexusBridge, SIGNAL(nxmFilesAvailable(int,QVariant,QVariant)), this, SLOT(nxmFilesAvailable(int,QVariant,QVariant)));
- connect(&m_NexusBridge, SIGNAL(nxmEndorsementToggled(int,QVariant,QVariant)), this, SLOT(nxmEndorsementToggled(int,QVariant,QVariant)));
- connect(&m_NexusBridge, SIGNAL(nxmRequestFailed(int,QVariant,QString)), this, SLOT(nxmRequestFailed(int,QVariant,QString)));
-}
-
-
-ModInfoRegular::~ModInfoRegular()
-{
- try {
- //TODO this may cause the meta-file and the directory to be
- // re-created after a remove
- saveMeta();
- } catch (const std::exception &e) {
- qCritical("failed to save meta information for \"%s\": %s",
- m_Name.toUtf8().constData(), e.what());
- }
-}
-
-
-void ModInfoRegular::saveMeta()
-{
- if (m_MetaInfoChanged) {
- if (QFile::exists(absolutePath().append("/meta.ini"))) {
- 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("version", m_Version.canonicalString());
- 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 == ENDORSED_TRUE ? true : false);
- }
-
- metaFile.beginWriteArray("nexusFiles");
- for (int i = 0; i < m_NexusFileInfos.size(); ++i) {
- metaFile.setArrayIndex(i);
- metaFile.setValue("info", m_NexusFileInfos.at(i).toString());
- }
- metaFile.endArray();
- } else {
- reportError(tr("failed to write %1/meta.ini: %2").arg(absolutePath()).arg(metaFile.status()));
- }
- } else {
- qWarning("mod %s has no meta.ini at %s/meta.ini", m_Name.toUtf8().constData(), absolutePath().toUtf8().constData());
- }
- m_MetaInfoChanged = false;
- }
-}
-
-
-bool ModInfoRegular::updateAvailable() const
-{
- return m_NewestVersion.isValid() && (m_Version < m_NewestVersion);
-}
-
-
-void ModInfoRegular::nxmDescriptionAvailable(int, QVariant, QVariant resultData)
-{
- QVariantMap result = resultData.toMap();
- m_NewestVersion.parse(result["version"].toString());
- m_NexusDescription = result["description"].toString();
- m_EndorsedState = result["voted_by_user"].toBool() ? ENDORSED_TRUE : ENDORSED_FALSE;
- m_NexusBridge.requestFiles(m_NexusID, QVariant());
-}
-
-
-void ModInfoRegular::nxmFilesAvailable(int, QVariant, QVariant resultData)
-{
- m_NexusFileInfos.clear();
-
- QVariantList result = resultData.toList();
-
- foreach(QVariant file, result) {
- QVariantMap fileInfo = file.toMap();
-
- m_NexusFileInfos.push_back(NexusFileInfo(fileInfo["id"].toInt(),
- fileInfo["name"].toString(),
- fileInfo["uri"].toString(),
- fileInfo["version"].toString(),
- fileInfo["description"].toString(),
- fileInfo["category_id"].toInt(),
- fileInfo["size"].toInt()));
- }
-
- 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, 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 (!modDir.rename(m_Name, name)) {
- return false;
- }
- }
-
- std::map<QString, unsigned int>::iterator nameIter = s_ModsByName.find(m_Name);
- 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();
-
- return true;
-}
-
-void ModInfoRegular::setNotes(const QString &notes)
-{
- m_Notes = notes;
-}
-
-void ModInfoRegular::setVersion(const VersionInfo &version)
-{
- m_Version = version;
- m_MetaInfoChanged = true;
-}
-
-void ModInfoRegular::setNexusDescription(const QString &description)
-{
- m_NexusDescription = description;
- m_MetaInfoChanged = true;
-}
-
-
-void ModInfoRegular::setIsEndorsed(bool endorsed)
-{
- m_EndorsedState = endorsed ? ENDORSED_TRUE : ENDORSED_FALSE;
- m_MetaInfoChanged = true;
-}
-
-
-bool ModInfoRegular::remove()
-{
- m_MetaInfoChanged = false;
- return removeDir(absolutePath());
-}
-
-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;
-}
-
-
-std::vector<ModInfo::EFlag> ModInfoRegular::getFlags() const
-{
- std::vector<ModInfo::EFlag> result;
- if (!isValid()) {
- result.push_back(ModInfo::FLAG_INVALID);
- }
- if ((m_NexusID != -1) && (endorsedState() == ENDORSED_FALSE)) {
- result.push_back(ModInfo::FLAG_NOTENDORSED);
- }
- 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 */ }
- }
- 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;
-}
-
-void ModInfoRegular::getNexusFiles(QList<ModInfo::NexusFileInfo>::const_iterator &begin, QList<ModInfo::NexusFileInfo>::const_iterator &end)
-{
- begin = m_NexusFileInfos.begin();
- end = m_NexusFileInfos.end();
-}
-
-QString ModInfoRegular::getNexusDescription() const
-{
- return m_NexusDescription;
-}
-
-
-ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const
-{
- return m_EndorsedState;
-}
-
-
-ModInfoRegular::EConflictType ModInfoRegular::isConflicted() const
-{
- 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(m_Name);
- if ((*m_DirectoryStructure)->originExists(name)) {
- FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name);
- std::vector<FileEntry*> 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) {
- 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;
- }
- }
- }
- }
- }
- }
-
- if (overwrite && overwritten) return CONFLICT_MIXED;
- else if (overwrite) return CONFLICT_OVERWRITE;
- else if (overwritten) {
- if (!regular) {
- return CONFLICT_REDUNDANT;
- } else {
- return CONFLICT_OVERWRITTEN;
- }
- }
- else return CONFLICT_NONE;
-}
-
-
-bool ModInfoRegular::isRedundant() const
-{
- std::wstring name = ToWString(m_Name);
- if ((*m_DirectoryStructure)->originExists(name)) {
- FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name);
- std::vector<FileEntry*> 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;
- }
-}
-
-
-QDateTime ModInfoRegular::getLastNexusQuery() const
-{
- return m_LastNexusQuery;
-}
-
-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, 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()
-{
- testValid();
-
-}
-
-
-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)");
-}
+#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>
+
+
+std::vector<ModInfo::Ptr> ModInfo::s_Collection;
+std::map<QString, unsigned int> ModInfo::s_ModsByName;
+std::map<int, 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::NexusFileInfo::NexusFileInfo(const QString &data)
+{
+ QVariantList result = QtJson::Json::parse(data).toList();
+
+ while (result.length() < 7) {
+ result.append(QVariant());
+ }
+ id = result.at(0).toInt();
+ name = result.at(1).toString();
+ url = result.at(2).toString();
+ version = result.at(3).toString();
+ description = result.at(4).toString();
+ category = result.at(5).toInt();
+ size = result.at(6).toInt();
+}
+
+
+QString ModInfo::NexusFileInfo::toString() const
+{
+ return QString("[ %1,\"%2\",\"%3\",\"%4\",\"%5\",%6,%7 ]")
+ .arg(id)
+ .arg(name)
+ .arg(url)
+ .arg(version)
+ .arg(description.mid(0).replace("\"", "'"))
+ .arg(category)
+ .arg(size);
+}
+
+
+ModInfo::Ptr ModInfo::createFrom(const QDir &dir, DirectoryEntry **directoryStructure)
+{
+ QMutexLocker locker(&s_Mutex);
+// int id = s_NextID++;
+ static QRegExp backupExp(".*backup[0-9]*");
+ ModInfo::Ptr result;
+ if (backupExp.exactMatch(dir.dirName())) {
+ result = ModInfo::Ptr(new ModInfoBackup(dir, directoryStructure));
+ } else {
+ result = ModInfo::Ptr(new ModInfoRegular(dir, directoryStructure));
+ }
+ s_Collection.push_back(result);
+ return result;
+}
+
+
+void ModInfo::createFromOverwrite()
+{
+ QMutexLocker locker(&s_Mutex);
+
+ s_Collection.push_back(ModInfo::Ptr(new ModInfoOverwrite));
+}
+
+
+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];
+}
+
+
+ModInfo::Ptr ModInfo::getByModID(int modID, bool missingAcceptable)
+{
+ QMutexLocker locker(&s_Mutex);
+
+ std::map<int, unsigned int>::iterator iter = s_ModsByModID.find(modID);
+ if (iter == s_ModsByModID.end()) {
+ if (missingAcceptable) {
+ return ModInfo::Ptr();
+ } else {
+ throw MyException(tr("invalid mod id %1").arg(modID));
+ }
+ }
+
+ return getByIndex(iter->second);
+}
+
+
+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()));
+
+ //TODO this is a bit more complicated since multiple mods may have the
+ // same mod id but only one appears in the index
+ std::map<int, unsigned int>::iterator iter = s_ModsByModID.find(modInfo->getNexusID()) ;
+ if ((iter != s_ModsByModID.end()) &&
+ (iter->second == index)) {
+ s_ModsByModID.erase(iter);
+ }
+
+ // 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)
+{
+ 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);
+ }
+
+ 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;
+
+ // don't overwrite a modid-entry with a backup entry. This is a bit of a workaround
+ if ((s_ModsByModID.find(modID) == s_ModsByModID.end()) ||
+ !backupRegEx.exactMatch(modName)) {
+ s_ModsByModID[modID] = 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());
+ }
+}
+
+
+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::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();
+ }
+}
+
+
+ModInfoRegular::ModInfoRegular(const QDir &path, DirectoryEntry **directoryStructure)
+ : ModInfo(), m_Name(path.dirName()), m_Path(path.absolutePath()), m_MetaInfoChanged(false),
+ m_EndorsedState(ENDORSED_UNKNOWN), m_DirectoryStructure(directoryStructure)
+{
+ testValid();
+
+ // read out the meta-file for information
+ QString metaFileName = path.absoluteFilePath("meta.ini");
+ QSettings metaFile(metaFileName, 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_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")) {
+ m_EndorsedState = metaFile.value("endorsed", false).toBool() ? ENDORSED_TRUE : ENDORSED_FALSE;
+ }
+
+ int numNexusFiles = metaFile.beginReadArray("nexusFiles");
+ for (int i = 0; i < numNexusFiles; ++i) {
+ metaFile.setArrayIndex(i);
+ QString infoString = metaFile.value("info", "").toString();
+ NexusFileInfo info(infoString);
+ m_NexusFileInfos.push_back(info);
+ }
+ metaFile.endArray();
+
+ 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;
+ }
+ }
+ }
+
+ connect(&m_NexusBridge, SIGNAL(nxmDescriptionAvailable(int,QVariant,QVariant)), this, SLOT(nxmDescriptionAvailable(int,QVariant,QVariant)));
+ connect(&m_NexusBridge, SIGNAL(nxmFilesAvailable(int,QVariant,QVariant)), this, SLOT(nxmFilesAvailable(int,QVariant,QVariant)));
+ connect(&m_NexusBridge, SIGNAL(nxmEndorsementToggled(int,QVariant,QVariant)), this, SLOT(nxmEndorsementToggled(int,QVariant,QVariant)));
+ connect(&m_NexusBridge, SIGNAL(nxmRequestFailed(int,QVariant,QString)), this, SLOT(nxmRequestFailed(int,QVariant,QString)));
+}
+
+
+ModInfoRegular::~ModInfoRegular()
+{
+ try {
+ //TODO this may cause the meta-file and the directory to be
+ // re-created after a remove
+ saveMeta();
+ } catch (const std::exception &e) {
+ qCritical("failed to save meta information for \"%s\": %s",
+ m_Name.toUtf8().constData(), e.what());
+ }
+}
+
+
+void ModInfoRegular::saveMeta()
+{
+ if (m_MetaInfoChanged) {
+ if (QFile::exists(absolutePath().append("/meta.ini"))) {
+ 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("version", m_Version.canonicalString());
+ 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 == ENDORSED_TRUE ? true : false);
+ }
+
+ metaFile.beginWriteArray("nexusFiles");
+ for (int i = 0; i < m_NexusFileInfos.size(); ++i) {
+ metaFile.setArrayIndex(i);
+ metaFile.setValue("info", m_NexusFileInfos.at(i).toString());
+ }
+ metaFile.endArray();
+ } else {
+ reportError(tr("failed to write %1/meta.ini: %2").arg(absolutePath()).arg(metaFile.status()));
+ }
+ } else {
+ qWarning("mod %s has no meta.ini at %s/meta.ini", m_Name.toUtf8().constData(), absolutePath().toUtf8().constData());
+ }
+ m_MetaInfoChanged = false;
+ }
+}
+
+
+bool ModInfoRegular::updateAvailable() const
+{
+ return m_NewestVersion.isValid() && (m_Version < m_NewestVersion);
+}
+
+
+void ModInfoRegular::nxmDescriptionAvailable(int, QVariant, QVariant resultData)
+{
+ QVariantMap result = resultData.toMap();
+ m_NewestVersion.parse(result["version"].toString());
+ m_NexusDescription = result["description"].toString();
+ m_EndorsedState = result["voted_by_user"].toBool() ? ENDORSED_TRUE : ENDORSED_FALSE;
+ m_NexusBridge.requestFiles(m_NexusID, QVariant());
+}
+
+
+void ModInfoRegular::nxmFilesAvailable(int, QVariant, QVariant resultData)
+{
+ m_NexusFileInfos.clear();
+
+ QVariantList result = resultData.toList();
+
+ foreach(QVariant file, result) {
+ QVariantMap fileInfo = file.toMap();
+
+ m_NexusFileInfos.push_back(NexusFileInfo(fileInfo["id"].toInt(),
+ fileInfo["name"].toString(),
+ fileInfo["uri"].toString(),
+ fileInfo["version"].toString(),
+ fileInfo["description"].toString(),
+ fileInfo["category_id"].toInt(),
+ fileInfo["size"].toInt()));
+ }
+
+ 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, 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 (!modDir.rename(m_Name, name)) {
+ return false;
+ }
+ }
+
+ std::map<QString, unsigned int>::iterator nameIter = s_ModsByName.find(m_Name);
+ 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();
+
+ return true;
+}
+
+void ModInfoRegular::setNotes(const QString &notes)
+{
+ m_Notes = notes;
+}
+
+void ModInfoRegular::setVersion(const VersionInfo &version)
+{
+ m_Version = version;
+ m_MetaInfoChanged = true;
+}
+
+void ModInfoRegular::setNexusDescription(const QString &description)
+{
+ m_NexusDescription = description;
+ m_MetaInfoChanged = true;
+}
+
+
+void ModInfoRegular::setIsEndorsed(bool endorsed)
+{
+ m_EndorsedState = endorsed ? ENDORSED_TRUE : ENDORSED_FALSE;
+ m_MetaInfoChanged = true;
+}
+
+
+bool ModInfoRegular::remove()
+{
+ m_MetaInfoChanged = false;
+ return removeDir(absolutePath());
+}
+
+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;
+}
+
+
+std::vector<ModInfo::EFlag> ModInfoRegular::getFlags() const
+{
+ std::vector<ModInfo::EFlag> result;
+ if (!isValid()) {
+ result.push_back(ModInfo::FLAG_INVALID);
+ }
+ if ((m_NexusID != -1) && (endorsedState() == ENDORSED_FALSE)) {
+ result.push_back(ModInfo::FLAG_NOTENDORSED);
+ }
+ 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 */ }
+ }
+ 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;
+}
+
+void ModInfoRegular::getNexusFiles(QList<ModInfo::NexusFileInfo>::const_iterator &begin, QList<ModInfo::NexusFileInfo>::const_iterator &end)
+{
+ begin = m_NexusFileInfos.begin();
+ end = m_NexusFileInfos.end();
+}
+
+QString ModInfoRegular::getNexusDescription() const
+{
+ return m_NexusDescription;
+}
+
+
+ModInfoRegular::EEndorsedState ModInfoRegular::endorsedState() const
+{
+ return m_EndorsedState;
+}
+
+
+ModInfoRegular::EConflictType ModInfoRegular::isConflicted() const
+{
+ 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(m_Name);
+ if ((*m_DirectoryStructure)->originExists(name)) {
+ FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name);
+ std::vector<FileEntry*> 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;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ if (overwrite && overwritten) return CONFLICT_MIXED;
+ else if (overwrite) return CONFLICT_OVERWRITE;
+ else if (overwritten) {
+ if (!regular) {
+ return CONFLICT_REDUNDANT;
+ } else {
+ return CONFLICT_OVERWRITTEN;
+ }
+ }
+ else return CONFLICT_NONE;
+}
+
+
+bool ModInfoRegular::isRedundant() const
+{
+ std::wstring name = ToWString(m_Name);
+ if ((*m_DirectoryStructure)->originExists(name)) {
+ FilesOrigin &origin = (*m_DirectoryStructure)->getOriginByName(name);
+ std::vector<FileEntry*> 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;
+ }
+}
+
+
+QDateTime ModInfoRegular::getLastNexusQuery() const
+{
+ return m_LastNexusQuery;
+}
+
+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, 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()
+{
+ testValid();
+
+}
+
+
+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)");
+}
diff --git a/src/modinfo.h b/src/modinfo.h
index 0e5367e4..12ba89fd 100644
--- a/src/modinfo.h
+++ b/src/modinfo.h
@@ -17,808 +17,810 @@ 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 <directoryentry.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>
-
-
-/**
- * @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 IModInterface
-{
-
- Q_OBJECT
-
-public:
-
- typedef QSharedPointer<ModInfo> Ptr;
-
- static QString s_HiddenExt;
-
- enum EFlag {
- FLAG_INVALID,
- FLAG_BACKUP,
- FLAG_OVERWRITE,
- 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
- };
-
- struct NexusFileInfo {
- NexusFileInfo(const QString &data);
- NexusFileInfo(int id, const QString &name, const QString &url, const QString &version,
- const QString &description, int category, int size)
- : id(id), name(name), url(url), version(version), description(description),
- category(category), size(size) {}
- int id;
- QString name;
- QString url;
- QString version;
- QString description;
- int category;
- int size;
-
- QString toString() const;
- };
-
-public:
-
- /**
- * @brief read the mod directory and Mod ModInfo objects for all subdirectories
- **/
- static void updateFromDisc(const QString &modDirectory, DirectoryEntry **directoryStructure);
-
- 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
- * @param missingAcceptable if true, this function will return a null-pointer if no mod has the specified mod id, otherwise an exception is thrown
- * @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 ModInfo::Ptr getByModID(int modID, bool missingAcceptable);
-
- /**
- * @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, DirectoryEntry **directoryStructure);
-
- /**
- * @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;
-
- /**
- * @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 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)
- **/
- virtual bool setName(const QString &name) = 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 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 VersionInfo &version) = 0;
-
- /**
- * @brief changes/updates the nexus description text
- * @param description the current description text
- */
- virtual void setNexusDescription(const QString &description) = 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;
-
- /**
- * @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 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 VersionInfo getVersion() const { return m_Version; }
-
- /**
- * @brief getter for the newest version number of this mod
- *
- * @return newest version of the mod
- **/
- virtual VersionInfo getNewestVersion() const = 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 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;
-
- /**
- * @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 manually set notes for this mod
- */
- virtual QString notes() const = 0;
-
- /**
- * @brief return the list of files on nexus related to this mod in the form of iterators
- * @note this is valid only after querying the files
- * @param begin iterator to the first file
- * @param end iterator to one past the last file
- */
- virtual void getNexusFiles(QList<NexusFileInfo>::const_iterator &begin, QList<NexusFileInfo>::const_iterator &end) = 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;
-
- /**
- * @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 this mod belongs to
- *
- * @return list of categories
- **/
- const std::set<int> &getCategories() const { return m_Categories; }
-
- /**
- * @return 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
- */
- 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_UNKNOWN; }
-
- /**
- * @brief updates the valid-flag for this mod
- */
- void testValid();
-
- /**
- * @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;
-
- VersionInfo m_Version;
-
-private:
-
- static QMutex s_Mutex;
- static std::map<int, unsigned int> s_ModsByModID;
- static int s_NextID;
-
- bool m_Valid;
-
-};
-
-
-
-/**
- * @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 ModInfo
-{
-
- Q_OBJECT
-
- friend class ModInfo;
-
-public:
-
- ~ModInfoRegular();
-
- virtual bool isRegular() const { return true; }
-
- /**
- * @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;
-
- /**
- * @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) { m_NexusID = 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 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 VersionInfo &version) { m_NewestVersion = version; }
-
- /**
- * @brief changes/updates the nexus description text
- * @param description the current description text
- */
- virtual void setNexusDescription(const QString &description);
-
- /**
- * 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);
-
- /**
- * @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
- **/
- VersionInfo getNewestVersion() const { return m_NewestVersion; }
-
- /**
- * @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;
-
- /**
- * @brief return the list of files on nexus related to this mod in the form of iterators
- * @note this is valid only after querying the files
- * @param begin iterator to the first file
- * @param end iterator to one past the last file
- */
- virtual void getNexusFiles(QList<NexusFileInfo>::const_iterator &begin, QList<NexusFileInfo>::const_iterator &end);
-
- /**
- * @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
- */
- QDateTime getLastNexusQuery() const;
-
- /**
- * @brief stores meta information back to disk
- */
- virtual void saveMeta();
-
-private:
-
- enum EConflictType {
- CONFLICT_NONE,
- CONFLICT_OVERWRITE,
- CONFLICT_OVERWRITTEN,
- CONFLICT_MIXED,
- CONFLICT_REDUNDANT
- };
-
-private slots:
-
- void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData);
- void nxmFilesAvailable(int, QVariant, QVariant resultData);
- void nxmEndorsementToggled(int, QVariant userData, QVariant resultData);
- void nxmRequestFailed(int modID, QVariant userData, const QString &errorMessage);
-
-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;
-
-protected:
-
- ModInfoRegular(const QDir &path, DirectoryEntry **directoryStructure);
-
-private:
-
- QString m_Name;
- QString m_Path;
- QString m_InstallationFile;
- QString m_Notes;
- QString m_NexusDescription;
- QList<NexusFileInfo> m_NexusFileInfos;
-
- QDateTime m_LastNexusQuery;
-
- int m_NexusID;
-
- bool m_MetaInfoChanged;
- VersionInfo m_NewestVersion;
-
- EEndorsedState m_EndorsedState;
-
- NexusBridge m_NexusBridge;
-
- DirectoryEntry **m_DirectoryStructure;
-
-};
-
-
-class ModInfoBackup : public ModInfoRegular
-{
-
- friend class ModInfo;
-
-public:
-
- virtual bool updateAvailable() const { return false; }
- virtual bool updateNXMInfo() { return false; }
- virtual void setNexusID(int) {}
- virtual void endorse(bool) {}
- virtual int getFixedPriority() const { return -1; }
- 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<NexusFileInfo>::const_iterator&, QList<NexusFileInfo>::const_iterator&) {}
- virtual QString getNexusDescription() const { return QString(); }
-
-private:
-
- ModInfoBackup(const QDir &path, DirectoryEntry **directoryStructure);
-
-};
-
-
-class ModInfoOverwrite : public ModInfo
-{
-
- Q_OBJECT
-
- friend class ModInfo;
-
-public:
-
- virtual bool updateAvailable() 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 VersionInfo&) {}
- virtual void setNexusDescription(const QString&) {}
- virtual void setIsEndorsed(bool) {}
- virtual bool remove() { return false; }
- virtual void endorse(bool) {}
- virtual QString name() const { return tr("Overwrite"); }
- virtual QString notes() const { return ""; }
- virtual QString absolutePath() const;
- virtual 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 void getNexusFiles(QList<NexusFileInfo>::const_iterator&, QList<NexusFileInfo>::const_iterator&) {}
- virtual QString getNexusDescription() const { return QString(); }
-
-private:
-
- ModInfoOverwrite();
-
-};
-
-#endif // MODINFO_H
+#ifndef MODINFO_H
+#define MODINFO_H
+
+#include "nexusinterface.h"
+#include <directoryentry.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>
+
+
+/**
+ * @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 IModInterface
+{
+
+ Q_OBJECT
+
+public:
+
+ typedef QSharedPointer<ModInfo> Ptr;
+
+ static QString s_HiddenExt;
+
+ enum EFlag {
+ FLAG_INVALID,
+ FLAG_BACKUP,
+ FLAG_OVERWRITE,
+ 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
+ };
+
+ struct NexusFileInfo {
+ NexusFileInfo(const QString &data);
+ NexusFileInfo(int id, const QString &name, const QString &url, const QString &version,
+ const QString &description, int category, int size)
+ : id(id), name(name), url(url), version(version), description(description),
+ category(category), size(size) {}
+ int id;
+ QString name;
+ QString url;
+ QString version;
+ QString description;
+ int category;
+ int size;
+
+ QString toString() const;
+ };
+
+public:
+
+ /**
+ * @brief read the mod directory and Mod ModInfo objects for all subdirectories
+ **/
+ static void updateFromDisc(const QString &modDirectory, DirectoryEntry **directoryStructure);
+
+ 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
+ * @param missingAcceptable if true, this function will return a null-pointer if no mod has the specified mod id, otherwise an exception is thrown
+ * @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 ModInfo::Ptr getByModID(int modID, bool missingAcceptable);
+
+ /**
+ * @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, DirectoryEntry **directoryStructure);
+
+ virtual bool isRegular() 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;
+
+ /**
+ * @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 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)
+ **/
+ virtual bool setName(const QString &name) = 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 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 VersionInfo &version) = 0;
+
+ /**
+ * @brief changes/updates the nexus description text
+ * @param description the current description text
+ */
+ virtual void setNexusDescription(const QString &description) = 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;
+
+ /**
+ * @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 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 VersionInfo getVersion() const { return m_Version; }
+
+ /**
+ * @brief getter for the newest version number of this mod
+ *
+ * @return newest version of the mod
+ **/
+ virtual VersionInfo getNewestVersion() const = 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 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;
+
+ /**
+ * @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 manually set notes for this mod
+ */
+ virtual QString notes() const = 0;
+
+ /**
+ * @brief return the list of files on nexus related to this mod in the form of iterators
+ * @note this is valid only after querying the files
+ * @param begin iterator to the first file
+ * @param end iterator to one past the last file
+ */
+ virtual void getNexusFiles(QList<NexusFileInfo>::const_iterator &begin, QList<NexusFileInfo>::const_iterator &end) = 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;
+
+ /**
+ * @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 this mod belongs to
+ *
+ * @return list of categories
+ **/
+ const std::set<int> &getCategories() const { return m_Categories; }
+
+ /**
+ * @return 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
+ */
+ 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_UNKNOWN; }
+
+ /**
+ * @brief updates the valid-flag for this mod
+ */
+ void testValid();
+
+ /**
+ * @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;
+
+ VersionInfo m_Version;
+
+private:
+
+ static QMutex s_Mutex;
+ static std::map<int, unsigned int> s_ModsByModID;
+ static int s_NextID;
+
+ bool m_Valid;
+
+};
+
+
+
+/**
+ * @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 ModInfo
+{
+
+ Q_OBJECT
+
+ friend class ModInfo;
+
+public:
+
+ ~ModInfoRegular();
+
+ virtual bool isRegular() const { return true; }
+
+ /**
+ * @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;
+
+ /**
+ * @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) { m_NexusID = 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 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 VersionInfo &version) { m_NewestVersion = version; }
+
+ /**
+ * @brief changes/updates the nexus description text
+ * @param description the current description text
+ */
+ virtual void setNexusDescription(const QString &description);
+
+ /**
+ * 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);
+
+ /**
+ * @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
+ **/
+ VersionInfo getNewestVersion() const { return m_NewestVersion; }
+
+ /**
+ * @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;
+
+ /**
+ * @brief return the list of files on nexus related to this mod in the form of iterators
+ * @note this is valid only after querying the files
+ * @param begin iterator to the first file
+ * @param end iterator to one past the last file
+ */
+ virtual void getNexusFiles(QList<NexusFileInfo>::const_iterator &begin, QList<NexusFileInfo>::const_iterator &end);
+
+ /**
+ * @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
+ */
+ QDateTime getLastNexusQuery() const;
+
+ /**
+ * @brief stores meta information back to disk
+ */
+ virtual void saveMeta();
+
+private:
+
+ enum EConflictType {
+ CONFLICT_NONE,
+ CONFLICT_OVERWRITE,
+ CONFLICT_OVERWRITTEN,
+ CONFLICT_MIXED,
+ CONFLICT_REDUNDANT
+ };
+
+private slots:
+
+ void nxmDescriptionAvailable(int modID, QVariant userData, QVariant resultData);
+ void nxmFilesAvailable(int, QVariant, QVariant resultData);
+ void nxmEndorsementToggled(int, QVariant userData, QVariant resultData);
+ void nxmRequestFailed(int modID, QVariant userData, const QString &errorMessage);
+
+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;
+
+protected:
+
+ ModInfoRegular(const QDir &path, DirectoryEntry **directoryStructure);
+
+private:
+
+ QString m_Name;
+ QString m_Path;
+ QString m_InstallationFile;
+ QString m_Notes;
+ QString m_NexusDescription;
+ QList<NexusFileInfo> m_NexusFileInfos;
+
+ QDateTime m_LastNexusQuery;
+
+ int m_NexusID;
+
+ bool m_MetaInfoChanged;
+ VersionInfo m_NewestVersion;
+
+ EEndorsedState m_EndorsedState;
+
+ NexusBridge m_NexusBridge;
+
+ DirectoryEntry **m_DirectoryStructure;
+
+};
+
+
+class ModInfoBackup : public ModInfoRegular
+{
+
+ friend class ModInfo;
+
+public:
+
+ virtual bool updateAvailable() const { return false; }
+ virtual bool updateNXMInfo() { return false; }
+ virtual void setNexusID(int) {}
+ virtual void endorse(bool) {}
+ virtual int getFixedPriority() const { return -1; }
+ 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<NexusFileInfo>::const_iterator&, QList<NexusFileInfo>::const_iterator&) {}
+ virtual QString getNexusDescription() const { return QString(); }
+
+private:
+
+ ModInfoBackup(const QDir &path, DirectoryEntry **directoryStructure);
+
+};
+
+
+class ModInfoOverwrite : public ModInfo
+{
+
+ Q_OBJECT
+
+ friend class ModInfo;
+
+public:
+
+ virtual bool updateAvailable() 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 VersionInfo&) {}
+ virtual void setNexusDescription(const QString&) {}
+ virtual void setIsEndorsed(bool) {}
+ virtual bool remove() { return false; }
+ virtual void endorse(bool) {}
+ virtual QString name() const { return tr("Overwrite"); }
+ virtual QString notes() const { return ""; }
+ virtual QString absolutePath() const;
+ virtual 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 void getNexusFiles(QList<NexusFileInfo>::const_iterator&, QList<NexusFileInfo>::const_iterator&) {}
+ virtual QString getNexusDescription() const { return QString(); }
+
+private:
+
+ ModInfoOverwrite();
+
+};
+
+#endif // MODINFO_H
diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp
index 88440d99..14b40c91 100644
--- a/src/modinfodialog.cpp
+++ b/src/modinfodialog.cpp
@@ -17,1202 +17,1201 @@ 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 "json.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>
-
-
-using QtJson::Json;
-
-
-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, 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());
-
- m_UTF8Codec = QTextCodec::codecForName("utf-8");
-
- QListWidget *textFileList = findChild<QListWidget*>("textFileList");
- QListWidget *iniFileList = findChild<QListWidget*>("iniFileList");
- QListWidget *iniTweaksList = findChild<QListWidget*>("iniTweaksList");
- QListWidget *activeESPList = findChild<QListWidget*>("activeESPList");
- QListWidget *inactiveESPList = findChild<QListWidget*>("inactiveESPList");
- QTreeWidget *categoriesTree = findChild<QTreeWidget*>("categoriesTree");
- QHBoxLayout *thumbnailArea = findChild<QHBoxLayout*>("thumbnailArea");
-
- m_FileTree = findChild<QTreeView*>("fileTree");
-
- 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");
- modIDEdit->setValidator(new QIntValidator(modIDEdit));
- modIDEdit->setText(QString("%1").arg(modInfo->getNexusID()));
-
- ui->notesEdit->setText(modInfo->notes());
-
- m_FileSystemModel = new QFileSystemModel(this);
- m_FileSystemModel->setReadOnly(false);
- m_FileSystemModel->setRootPath(m_RootPath);
- m_FileTree->setModel(m_FileSystemModel);
- m_FileTree->setRootIndex(m_FileSystemModel->index(m_RootPath));
- m_FileTree->setColumnWidth(0, 300);
-
- connect(&m_ThumbnailMapper, SIGNAL(mapped(const QString&)), this, SIGNAL(thumbnailClickedSignal(const QString&)));
- connect(this, SIGNAL(thumbnailClickedSignal(const QString&)), this, SLOT(thumbnailClicked(const QString&)));
-
- m_DeleteAction = new QAction(tr("&Delete"), m_FileTree);
- m_RenameAction = new QAction(tr("&Rename"), m_FileTree);
- m_HideAction = new QAction(tr("&Hide"), m_FileTree);
- m_UnhideAction = new QAction(tr("&Unhide"), m_FileTree);
- m_OpenAction = new QAction(tr("&Open"), m_FileTree);
- m_NewFolderAction = new QAction(tr("&New Folder"), m_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()));
- connect(m_ModInfo.data(), SIGNAL(modDetailsUpdated(bool)), this, SLOT(modDetailsUpdated(bool)));
-
- ui->descriptionView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
- QObject::connect(ui->descriptionView, SIGNAL(linkClicked(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;
- }
- }
-
- addCategories(CategoryFactory::instance(), modInfo->getCategories(), categoriesTree->invisibleRootItem(), 0);
- refreshPrimaryCategoriesBox();
- refreshLists();
-
- int numTweaks = m_Settings->beginReadArray("INI Tweaks");
- for (int i = 0; i < numTweaks; ++i) {
- m_Settings->setArrayIndex(i);
- QList<QListWidgetItem*> items = iniTweaksList->findItems(m_Settings->value("name").toString(), Qt::MatchFixedString);
- if (items.size() != 0) {
- items.at(0)->setCheckState(Qt::Checked);
- }
- }
- m_Settings->endArray();
-
- QTabWidget *tabWidget = findChild<QTabWidget*>("tabWidget");
- tabWidget->setTabEnabled(TAB_TEXTFILES, textFileList->count() != 0);
- tabWidget->setTabEnabled(TAB_INIFILES, (iniFileList->count() != 0) || (iniTweaksList->count() != 0));
- tabWidget->setTabEnabled(TAB_IMAGES, thumbnailArea->count() != 0);
- tabWidget->setTabEnabled(TAB_ESPS, (inactiveESPList->count() != 0) || (activeESPList->count() != 0));
- tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != NULL);
-
- if (tabWidget->currentIndex() == TAB_NEXUS) {
- activateNexusTab();
- }
-
- ui->endorseBtn->setEnabled(m_ModInfo->endorsedState() == ModInfo::ENDORSED_FALSE);
-}
-
-
-ModInfoDialog::~ModInfoDialog()
-{
- m_ModInfo->setNotes(ui->notesEdit->toPlainText());
- saveIniTweaks();
- saveCategories(ui->categoriesTree->invisibleRootItem());
-
- delete ui;
- delete m_Settings;
-}
-
-
-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*> 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 ignore;
- if ((*iter)->getOrigin(ignore) == 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]);
- ui->overwriteTree->addTopLevelItem(item);
- ++numOverwrite;
- } else {// otherwise don't display the file
- ++numNonConflicting;
- }
- } else {
- FilesOrigin &realOrigin = m_Directory->getOriginByID((*iter)->getOrigin(ignore));
- 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;
- }
- }
- }
-
-
- 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 (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)
-{
- QFile textFile(fileName);
- textFile.open(QIODevice::ReadOnly);
- QByteArray buffer = textFile.readAll();
- QTextCodec *codec = QTextCodec::codecForUtfText(buffer, m_UTF8Codec);
- ui->textFileView->setText(codec->toUnicode(buffer));
- ui->textFileView->setProperty("currentFile", fileName);
- ui->textFileView->setProperty("encoding", codec->name());
- ui->saveTXTButton->setEnabled(false);
-}
-
-
-void ModInfoDialog::openIniFile(const QString &fileName)
-{
- QPushButton* saveButton = findChild<QPushButton*>("saveButton");
-
- QFile iniFile(fileName);
- iniFile.open(QIODevice::ReadOnly);
- QByteArray buffer = iniFile.readAll();
- QTextCodec *codec = QTextCodec::codecForUtfText(buffer, m_UTF8Codec);
- QTextEdit *iniFileView = findChild<QTextEdit*>("iniFileView");
- iniFileView->setText(codec->toUnicode(buffer));
- iniFileView->setProperty("currentFile", fileName);
- iniFileView->setProperty("encoding", codec->name());
- iniFile.close();
-
- saveButton->setEnabled(false);
-}
-
-
-void ModInfoDialog::saveIniTweaks()
-{
- QListWidget *iniTweaksList = findChild<QListWidget*>("iniTweaksList");
-
- m_Settings->beginWriteArray("INI Tweaks");
-
- int countEnabled = 0;
- for (int i = 0; i < iniTweaksList->count(); ++i) {
- if (iniTweaksList->item(i)->checkState() == Qt::Checked) {
- m_Settings->setArrayIndex(countEnabled++);
- m_Settings->setValue("name", 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()) {
- QString fileName = fileNameVar.toString();
- 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()))) {
- 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()) {
- versionColor.setColor(QPalette::Text, Qt::red);
- ui->versionEdit->setToolTip(tr("Current Version: %1").arg(m_ModInfo->getNewestVersion().canonicalString()));
- } else {
- 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)"));
- }
-
- QString version = m_ModInfo->getNewestVersion().canonicalString();
-
- if (!version.isEmpty()) {
- m_ModInfo->setNewestVersion(version);
-
- updateVersionColor();
- }
-
- QTableWidget *filesWidget = findChild<QTableWidget*>("filesWidget");
- filesWidget->clearContents();
- filesWidget->setRowCount(0);
- filesWidget->setColumnCount(5);
-#if QT_VERSION >= 0x050000
- filesWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
-#else
- filesWidget->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch);
-#endif
- filesWidget->horizontalHeader()->setVisible(true);
- filesWidget->setColumnHidden(4, true);
-
-
- // prevent sorting while we change the table
- filesWidget->setSortingEnabled(false);
- int row = 0;
- QList<ModInfo::NexusFileInfo>::const_iterator current, end;
- m_ModInfo->getNexusFiles(current, end);
- for (; current != end; ++current) {
- filesWidget->insertRow(row);
- int type = current->category;
- filesWidget->setItem(row, 4, new QTableWidgetItem(QString("%1").arg(type)));
- filesWidget->setItem(row, 0, new QTableWidgetItem(getFileCategory(type)));
- QTableWidgetItem *nameItem = new QTableWidgetItem(current->name);
- nameItem->setToolTip(current->description);
- nameItem->setData(Qt::UserRole, current->id);
- filesWidget->setItem(row, 1, nameItem);
- filesWidget->setItem(row, 2, new QTableWidgetItem(current->version));
- QTableWidgetItem *sizeItem = new QTableWidgetItem();
- sizeItem->setData(Qt::DisplayRole, current->size);
- filesWidget->setItem(row, 3, sizeItem);
- ++row;
- }
- filesWidget->setSortingEnabled(true);
- filesWidget->sortItems(4);
- }
-}
-
-
-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())).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 (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();
-}
-
-void ModInfoDialog::on_filesWidget_doubleClicked(const QModelIndex &index)
-{
- QTableWidget *filesWidget = findChild<QTableWidget*>("filesWidget");
-
- QTableWidgetItem *selectedItem = filesWidget->item(index.row(), 1);
- if (selectedItem == NULL) {
- qDebug("no item in row %d", index.row());
- return;
- }
-
- QString nexusLink = QString("nxm://%1/mods/%2/files/%3")
- .arg(ToQString(GameInfo::instance().getGameName()))
- .arg(m_Settings->value("modid").toInt())
- .arg(selectedItem->data(Qt::UserRole).toInt());
-
- try {
- if (QuestionBoxMemory::query(this, Settings::instance().directInterface(), "confirmNexusDownload",
- tr("Confirm"), tr("Download \"%1\"?").arg(selectedItem->text()),
- QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::Yes) {
- emit downloadRequest(nexusLink);
- MessageDialog::showMessage(tr("Download started"), this);
- }
- } catch (const std::exception &e) {
- reportError(tr("Exception: %1").arg(e.what()));
- }
-}
-
-
-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;
- }
-
- m_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;
- }
-
- m_FileTree->setCurrentIndex(newIndex);
- m_FileTree->edit(newIndex);
-}
-
-
-void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos)
-{
- QItemSelectionModel *selectionModel = m_FileTree->selectionModel();
- m_FileSelection = selectionModel->selectedRows(0);
-
-// m_FileSelection = m_FileTree->indexAt(pos);
- QMenu menu(m_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(m_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())) {
- int originID = m_ConflictsContextItem->data(1, Qt::UserRole + 1).toInt();
- emit originModified(originID);
- refreshLists();
- }
-}
-
-
-void ModInfoDialog::unhideConflictFile()
-{
- if (unhideFile(m_ConflictsContextItem->data(0, Qt::UserRole).toString())) {
- int originID = m_ConflictsContextItem->data(1, Qt::UserRole + 1).toInt();
- emit originModified(originID);
- refreshLists();
- }
-}
-
-
-void ModInfoDialog::on_overwriteTree_customContextMenuRequested(const QPoint &pos)
-{
- m_ConflictsContextItem = ui->overwriteTree->itemAt(pos.x(), pos.y());
-
- QMenu menu;
- if (m_ConflictsContextItem != NULL) {
- // offer to hide/unhide file, but not for files from archives
-// if (!m_ConflictsContextItem->data(0, Qt::UserRole + 1).toBool()) {
- if (true) {
- 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)
-{
- this->close();
- emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS);
-}
-
-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);
-}
+#include "modinfodialog.h"
+#include "ui_modinfodialog.h"
+
+#include "report.h"
+#include "utility.h"
+#include "json.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>
+
+
+using QtJson::Json;
+
+
+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, 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_UTF8Codec = QTextCodec::codecForName("utf-8");
+
+ QListWidget *textFileList = findChild<QListWidget*>("textFileList");
+ QListWidget *iniFileList = findChild<QListWidget*>("iniFileList");
+ QListWidget *iniTweaksList = findChild<QListWidget*>("iniTweaksList");
+ QListWidget *activeESPList = findChild<QListWidget*>("activeESPList");
+ QListWidget *inactiveESPList = findChild<QListWidget*>("inactiveESPList");
+ QTreeWidget *categoriesTree = findChild<QTreeWidget*>("categoriesTree");
+ QHBoxLayout *thumbnailArea = findChild<QHBoxLayout*>("thumbnailArea");
+
+ m_FileTree = findChild<QTreeView*>("fileTree");
+
+ 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");
+ modIDEdit->setValidator(new QIntValidator(modIDEdit));
+ modIDEdit->setText(QString("%1").arg(modInfo->getNexusID()));
+
+ ui->notesEdit->setText(modInfo->notes());
+
+ m_FileSystemModel = new QFileSystemModel(this);
+ m_FileSystemModel->setReadOnly(false);
+ m_FileSystemModel->setRootPath(m_RootPath);
+ m_FileTree->setModel(m_FileSystemModel);
+ m_FileTree->setRootIndex(m_FileSystemModel->index(m_RootPath));
+ m_FileTree->setColumnWidth(0, 300);
+
+ connect(&m_ThumbnailMapper, SIGNAL(mapped(const QString&)), this, SIGNAL(thumbnailClickedSignal(const QString&)));
+ connect(this, SIGNAL(thumbnailClickedSignal(const QString&)), this, SLOT(thumbnailClicked(const QString&)));
+
+ m_DeleteAction = new QAction(tr("&Delete"), m_FileTree);
+ m_RenameAction = new QAction(tr("&Rename"), m_FileTree);
+ m_HideAction = new QAction(tr("&Hide"), m_FileTree);
+ m_UnhideAction = new QAction(tr("&Unhide"), m_FileTree);
+ m_OpenAction = new QAction(tr("&Open"), m_FileTree);
+ m_NewFolderAction = new QAction(tr("&New Folder"), m_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()));
+ connect(m_ModInfo.data(), SIGNAL(modDetailsUpdated(bool)), this, SLOT(modDetailsUpdated(bool)));
+
+ ui->descriptionView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks);
+ QObject::connect(ui->descriptionView, SIGNAL(linkClicked(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;
+ }
+ }
+
+ addCategories(CategoryFactory::instance(), modInfo->getCategories(), categoriesTree->invisibleRootItem(), 0);
+ refreshPrimaryCategoriesBox();
+ refreshLists();
+
+ int numTweaks = m_Settings->beginReadArray("INI Tweaks");
+ for (int i = 0; i < numTweaks; ++i) {
+ m_Settings->setArrayIndex(i);
+ QList<QListWidgetItem*> items = iniTweaksList->findItems(m_Settings->value("name").toString(), Qt::MatchFixedString);
+ if (items.size() != 0) {
+ items.at(0)->setCheckState(Qt::Checked);
+ }
+ }
+ m_Settings->endArray();
+
+ QTabWidget *tabWidget = findChild<QTabWidget*>("tabWidget");
+ tabWidget->setTabEnabled(TAB_TEXTFILES, textFileList->count() != 0);
+ tabWidget->setTabEnabled(TAB_INIFILES, (iniFileList->count() != 0) || (iniTweaksList->count() != 0));
+ tabWidget->setTabEnabled(TAB_IMAGES, thumbnailArea->count() != 0);
+ tabWidget->setTabEnabled(TAB_ESPS, (inactiveESPList->count() != 0) || (activeESPList->count() != 0));
+ tabWidget->setTabEnabled(TAB_CONFLICTS, m_Origin != NULL);
+
+ if (tabWidget->currentIndex() == TAB_NEXUS) {
+ activateNexusTab();
+ }
+
+ ui->endorseBtn->setEnabled(m_ModInfo->endorsedState() == ModInfo::ENDORSED_FALSE);
+}
+
+
+ModInfoDialog::~ModInfoDialog()
+{
+ m_ModInfo->setNotes(ui->notesEdit->toPlainText());
+ saveIniTweaks();
+ saveCategories(ui->categoriesTree->invisibleRootItem());
+
+ delete ui;
+ delete m_Settings;
+}
+
+
+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*> 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;
+ }
+ }
+ }
+
+
+ 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 (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)
+{
+ QFile textFile(fileName);
+ textFile.open(QIODevice::ReadOnly);
+ QByteArray buffer = textFile.readAll();
+ QTextCodec *codec = QTextCodec::codecForUtfText(buffer, m_UTF8Codec);
+ ui->textFileView->setText(codec->toUnicode(buffer));
+ ui->textFileView->setProperty("currentFile", fileName);
+ ui->textFileView->setProperty("encoding", codec->name());
+ ui->saveTXTButton->setEnabled(false);
+}
+
+
+void ModInfoDialog::openIniFile(const QString &fileName)
+{
+ QPushButton* saveButton = findChild<QPushButton*>("saveButton");
+
+ QFile iniFile(fileName);
+ iniFile.open(QIODevice::ReadOnly);
+ QByteArray buffer = iniFile.readAll();
+ QTextCodec *codec = QTextCodec::codecForUtfText(buffer, m_UTF8Codec);
+ QTextEdit *iniFileView = findChild<QTextEdit*>("iniFileView");
+ iniFileView->setText(codec->toUnicode(buffer));
+ iniFileView->setProperty("currentFile", fileName);
+ iniFileView->setProperty("encoding", codec->name());
+ iniFile.close();
+
+ saveButton->setEnabled(false);
+}
+
+
+void ModInfoDialog::saveIniTweaks()
+{
+ QListWidget *iniTweaksList = findChild<QListWidget*>("iniTweaksList");
+
+ m_Settings->beginWriteArray("INI Tweaks");
+
+ int countEnabled = 0;
+ for (int i = 0; i < iniTweaksList->count(); ++i) {
+ if (iniTweaksList->item(i)->checkState() == Qt::Checked) {
+ m_Settings->setArrayIndex(countEnabled++);
+ m_Settings->setValue("name", 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()) {
+ QString fileName = fileNameVar.toString();
+ 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()))) {
+ 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()) {
+ versionColor.setColor(QPalette::Text, Qt::red);
+ ui->versionEdit->setToolTip(tr("Current Version: %1").arg(m_ModInfo->getNewestVersion().canonicalString()));
+ } else {
+ 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)"));
+ }
+
+ QString version = m_ModInfo->getNewestVersion().canonicalString();
+
+ if (!version.isEmpty()) {
+ m_ModInfo->setNewestVersion(version);
+
+ updateVersionColor();
+ }
+
+ QTableWidget *filesWidget = findChild<QTableWidget*>("filesWidget");
+ filesWidget->clearContents();
+ filesWidget->setRowCount(0);
+ filesWidget->setColumnCount(5);
+#if QT_VERSION >= 0x050000
+ filesWidget->horizontalHeader()->setSectionResizeMode(1, QHeaderView::Stretch);
+#else
+ filesWidget->horizontalHeader()->setResizeMode(1, QHeaderView::Stretch);
+#endif
+ filesWidget->horizontalHeader()->setVisible(true);
+ filesWidget->setColumnHidden(4, true);
+
+
+ // prevent sorting while we change the table
+ filesWidget->setSortingEnabled(false);
+ int row = 0;
+ QList<ModInfo::NexusFileInfo>::const_iterator current, end;
+ m_ModInfo->getNexusFiles(current, end);
+ for (; current != end; ++current) {
+ filesWidget->insertRow(row);
+ int type = current->category;
+ filesWidget->setItem(row, 4, new QTableWidgetItem(QString("%1").arg(type)));
+ filesWidget->setItem(row, 0, new QTableWidgetItem(getFileCategory(type)));
+ QTableWidgetItem *nameItem = new QTableWidgetItem(current->name);
+ nameItem->setToolTip(current->description);
+ nameItem->setData(Qt::UserRole, current->id);
+ filesWidget->setItem(row, 1, nameItem);
+ filesWidget->setItem(row, 2, new QTableWidgetItem(current->version));
+ QTableWidgetItem *sizeItem = new QTableWidgetItem();
+ sizeItem->setData(Qt::DisplayRole, current->size);
+ filesWidget->setItem(row, 3, sizeItem);
+ ++row;
+ }
+ filesWidget->setSortingEnabled(true);
+ filesWidget->sortItems(4);
+ }
+}
+
+
+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())).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 (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();
+}
+
+void ModInfoDialog::on_filesWidget_doubleClicked(const QModelIndex &index)
+{
+ QTableWidget *filesWidget = findChild<QTableWidget*>("filesWidget");
+
+ QTableWidgetItem *selectedItem = filesWidget->item(index.row(), 1);
+ if (selectedItem == NULL) {
+ qDebug("no item in row %d", index.row());
+ return;
+ }
+
+ QString nexusLink = QString("nxm://%1/mods/%2/files/%3")
+ .arg(ToQString(GameInfo::instance().getGameName()))
+ .arg(m_Settings->value("modid").toInt())
+ .arg(selectedItem->data(Qt::UserRole).toInt());
+
+ try {
+ if (QuestionBoxMemory::query(this, Settings::instance().directInterface(), "confirmNexusDownload",
+ tr("Confirm"), tr("Download \"%1\"?").arg(selectedItem->text()),
+ QDialogButtonBox::Yes | QDialogButtonBox::No) == QDialogButtonBox::Yes) {
+ emit downloadRequest(nexusLink);
+ MessageDialog::showMessage(tr("Download started"), this);
+ }
+ } catch (const std::exception &e) {
+ reportError(tr("Exception: %1").arg(e.what()));
+ }
+}
+
+
+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;
+ }
+
+ m_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;
+ }
+
+ m_FileTree->setCurrentIndex(newIndex);
+ m_FileTree->edit(newIndex);
+}
+
+
+void ModInfoDialog::on_fileTree_customContextMenuRequested(const QPoint &pos)
+{
+ QItemSelectionModel *selectionModel = m_FileTree->selectionModel();
+ m_FileSelection = selectionModel->selectedRows(0);
+
+// m_FileSelection = m_FileTree->indexAt(pos);
+ QMenu menu(m_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(m_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());
+
+ QMenu menu;
+ if (m_ConflictsContextItem != NULL) {
+ // offer to hide/unhide file, but not for files from archives
+// if (!m_ConflictsContextItem->data(0, Qt::UserRole + 1).toBool()) {
+ if (!m_ConflictsContextItem->data(0, Qt::UserRole + 2).toBool()) {
+ 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)
+{
+ this->close();
+ emit modOpen(item->data(1, Qt::UserRole).toString(), TAB_CONFLICTS);
+}
+
+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);
+}
diff --git a/src/modinfodialog.h b/src/modinfodialog.h
index 8c91b74d..07aa8e84 100644
--- a/src/modinfodialog.h
+++ b/src/modinfodialog.h
@@ -17,201 +17,202 @@ 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 <directoryentry.h>
-
-#include <QDialog>
-#include <QSignalMapper>
-#include <QSettings>
-#include <QNetworkAccessManager>
-#include <QNetworkReply>
-#include <QModelIndex>
-#include <QAction>
-#include <QListWidgetItem>
-#include <QTreeWidgetItem>
-#include <QTextCodec>
-#include <set>
-
-
-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 TutorableDialog
-{
- Q_OBJECT
-
-public:
-
- enum ETabs {
- TAB_TEXTFILES,
- TAB_INIFILES,
- TAB_IMAGES,
- TAB_ESPS,
- TAB_CONFLICTS,
- TAB_CATEGORIES,
- TAB_NEXUS,
- TAB_FILETREE
- };
-
-public:
-
- /**
- * @brief constructor
- *
- * @param modInfo info structure about the mod to display
- * @param parent parend widget
- **/
- explicit ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, 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);
-
-signals:
-
- void thumbnailClickedSignal(const QString &filename);
- void nexusLinkActivated(const QString &link);
- void downloadRequest(const QString &link);
- void modOpen(const QString &modName, int tab);
- void originModified(int originID);
- void endorseMod(ModInfo::Ptr nexusID);
-
-public slots:
-
- void modDetailsUpdated(bool success);
-
-private:
-
- 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();
-
-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_filesWidget_doubleClicked(const QModelIndex &index);
- 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();
-
-private:
-
- Ui::ModInfoDialog *ui;
-
- ModInfo::Ptr m_ModInfo;
-
- 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 DirectoryEntry *m_Directory;
- FilesOrigin *m_Origin;
- QTextCodec *m_UTF8Codec;
-
-};
-
-#endif // MODINFODIALOG_H
+#ifndef MODINFODIALOG_H
+#define MODINFODIALOG_H
+
+
+#include "modinfo.h"
+#include "categories.h"
+#include "tutorabledialog.h"
+#include <directoryentry.h>
+
+#include <QDialog>
+#include <QSignalMapper>
+#include <QSettings>
+#include <QNetworkAccessManager>
+#include <QNetworkReply>
+#include <QModelIndex>
+#include <QAction>
+#include <QListWidgetItem>
+#include <QTreeWidgetItem>
+#include <QTextCodec>
+#include <set>
+
+
+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 TutorableDialog
+{
+ Q_OBJECT
+
+public:
+
+ enum ETabs {
+ TAB_TEXTFILES,
+ TAB_INIFILES,
+ TAB_IMAGES,
+ TAB_ESPS,
+ TAB_CONFLICTS,
+ TAB_CATEGORIES,
+ TAB_NEXUS,
+ TAB_FILETREE
+ };
+
+public:
+
+ /**
+ * @brief constructor
+ *
+ * @param modInfo info structure about the mod to display
+ * @param parent parend widget
+ **/
+ explicit ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directory, 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);
+
+signals:
+
+ void thumbnailClickedSignal(const QString &filename);
+ void nexusLinkActivated(const QString &link);
+ void downloadRequest(const QString &link);
+ void modOpen(const QString &modName, int tab);
+ void originModified(int originID);
+ void endorseMod(ModInfo::Ptr nexusID);
+
+public slots:
+
+ void modDetailsUpdated(bool success);
+
+private:
+
+ 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();
+
+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_filesWidget_doubleClicked(const QModelIndex &index);
+ 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();
+
+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 DirectoryEntry *m_Directory;
+ FilesOrigin *m_Origin;
+ QTextCodec *m_UTF8Codec;
+
+};
+
+#endif // MODINFODIALOG_H
diff --git a/src/modlist.cpp b/src/modlist.cpp
index b1d0caf8..aac913c5 100644
--- a/src/modlist.cpp
+++ b/src/modlist.cpp
@@ -17,655 +17,652 @@ 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 "utility.h"
-#include "messagedialog.h"
-#include "installationtester.h"
-#include <gameinfo.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>
-
-
-ModList::ModList(QObject *parent)
- : QAbstractItemModel(parent), m_Profile(NULL), m_Modified(false),
- m_FontMetrics(QFont())
-{
-// setSupportedDragActions(Qt::MoveAction);
-}
-
-
-void ModList::setProfile(Profile *profile)
-{
- m_Profile = profile;
-}
-
-
-int ModList::rowCount(const QModelIndex &parent) const
-{
- if (!parent.isValid()) {
- return ModInfo::getNumMods();
- } else {
- return 0;
- }
-}
-
-
-int ModList::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 tr("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();
- 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) {
- QString version = modInfo->getVersion().canonicalString();
- 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 {
- if ((m_Profile->getModPriority(modIndex) == 0) && (role == Qt::DisplayRole)) {
- return tr("min");
- } else if ((m_Profile->getModPriority(modIndex) == m_Profile->numRegularMods() - 1) &&
- (role == Qt::DisplayRole)) {
- return tr("max");
- } else {
- //return QString::number(m_Profile->getModPriority(modIndex));
- 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) {
- int category = modInfo->getPrimaryCategory();
- if (category != -1) {
- CategoryFactory &categoryFactory = CategoryFactory::instance();
- try {
- return categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(category));
- } catch (const std::exception &e) {
- qCritical("failed to retrieve category name: %s", e.what());
- return QString();
- }
- } else {
- //return tr("None");
- 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) {
- return modInfo->getNexusID();
- } else if (role == Qt::FontRole) {
- QFont result;
- if (column == COL_NAME) {
- if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_INVALID) {
- result.setItalic(true);
- }
- } else if (column == COL_VERSION) {
- if (modInfo->updateAvailable()) {
- result.setWeight(QFont::Bold);
- }
- }
- return result;
- } else if (role == Qt::DecorationRole) {
- if ((column == COL_VERSION) &&
- modInfo->updateAvailable() &&
- modInfo->getNewestVersion().isValid()) {
- return QIcon(":/MO/gui/update_available");
- }
- 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()) {
- 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) {
- return tr("installed version: %1, newest version: %2").arg(modInfo->getVersion().canonicalString()).arg(modInfo->getNewestVersion().canonicalString());
- } 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)
-{
- // 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();
- modInfo->setName(newName);
- // this just broke all profiles! The recipient of modRenamed has to do some magic
- // to can't write the currently active profile back
- emit modRenamed(oldName, newName);
-
- // 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;
- }
-
- if (role == Qt::CheckStateRole) {
- bool enabled = value.toInt() == Qt::Checked;
- if (m_Profile->modEnabled(index.row()) != enabled) {
- m_Profile->setModEnabled(index.row(), enabled);
- m_Modified = true;
-
- emit modlist_changed(index.row());
- }
- return true;
- } else if (role == Qt::EditRole) {
- switch (index.column()) {
- case COL_NAME: {
- return renameMod(index.row(), value.toString());
- } break;
- case COL_PRIORITY: {
- bool ok = false;
- int newPriority = value.toInt(&ok);
- if (ok) {
- m_Profile->setModPriority(index.row(), newPriority);
-
- emit modlist_changed(index.row());
- return true;
- } else {
- return false;
- }
- } break;
- case COL_MODID: {
- ModInfo::Ptr info = ModInfo::getByIndex(index.row());
- bool ok = false;
- int newID = value.toInt(&ok);
- if (ok) {
- info->setNexusID(newID);
- emit modlist_changed(index.row());
- return true;
- } else {
- return false;
- }
- } break;
- case COL_VERSION: {
- ModInfo::Ptr info = ModInfo::getByIndex(index.row());
- VersionInfo version(value.toString());
- if (version.isValid()) {
- info->setVersion(version);
- return true;
- } else {
- return false;
- }
- } break;
- default: {
- qWarning("edit on column \"%s\" not supported",
- getColumnName(index.column()).toUtf8().constData());
- return false;
- } break;
- }
- } else {
- return false;
- }
-}
-
-
-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;
- }
- }
- } else {
- result |= Qt::ItemIsDropEnabled;
- }
- 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) {
- m_Profile->setModPriority(*iter, 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();
-}
-
-
-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;
-
- 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;
-}
-
-
-void ModList::removeRowForce(int row)
-{
- if (static_cast<unsigned int>(row) >= ModInfo::getNumMods()) {
- return;
- }
- if (m_Profile == NULL) return;
-
- 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());
- }
-}
-
-
-void ModList::removeRow(int row, const QModelIndex&)
-{
- if (static_cast<unsigned int>(row) >= ModInfo::getNumMods()) {
- return;
- }
- if (m_Profile == NULL) return;
-
- ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
-
- 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 row)
-{
- if (row < 0) {
- beginResetModel();
- endResetModel();
- } else {
- emit dataChanged(this->index(row, 0), this->index(row, this->columnCount() - 1));
- }
-}
-
-
-QModelIndex ModList::index(int row, int column, const QModelIndex&) const
-{
- return createIndex(row, column, row);
-}
-
-
-QModelIndex ModList::parent(const QModelIndex&) const
-{
- return QModelIndex();
-}
-
-
-QString ModList::getColumnName(int column)
-{
- switch (column) {
- case COL_FLAGS: return tr("Flags");
- case COL_NAME: return tr("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");
- 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.");
- 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) {
- 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 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_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) {
- emit removeSelectedMods();
-/* QItemSelectionModel *selectionModel = itemView->selectionModel();
- const QSortFilterProxyModel *proxyModel = qobject_cast<const QSortFilterProxyModel*>(selectionModel->model());
- QModelIndexList rows = selectionModel->selectedRows();
- foreach (QModelIndex idx, rows) {
- if (proxyModel != NULL) {
- idx = proxyModel->mapToSource(idx);
- }
- removeRow(idx.row(), QModelIndex());
- }*/
- return true;
- }
- }
- return QObject::eventFilter(obj, event);
-}
+#include "modlist.h"
+
+#include "report.h"
+#include "utility.h"
+#include "messagedialog.h"
+#include "installationtester.h"
+#include <gameinfo.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>
+
+
+ModList::ModList(QObject *parent)
+ : QAbstractItemModel(parent), m_Profile(NULL), m_Modified(false),
+ m_FontMetrics(QFont())
+{
+// setSupportedDragActions(Qt::MoveAction);
+}
+
+
+void ModList::setProfile(Profile *profile)
+{
+ m_Profile = profile;
+}
+
+
+int ModList::rowCount(const QModelIndex &parent) const
+{
+ if (!parent.isValid()) {
+ return ModInfo::getNumMods();
+ } else {
+ return 0;
+ }
+}
+
+
+int ModList::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 tr("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();
+ 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) {
+ QString version = modInfo->getVersion().canonicalString();
+ 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 {
+ if ((m_Profile->getModPriority(modIndex) == 0) && (role == Qt::DisplayRole)) {
+ return tr("min");
+ } else if ((m_Profile->getModPriority(modIndex) == m_Profile->numRegularMods() - 1) &&
+ (role == Qt::DisplayRole)) {
+ return tr("max");
+ } else {
+ //return QString::number(m_Profile->getModPriority(modIndex));
+ 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) {
+ int category = modInfo->getPrimaryCategory();
+ if (category != -1) {
+ CategoryFactory &categoryFactory = CategoryFactory::instance();
+ try {
+ return categoryFactory.getCategoryName(categoryFactory.getCategoryIndex(category));
+ } catch (const std::exception &e) {
+ qCritical("failed to retrieve category name: %s", e.what());
+ return QString();
+ }
+ } else {
+ //return tr("None");
+ 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) {
+ return modInfo->getNexusID();
+ } else if (role == Qt::FontRole) {
+ QFont result;
+ if (column == COL_NAME) {
+ if (modInfo->getHighlight() & ModInfo::HIGHLIGHT_INVALID) {
+ result.setItalic(true);
+ }
+ } else if (column == COL_VERSION) {
+ if (modInfo->updateAvailable()) {
+ result.setWeight(QFont::Bold);
+ }
+ }
+ return result;
+ } else if (role == Qt::DecorationRole) {
+ if ((column == COL_VERSION) &&
+ modInfo->updateAvailable() &&
+ modInfo->getNewestVersion().isValid()) {
+ return QIcon(":/MO/gui/update_available");
+ }
+ 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()) {
+ 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) {
+ return tr("installed version: %1, newest version: %2").arg(modInfo->getVersion().canonicalString()).arg(modInfo->getNewestVersion().canonicalString());
+ } 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)
+{
+ // 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();
+ modInfo->setName(newName);
+ // this just broke all profiles! The recipient of modRenamed has to do some magic
+ // to can't write the currently active profile back
+ emit modRenamed(oldName, newName);
+
+ // 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;
+ }
+
+ if (role == Qt::CheckStateRole) {
+ bool enabled = value.toInt() == Qt::Checked;
+ if (m_Profile->modEnabled(index.row()) != enabled) {
+ m_Profile->setModEnabled(index.row(), enabled);
+ m_Modified = true;
+
+ emit modlist_changed(index.row());
+ }
+ return true;
+ } else if (role == Qt::EditRole) {
+ switch (index.column()) {
+ case COL_NAME: {
+ return renameMod(index.row(), value.toString());
+ } break;
+ case COL_PRIORITY: {
+ bool ok = false;
+ int newPriority = value.toInt(&ok);
+ if (ok) {
+ m_Profile->setModPriority(index.row(), newPriority);
+
+ emit modlist_changed(index.row());
+ return true;
+ } else {
+ return false;
+ }
+ } break;
+ case COL_MODID: {
+ ModInfo::Ptr info = ModInfo::getByIndex(index.row());
+ bool ok = false;
+ int newID = value.toInt(&ok);
+ if (ok) {
+ info->setNexusID(newID);
+ emit modlist_changed(index.row());
+ return true;
+ } else {
+ return false;
+ }
+ } break;
+ case COL_VERSION: {
+ ModInfo::Ptr info = ModInfo::getByIndex(index.row());
+ VersionInfo version(value.toString());
+ if (version.isValid()) {
+ info->setVersion(version);
+ return true;
+ } else {
+ return false;
+ }
+ } break;
+ default: {
+ qWarning("edit on column \"%s\" not supported",
+ getColumnName(index.column()).toUtf8().constData());
+ return false;
+ } break;
+ }
+ } else {
+ return false;
+ }
+}
+
+
+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;
+ }
+ }
+ } else {
+ result |= Qt::ItemIsDropEnabled;
+ }
+ 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) {
+ m_Profile->setModPriority(*iter, 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();
+}
+
+
+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;
+
+ 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;
+}
+
+
+void ModList::removeRowForce(int row)
+{
+ if (static_cast<unsigned int>(row) >= ModInfo::getNumMods()) {
+ return;
+ }
+ if (m_Profile == NULL) return;
+
+ 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());
+ }
+}
+
+
+void ModList::removeRow(int row, const QModelIndex&)
+{
+ if (static_cast<unsigned int>(row) >= ModInfo::getNumMods()) {
+ return;
+ }
+ if (m_Profile == NULL) return;
+
+ ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
+
+ 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 row)
+{
+ if (row < 0) {
+ beginResetModel();
+ endResetModel();
+ } else {
+ emit dataChanged(this->index(row, 0), this->index(row, this->columnCount() - 1));
+ }
+}
+
+
+QModelIndex ModList::index(int row, int column, const QModelIndex&) const
+{
+ return createIndex(row, column, row);
+}
+
+
+QModelIndex ModList::parent(const QModelIndex&) const
+{
+ return QModelIndex();
+}
+
+
+QString ModList::getColumnName(int column)
+{
+ switch (column) {
+ case COL_FLAGS: return tr("Flags");
+ case COL_NAME: return tr("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");
+ 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.");
+ 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) {
+ 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 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_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].row(), QModelIndex());
+ }
+ return true;
+ }
+ }
+ return QObject::eventFilter(obj, event);
+}
diff --git a/src/modlistsortproxy.cpp b/src/modlistsortproxy.cpp
index de1e3230..bf70f238 100644
--- a/src/modlistsortproxy.cpp
+++ b/src/modlistsortproxy.cpp
@@ -17,213 +17,218 @@ 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(CategoryFactory::CATEGORY_NONE), m_CurrentFilter()
-{
- 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::setCategoryFilter(int category)
-{
- m_CategoryFilter = category;
- 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)).row();
- m_Profile->setModEnabled(modID, true);
- }
-}
-
-
-void ModListSortProxy::disableAllVisible()
-{
- if (m_Profile == NULL) return;
- for (int i = 0; i < this->rowCount(); ++i) {
- int modID = mapToSource(index(i, 0)).row();
- m_Profile->setModEnabled(modID, false);
- }
-}
-
-
-bool ModListSortProxy::lessThan(const QModelIndex &left,
- const QModelIndex &right) const
-{
- int leftIndex = left.internalId();
- int rightIndex = right.internalId();
- ModInfo::Ptr leftMod = ModInfo::getByIndex(leftIndex);
- ModInfo::Ptr rightMod = ModInfo::getByIndex(rightIndex);
-
- bool lt = false;
- switch (left.column()) {
- case ModList::COL_FLAGS: lt = leftMod->getFlags().size() < rightMod->getFlags().size(); break;
- case ModList::COL_NAME: lt = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive) < 0; break;
- case ModList::COL_CATEGORY: {
- 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: lt = leftMod->getNexusID() < rightMod->getNexusID(); break;
- case ModList::COL_VERSION: lt = leftMod->getVersion() < rightMod->getVersion(); break;
- case ModList::COL_PRIORITY: {
- if (m_Profile != NULL) {
- int leftPrio = leftMod->getFixedPriority();
- int rightPrio = rightMod->getFixedPriority();
- if (leftPrio == INT_MIN) leftPrio = m_Profile->getModPriority(leftIndex);
- if (rightPrio == INT_MIN) rightPrio = m_Profile->getModPriority(rightIndex);
-
- lt = leftPrio < rightPrio;
- } else {
- lt = leftMod->name() < rightMod->name();
- }
- } break;
- }
- return lt;
-}
-
-
-void ModListSortProxy::updateFilter(const QString &filter)
-{
- m_CurrentFilter = filter;
- 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::filterAcceptsRow(int row, const QModelIndex&) const
-{
- ModInfo::Ptr modInfo = ModInfo::getByIndex(row);
-
- if (!m_CurrentFilter.isEmpty() &&
- !modInfo->name().contains(m_CurrentFilter, Qt::CaseInsensitive)) {
- return false;
- }
-
- bool modEnabled = m_Profile != NULL ? m_Profile->modEnabled(row) : false;
- return ((m_CategoryFilter == CategoryFactory::CATEGORY_NONE) ||
- ((m_CategoryFilter < CategoryFactory::CATEGORY_SPECIAL_FIRST) && (modInfo->categorySet(m_CategoryFilter))) ||
- ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_CHECKED) && modEnabled) ||
- ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_UNCHECKED) && !modEnabled) ||
- ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) && modInfo->updateAvailable()) ||
- ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY) && (modInfo->getCategories().size() == 0)) ||
- ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_CONFLICT) && (hasConflictFlag(modInfo->getFlags()))));
-}
-
-
-/*bool ModListSortProxy::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const
-{
- return m_EnabledColumns.test(source_column);
-}*/
-
-
-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());
-}
+#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(CategoryFactory::CATEGORY_NONE), m_CurrentFilter()
+{
+ 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::setCategoryFilter(int category)
+{
+ m_CategoryFilter = category;
+ 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)).row();
+ m_Profile->setModEnabled(modID, true);
+ }
+}
+
+
+void ModListSortProxy::disableAllVisible()
+{
+ if (m_Profile == NULL) return;
+ for (int i = 0; i < this->rowCount(); ++i) {
+ int modID = mapToSource(index(i, 0)).row();
+ m_Profile->setModEnabled(modID, false);
+ }
+}
+
+
+bool ModListSortProxy::lessThan(const QModelIndex &left,
+ const QModelIndex &right) const
+{
+ int leftIndex = left.internalId();
+ int rightIndex = right.internalId();
+ ModInfo::Ptr leftMod = ModInfo::getByIndex(leftIndex);
+ ModInfo::Ptr rightMod = ModInfo::getByIndex(rightIndex);
+
+ bool lt = false;
+ switch (left.column()) {
+ case ModList::COL_FLAGS: lt = leftMod->getFlags().size() < rightMod->getFlags().size(); break;
+ case ModList::COL_NAME: lt = QString::compare(leftMod->name(), rightMod->name(), Qt::CaseInsensitive) < 0; break;
+ case ModList::COL_CATEGORY: {
+ 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: lt = leftMod->getNexusID() < rightMod->getNexusID(); break;
+ case ModList::COL_VERSION: lt = leftMod->getVersion() < rightMod->getVersion(); break;
+ case ModList::COL_PRIORITY: {
+ if (m_Profile != NULL) {
+ int leftPrio = leftMod->getFixedPriority();
+ int rightPrio = rightMod->getFixedPriority();
+ if (leftPrio == INT_MIN) leftPrio = m_Profile->getModPriority(leftIndex);
+ if (rightPrio == INT_MIN) rightPrio = m_Profile->getModPriority(rightIndex);
+
+ lt = leftPrio < rightPrio;
+ } else {
+ lt = leftMod->name() < rightMod->name();
+ }
+ } break;
+ }
+ return lt;
+}
+
+
+void ModListSortProxy::updateFilter(const QString &filter)
+{
+ m_CurrentFilter = filter;
+ 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::filterMatches(ModInfo::Ptr info, bool enabled) const
+{
+ if (!m_CurrentFilter.isEmpty() &&
+ !info->name().contains(m_CurrentFilter, Qt::CaseInsensitive)) {
+ return false;
+ }
+
+ return ((m_CategoryFilter == CategoryFactory::CATEGORY_NONE) ||
+ ((m_CategoryFilter < CategoryFactory::CATEGORY_SPECIAL_FIRST) && (info->categorySet(m_CategoryFilter))) ||
+ ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_CHECKED) && enabled) ||
+ ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_UNCHECKED) && !enabled) ||
+ ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_UPDATEAVAILABLE) && info->updateAvailable()) ||
+ ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_NOCATEGORY) && (info->getCategories().size() == 0)) ||
+ ((m_CategoryFilter == CategoryFactory::CATEGORY_SPECIAL_CONFLICT) && (hasConflictFlag(info->getFlags()))));
+}
+
+
+bool ModListSortProxy::filterAcceptsRow(int row, const QModelIndex&) const
+{
+ bool modEnabled = m_Profile != NULL ? m_Profile->modEnabled(row) : false;
+
+ return filterMatches(ModInfo::getByIndex(row), modEnabled);
+}
+
+
+/*bool ModListSortProxy::filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const
+{
+ return m_EnabledColumns.test(source_column);
+}*/
+
+
+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 aad87f06..f7a9b587 100644
--- a/src/modlistsortproxy.h
+++ b/src/modlistsortproxy.h
@@ -17,64 +17,66 @@ 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:
-
- explicit ModListSortProxy(Profile *profile, QObject *parent = 0);
-
- void setProfile(Profile *profile);
-
- void setCategoryFilter(int category);
-
- 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();
-
-public slots:
-
- void displayColumnSelection(const QPoint &pos);
- void updateFilter(const QString &filter);
-
-protected:
-
- virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
- virtual bool filterAcceptsRow(int row, const QModelIndex &parent) const;
-// virtual bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const;
-
-private:
-
- bool hasConflictFlag(const std::vector<ModInfo::EFlag> &flags) const;
-
-private:
-
- Profile *m_Profile;
-
- int m_CategoryFilter;
- std::bitset<ModList::COL_LASTCOLUMN + 1> m_EnabledColumns;
- QString m_CurrentFilter;
-
-};
-
-#endif // MODLISTSORTPROXY_H
+#ifndef MODLISTSORTPROXY_H
+#define MODLISTSORTPROXY_H
+
+#include <QSortFilterProxyModel>
+#include <bitset>
+#include "modlist.h"
+
+class Profile;
+
+class ModListSortProxy : public QSortFilterProxyModel
+{
+ Q_OBJECT
+
+public:
+
+ explicit ModListSortProxy(Profile *profile, QObject *parent = 0);
+
+ void setProfile(Profile *profile);
+
+ void setCategoryFilter(int category);
+
+ 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();
+
+ bool filterMatches(ModInfo::Ptr info, bool enabled) const;
+
+public slots:
+
+ void displayColumnSelection(const QPoint &pos);
+ void updateFilter(const QString &filter);
+
+protected:
+
+ virtual bool lessThan(const QModelIndex &left, const QModelIndex &right) const;
+ virtual bool filterAcceptsRow(int row, const QModelIndex &parent) const;
+// virtual bool filterAcceptsColumn(int source_column, const QModelIndex &source_parent) const;
+
+private:
+
+ bool hasConflictFlag(const std::vector<ModInfo::EFlag> &flags) const;
+
+private:
+
+ Profile *m_Profile;
+
+ int m_CategoryFilter;
+ std::bitset<ModList::COL_LASTCOLUMN + 1> m_EnabledColumns;
+ QString m_CurrentFilter;
+
+};
+
+#endif // MODLISTSORTPROXY_H
diff --git a/src/nexusdialog.cpp b/src/nexusdialog.cpp
index 770b740c..7046cdd7 100644
--- a/src/nexusdialog.cpp
+++ b/src/nexusdialog.cpp
@@ -17,307 +17,317 @@ 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 "nexusdialog.h"
-#include "ui_nexusdialog.h"
-
-#include "messagedialog.h"
-#include "report.h"
-#include "json.h"
-
-#include <utility.h>
-#include <gameinfo.h>
-#include <QNetworkCookieJar>
-#include <QNetworkCookie>
-#include <QMenu>
-#include <QInputDialog>
-#include <QWebHistory>
-
-
-NexusDialog::NexusDialog(NXMAccessManager *accessManager, QWidget *parent)
- : QDialog(parent), ui(new Ui::NexusDialog),
- m_AccessManager(accessManager),
- m_Tutorial(this, "NexusDialog")
-{
- ui->setupUi(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");
-
- m_View = new NexusView(this);
-
- initTab(m_View);
-
- m_LoadProgress = this->findChild<QProgressBar*>("loadProgress");
-
- connect(m_Tabs, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int)));
-
- m_Tutorial.registerControl();
-}
-
-
-NexusDialog::~NexusDialog()
-{
-
- delete ui;
-}
-
-void NexusDialog::closeEvent(QCloseEvent *event)
-{
-// m_AccessManager->showCookies();
- QDialog::closeEvent(event);
-}
-
-void NexusDialog::initTab(NexusView *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(NexusView*)), this, SLOT(initTab(NexusView*)));
- 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*)));
- connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(requestNXMDownload(QString)));
-
- ui->backBtn->setEnabled(false);
- ui->fwdBtn->setEnabled(false);
- m_Tabs->addTab(newView, tr("new"));
-
- m_View->settings()->setAttribute(QWebSettings::PluginsEnabled, true);
- m_View->settings()->setAttribute(QWebSettings::AutoLoadImages, true);
-}
-
-
-void NexusDialog::openInNewTab(const QUrl &url)
-{
- NexusView *newView = new NexusView(this);
-
- initTab(newView);
- newView->setUrl(url);
-}
-
-
-NexusView *NexusDialog::getCurrentView()
-{
- return qobject_cast<NexusView*>(m_Tabs->currentWidget());
-}
-
-
-void NexusDialog::urlChanged(const QUrl&)
-{
- NexusView *currentView = getCurrentView();
- if (currentView != NULL) {
- ui->backBtn->setEnabled(currentView->history()->canGoBack());
- ui->fwdBtn->setEnabled(currentView->history()->canGoForward());
- }
-}
-
-
-void NexusDialog::openUrl(const QString &url)
-{
- m_Url = url;
- if (m_Url.startsWith("www")) {
- m_Url.prepend("http://");
- }
-}
-
-void NexusDialog::login(const QString &username, const QString &password)
-{
- m_AccessManager->login(username, password);
-
- connect(m_AccessManager, SIGNAL(loginSuccessful(bool)), this, SLOT(loginFinished(bool)));
- connect(m_AccessManager, SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString)));
-}
-
-
-void NexusDialog::loginFailed(const QString &message)
-{
- if (this->isVisible()) {
- MessageDialog::showMessage(tr("login failed: %1").arg(message), this);
- }
-}
-
-
-void NexusDialog::loginFinished(bool necessary)
-{
- if (necessary && this->isVisible()) {
- MessageDialog::showMessage(tr("login successful"), this);
- }
- loadNexus();
- emit loginSuccessful(necessary);
-}
-
-
-void NexusDialog::loadNexus()
-{
- m_View->load(QUrl(m_Url));
-}
-
-
-void NexusDialog::progress(int value)
-{
- m_LoadProgress->setValue(value);
- m_LoadProgress->setVisible(value != 100);
-}
-
-
-void NexusDialog::titleChanged(const QString &title)
-{
- NexusView *view = qobject_cast<NexusView*>(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 NexusDialog::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 NexusDialog::unsupportedContent(QNetworkReply *reply)
-{
- try {
- QWebPage *page = qobject_cast<QWebPage*>(sender());
- if (page == NULL) {
- qCritical("sender not a page");
- return;
- }
- NexusView *view = qobject_cast<NexusView*>(page->view());
- if (view == NULL) {
- qCritical("no view?");
- return;
- }
-
- int modID = 0;
- QString fileName = guessFileName(reply->url().toString());
- qDebug("unsupported: %s - %s", view->url().toString().toUtf8().constData(), reply->url().toString().toUtf8().constData());
- QRegExp sourceExp(QString("http://[a-zA-Z0-9.]*.nexusmods.com/.*"), Qt::CaseInsensitive);
- QRegExp modidExp(QString("http://([a-zA-Z0-9]*).nexusmods.com/downloads/file.php\\?id=(\\d+)"), Qt::CaseInsensitive);
- QRegExp modidAltExp(QString("http://([a-zA-Z0-9]*).nexusmods.com/mods/(\\d+)"), Qt::CaseInsensitive);
- if (sourceExp.indexIn(reply->url().toString()) != -1) {
- if (modidExp.indexIn(view->url().toString()) != -1) {
- modID = modidExp.cap(2).toInt();
- } else if (modidAltExp.indexIn(view->url().toString()) != -1) {
- modID = modidAltExp.cap(2).toInt();
- } else {
- modID = view->getLastSeenModID();
- }
- } else {
- qDebug("not a nexus download: %s", reply->url().toString().toUtf8().constData());
- return;
- }
- emit requestDownload(reply, modID, fileName);
- } catch (const std::exception &e) {
- if (isVisible()) {
- MessageDialog::showMessage(tr("failed to start download"), this);
- }
- qCritical("exception downloading unsupported content: %s", e.what());
- }
-}
-
-
-void NexusDialog::downloadRequested(const QNetworkRequest &request)
-{
- qCritical("download request %s ignored", request.url().toString().toUtf8().constData());
-}
-
-
-void NexusDialog::requestNXMDownload(const QString&)
-{
- if (isVisible()) {
- MessageDialog::showMessage(tr("Download started"), this);
- }
-}
-
-
-void NexusDialog::tabCloseRequested(int index)
-{
- if (m_Tabs->count() == 1) {
- this->close();
- } else {
- m_Tabs->widget(index)->deleteLater();
- m_Tabs->removeTab(index);
- }
-}
-
-
-void NexusDialog::on_browserTabWidget_customContextMenuRequested(const QPoint&)
-{
-}
-
-void NexusDialog::on_backBtn_clicked()
-{
- NexusView *currentView = getCurrentView();
- if (currentView != NULL) {
- currentView->back();
- }
-}
-
-void NexusDialog::on_fwdBtn_clicked()
-{
- NexusView *currentView = getCurrentView();
- if (currentView != NULL) {
- currentView->forward();
- }
-}
-
-
-void NexusDialog::startSearch()
-{
- ui->searchEdit->setFocus();
-}
-
-
-void NexusDialog::on_modIDEdit_returnPressed()
-{
- QString url = ToQString(GameInfo::instance().getNexusPage()).append("/downloads/file.php?id=%1").arg(ui->modIDEdit->text());
- NexusView *currentView = getCurrentView();
- if (currentView != NULL) {
- currentView->load(QUrl(url));
- }
-}
-
-void NexusDialog::on_searchEdit_returnPressed()
-{
- NexusView *currentView = getCurrentView();
- if (currentView != NULL) {
- currentView->findText(ui->searchEdit->text(), QWebPage::FindWrapsAroundDocument);
- }
-}
-
-void NexusDialog::on_browserTabWidget_currentChanged(QWidget *current)
-{
- NexusView *currentView = qobject_cast<NexusView*>(current);
- if (currentView != NULL) {
- ui->backBtn->setEnabled(currentView->history()->canGoBack());
- ui->fwdBtn->setEnabled(currentView->history()->canGoForward());
- }
-}
-
-void NexusDialog::on_refreshBtn_clicked()
-{
- getCurrentView()->reload();
-}
+#include "nexusdialog.h"
+#include "ui_nexusdialog.h"
+
+#include "messagedialog.h"
+#include "report.h"
+#include "json.h"
+
+#include <utility.h>
+#include <gameinfo.h>
+#include <QNetworkCookieJar>
+#include <QNetworkCookie>
+#include <QMenu>
+#include <QInputDialog>
+#include <QWebHistory>
+
+
+NexusDialog::NexusDialog(NXMAccessManager *accessManager, QWidget *parent)
+ : QDialog(parent), ui(new Ui::NexusDialog),
+ m_ModUrlExp(QString("http://([a-zA-Z0-9]*).nexusmods.com/mods/(\\d+)"), Qt::CaseInsensitive),
+ m_AccessManager(accessManager),
+ m_Tutorial(this, "NexusDialog")
+{
+ ui->setupUi(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");
+
+ m_View = new NexusView(this);
+
+ initTab(m_View);
+
+ m_LoadProgress = this->findChild<QProgressBar*>("loadProgress");
+
+ connect(m_Tabs, SIGNAL(tabCloseRequested(int)), this, SLOT(tabCloseRequested(int)));
+ m_Tutorial.registerControl();
+}
+
+
+NexusDialog::~NexusDialog()
+{
+
+ delete ui;
+}
+
+void NexusDialog::closeEvent(QCloseEvent *event)
+{
+// m_AccessManager->showCookies();
+ QDialog::closeEvent(event);
+}
+
+void NexusDialog::initTab(NexusView *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(NexusView*)), this, SLOT(initTab(NexusView*)));
+ 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*)));
+ connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(requestNXMDownload(QString)));
+
+ ui->backBtn->setEnabled(false);
+ ui->fwdBtn->setEnabled(false);
+ m_Tabs->addTab(newView, tr("new"));
+
+ m_View->settings()->setAttribute(QWebSettings::PluginsEnabled, true);
+ m_View->settings()->setAttribute(QWebSettings::AutoLoadImages, true);
+}
+
+
+void NexusDialog::openInNewTab(const QUrl &url)
+{
+ NexusView *newView = new NexusView(this);
+
+ initTab(newView);
+ newView->setUrl(url);
+}
+
+
+NexusView *NexusDialog::getCurrentView()
+{
+ return qobject_cast<NexusView*>(m_Tabs->currentWidget());
+}
+
+
+void NexusDialog::urlChanged(const QUrl &url)
+{
+ NexusView *sendingView = qobject_cast<NexusView*>(sender());
+ NexusView *currentView = getCurrentView();
+ if ((m_ModUrlExp.indexIn(url.toString()) != -1) &&
+ (sendingView == currentView)) {
+ ui->modIDEdit->setText(m_ModUrlExp.cap(2));
+ }
+
+ if (currentView != NULL) {
+ ui->backBtn->setEnabled(currentView->history()->canGoBack());
+ ui->fwdBtn->setEnabled(currentView->history()->canGoForward());
+ }
+}
+
+
+void NexusDialog::openUrl(const QString &url)
+{
+ m_Url = url;
+ if (m_Url.startsWith("www")) {
+ m_Url.prepend("http://");
+ }
+}
+
+void NexusDialog::login(const QString &username, const QString &password)
+{
+ m_AccessManager->login(username, password);
+
+ connect(m_AccessManager, SIGNAL(loginSuccessful(bool)), this, SLOT(loginFinished(bool)));
+ connect(m_AccessManager, SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString)));
+}
+
+
+void NexusDialog::loginFailed(const QString &message)
+{
+ if (this->isVisible()) {
+ MessageDialog::showMessage(tr("login failed: %1").arg(message), this);
+ }
+}
+
+
+void NexusDialog::loginFinished(bool necessary)
+{
+ if (necessary && this->isVisible()) {
+ MessageDialog::showMessage(tr("login successful"), this);
+ }
+ loadNexus();
+ emit loginSuccessful(necessary);
+}
+
+
+void NexusDialog::loadNexus()
+{
+ m_View->load(QUrl(m_Url));
+}
+
+
+void NexusDialog::progress(int value)
+{
+ m_LoadProgress->setValue(value);
+ m_LoadProgress->setVisible(value != 100);
+}
+
+
+void NexusDialog::titleChanged(const QString &title)
+{
+ NexusView *view = qobject_cast<NexusView*>(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 NexusDialog::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 NexusDialog::unsupportedContent(QNetworkReply *reply)
+{
+ try {
+ QWebPage *page = qobject_cast<QWebPage*>(sender());
+ if (page == NULL) {
+ qCritical("sender not a page");
+ return;
+ }
+ NexusView *view = qobject_cast<NexusView*>(page->view());
+ if (view == NULL) {
+ qCritical("no view?");
+ return;
+ }
+
+ int modID = 0;
+ QString fileName = guessFileName(reply->url().toString());
+ qDebug("unsupported: %s - %s", view->url().toString().toUtf8().constData(), reply->url().toString().toUtf8().constData());
+ QRegExp sourceExp(QString("http://[a-zA-Z0-9.]*.nexusmods.com/.*"), Qt::CaseInsensitive);
+ QRegExp modidExp(QString("http://([a-zA-Z0-9]*).nexusmods.com/downloads/file.php\\?id=(\\d+)"), Qt::CaseInsensitive);
+ QRegExp modidAltExp(QString("http://([a-zA-Z0-9]*).nexusmods.com/mods/(\\d+)"), Qt::CaseInsensitive);
+ if (sourceExp.indexIn(reply->url().toString()) != -1) {
+ if (modidExp.indexIn(view->url().toString()) != -1) {
+ modID = modidExp.cap(2).toInt();
+ } else if (modidAltExp.indexIn(view->url().toString()) != -1) {
+ modID = modidAltExp.cap(2).toInt();
+ } else {
+ modID = view->getLastSeenModID();
+ }
+ } else {
+ qDebug("not a nexus download: %s", reply->url().toString().toUtf8().constData());
+ return;
+ }
+ emit requestDownload(reply, modID, fileName);
+ } catch (const std::exception &e) {
+ if (isVisible()) {
+ MessageDialog::showMessage(tr("failed to start download"), this);
+ }
+ qCritical("exception downloading unsupported content: %s", e.what());
+ }
+}
+
+
+void NexusDialog::downloadRequested(const QNetworkRequest &request)
+{
+ qCritical("download request %s ignored", request.url().toString().toUtf8().constData());
+}
+
+
+void NexusDialog::requestNXMDownload(const QString&)
+{
+ if (isVisible()) {
+ MessageDialog::showMessage(tr("Download started"), this);
+ }
+}
+
+
+void NexusDialog::tabCloseRequested(int index)
+{
+ if (m_Tabs->count() == 1) {
+ this->close();
+ } else {
+ m_Tabs->widget(index)->deleteLater();
+ m_Tabs->removeTab(index);
+ }
+}
+
+
+void NexusDialog::on_browserTabWidget_customContextMenuRequested(const QPoint&)
+{
+}
+
+void NexusDialog::on_backBtn_clicked()
+{
+ NexusView *currentView = getCurrentView();
+ if (currentView != NULL) {
+ currentView->back();
+ }
+}
+
+void NexusDialog::on_fwdBtn_clicked()
+{
+ NexusView *currentView = getCurrentView();
+ if (currentView != NULL) {
+ currentView->forward();
+ }
+}
+
+
+void NexusDialog::startSearch()
+{
+ ui->searchEdit->setFocus();
+}
+
+
+void NexusDialog::on_modIDEdit_returnPressed()
+{
+ QString url = ToQString(GameInfo::instance().getNexusPage()).append("/downloads/file.php?id=%1").arg(ui->modIDEdit->text());
+ NexusView *currentView = getCurrentView();
+ if (currentView != NULL) {
+ currentView->load(QUrl(url));
+ }
+}
+
+void NexusDialog::on_searchEdit_returnPressed()
+{
+ NexusView *currentView = getCurrentView();
+ if (currentView != NULL) {
+ currentView->findText(ui->searchEdit->text(), QWebPage::FindWrapsAroundDocument);
+ }
+}
+
+void NexusDialog::on_browserTabWidget_currentChanged(QWidget *current)
+{
+ NexusView *currentView = qobject_cast<NexusView*>(current);
+ if (currentView != NULL) {
+ ui->backBtn->setEnabled(currentView->history()->canGoBack());
+ ui->fwdBtn->setEnabled(currentView->history()->canGoForward());
+ }
+
+ if (m_ModUrlExp.indexIn(currentView->url().toString()) != -1) {
+ ui->modIDEdit->setText(m_ModUrlExp.cap(2));
+ }
+}
+
+void NexusDialog::on_refreshBtn_clicked()
+{
+ getCurrentView()->reload();
+}
diff --git a/src/nexusdialog.h b/src/nexusdialog.h
index 041b5e7c..fbe99f3d 100644
--- a/src/nexusdialog.h
+++ b/src/nexusdialog.h
@@ -17,144 +17,145 @@ 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 NEXUSDIALOG_H
-#define NEXUSDIALOG_H
-
-#include "nexusview.h"
-#include "nxmaccessmanager.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 NexusDialog;
-}
-
-
-/**
- * @brief a dialog containing a webbrowser that is intended to browse the nexus network
- **/
-class NexusDialog : public QDialog
-{
- Q_OBJECT
-
-public:
-
- /**
- * @brief constructor
- *
- * @param accessManager the access manager to use for network requests
- * @param parent parent widget
- **/
- explicit NexusDialog(NXMAccessManager *accessManager, QWidget *parent = 0);
- ~NexusDialog();
-
- /**
- * @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 QString &url);
-
- /**
- * @brief log-in to the nexus page.
- *
- * After successful login, loadNexus() is automatically called. If the user is already
- * logged in, this happens immediately
- *
- * @param username the user name to log in as
- * @param password the user password
- **/
- void login(const QString &username, const QString &password);
-
- /**
- * @brief load the page set with openUrl()
- **/
- void loadNexus();
-
-signals:
-
- /**
- * @brief emitted when the user caused a download
- *
- * @param reply the network-reply transmitting the file
- * @param modID mod id of the file requested
- * @param fileName suggested filename
- **/
- void requestDownload(QNetworkReply *reply, int modID, const QString &fileName);
-
- void loginSuccessful(bool necessary);
-
-protected:
-
- virtual void closeEvent(QCloseEvent *);
-
-private slots:
-
- void loginFailed(const QString &message);
- void loginFinished(bool necessary);
-// void loginError(QNetworkReply::NetworkError errorCode);
-// void loginTimeout();
-
- void initTab(NexusView *newView);
- void openInNewTab(const QUrl &url);
-
- void progress(int value);
-
- void titleChanged(const QString &title);
- void requestNXMDownload(const QString &url);
- void unsupportedContent(QNetworkReply *reply);
- void downloadRequested(const QNetworkRequest &request);
-
-
- void tabCloseRequested(int index);
-
-// void openByModID();
- void on_browserTabWidget_customContextMenuRequested(const QPoint &pos);
-
- void urlChanged(const QUrl &url);
-
- void on_backBtn_clicked();
-
- void on_fwdBtn_clicked();
-
- void on_modIDEdit_returnPressed();
-
- void on_searchEdit_returnPressed();
-
- void startSearch();
-
- void on_browserTabWidget_currentChanged(QWidget *arg1);
-
- void on_refreshBtn_clicked();
-
-private:
-
- QString guessFileName(const QString &url);
-
- NexusView *getCurrentView();
-
-private:
-
- Ui::NexusDialog *ui;
-
- TutorialControl m_Tutorial;
-
- QString m_Url;
-
- QTabWidget *m_Tabs;
- NexusView *m_View;
- QProgressBar *m_LoadProgress;
- NXMAccessManager *m_AccessManager;
-
-};
-
-#endif // NEXUSDIALOG_H
+#ifndef NEXUSDIALOG_H
+#define NEXUSDIALOG_H
+
+#include "nexusview.h"
+#include "nxmaccessmanager.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 NexusDialog;
+}
+
+
+/**
+ * @brief a dialog containing a webbrowser that is intended to browse the nexus network
+ **/
+class NexusDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+
+ /**
+ * @brief constructor
+ *
+ * @param accessManager the access manager to use for network requests
+ * @param parent parent widget
+ **/
+ explicit NexusDialog(NXMAccessManager *accessManager, QWidget *parent = 0);
+ ~NexusDialog();
+
+ /**
+ * @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 QString &url);
+
+ /**
+ * @brief log-in to the nexus page.
+ *
+ * After successful login, loadNexus() is automatically called. If the user is already
+ * logged in, this happens immediately
+ *
+ * @param username the user name to log in as
+ * @param password the user password
+ **/
+ void login(const QString &username, const QString &password);
+
+ /**
+ * @brief load the page set with openUrl()
+ **/
+ void loadNexus();
+
+signals:
+
+ /**
+ * @brief emitted when the user caused a download
+ *
+ * @param reply the network-reply transmitting the file
+ * @param modID mod id of the file requested
+ * @param fileName suggested filename
+ **/
+ void requestDownload(QNetworkReply *reply, int modID, const QString &fileName);
+
+ void loginSuccessful(bool necessary);
+
+protected:
+
+ virtual void closeEvent(QCloseEvent *);
+
+private slots:
+
+ void loginFailed(const QString &message);
+ void loginFinished(bool necessary);
+// void loginError(QNetworkReply::NetworkError errorCode);
+// void loginTimeout();
+
+ void initTab(NexusView *newView);
+ void openInNewTab(const QUrl &url);
+
+ void progress(int value);
+
+ void titleChanged(const QString &title);
+ void requestNXMDownload(const QString &url);
+ void unsupportedContent(QNetworkReply *reply);
+ void downloadRequested(const QNetworkRequest &request);
+
+
+ void tabCloseRequested(int index);
+
+// void openByModID();
+ void on_browserTabWidget_customContextMenuRequested(const QPoint &pos);
+
+ void urlChanged(const QUrl &url);
+
+ void on_backBtn_clicked();
+
+ void on_fwdBtn_clicked();
+
+ void on_modIDEdit_returnPressed();
+
+ void on_searchEdit_returnPressed();
+
+ void startSearch();
+
+ void on_browserTabWidget_currentChanged(QWidget *arg1);
+
+ void on_refreshBtn_clicked();
+
+private:
+
+ QString guessFileName(const QString &url);
+
+ NexusView *getCurrentView();
+
+private:
+
+ Ui::NexusDialog *ui;
+
+ TutorialControl m_Tutorial;
+
+ QString m_Url;
+ QRegExp m_ModUrlExp;
+
+ QTabWidget *m_Tabs;
+ NexusView *m_View;
+ QProgressBar *m_LoadProgress;
+ NXMAccessManager *m_AccessManager;
+
+};
+
+#endif // NEXUSDIALOG_H
diff --git a/src/nexusinterface.cpp b/src/nexusinterface.cpp
index c250a7d2..ad8c529d 100644
--- a/src/nexusinterface.cpp
+++ b/src/nexusinterface.cpp
@@ -17,450 +17,459 @@ 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 <utility.h>
-#include <regex>
-
-using QtJson::Json;
-
-
-NexusBridge::NexusBridge()
-{
- m_Interface = NexusInterface::instance();
-}
-
-
-void NexusBridge::requestDescription(int modID, QVariant userData, const QString &url)
-{
- m_RequestIDs.insert(m_Interface->requestDescription(modID, this, userData, url));
-}
-
-void NexusBridge::requestFiles(int modID, QVariant userData, const QString &url)
-{
- m_RequestIDs.insert(m_Interface->requestFiles(modID, this, userData, url));
-}
-
-void NexusBridge::requestFileInfo(int modID, int fileID, QVariant userData, const QString &url)
-{
- m_RequestIDs.insert(m_Interface->requestFileInfo(modID, fileID, this, userData, url));
-}
-
-void NexusBridge::requestDownloadURL(int modID, int fileID, QVariant userData, const QString &url)
-{
- m_RequestIDs.insert(m_Interface->requestDownloadURL(modID, fileID, this, userData, url));
-}
-
-void NexusBridge::requestToggleEndorsement(int modID, bool endorse, QVariant userData, const QString &url)
-{
- m_RequestIDs.insert(m_Interface->requestToggleEndorsement(modID, endorse, this, userData, 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 nxmDescriptionAvailable(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);
- emit nxmFilesAvailable(modID, userData, resultData);
- }
-}
-
-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 nxmFileInfoAvailable(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 nxmDownloadURLsAvailable(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 nxmEndorsementToggled(modID, userData, resultData);
- }
-}
-
-void NexusBridge::nxmRequestFailed(int modID, 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 nxmRequestFailed(modID, userData, errorMessage);
- }
-}
-
-
-QAtomicInt NexusInterface::NXMRequestInfo::s_NextID(0);
-
-
-NexusInterface::NexusInterface()
- : m_NMMVersion()
-{
- m_AccessManager = new NXMAccessManager(this);
-
- m_DiskCache = new QNetworkDiskCache(this);
- connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString)));
-}
-
-
-NXMAccessManager *NexusInterface::getAccessManager()
-{
- return m_AccessManager;
-}
-
-
-NexusInterface *NexusInterface::s_Instance = NULL;
-
-
-NexusInterface *NexusInterface::instance()
-{
- if (s_Instance == NULL) {
- s_Instance = new NexusInterface;
- }
- 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::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]+).*");
-// std::tr1::match_results<std::string::const_iterator> result;
- 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 {
- modName.clear();
- modID = -1;
- }
-}
-
-
-int NexusInterface::requestDescription(int modID, QObject *receiver, QVariant userData, const QString &url)
-{
- NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_DESCRIPTION, userData, url);
- 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,QVariant,int,QString)),
- receiver, SLOT(nxmRequestFailed(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 &url)
-{
- NXMRequestInfo requestInfo(modIDs, NXMRequestInfo::TYPE_GETUPDATES, userData, url);
- 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,QVariant,int,QString)),
- receiver, SLOT(nxmRequestFailed(int,QVariant,int,QString)), Qt::UniqueConnection);
-
- nextRequest();
- return requestInfo.m_ID;
-}
-
-
-int NexusInterface::requestFiles(int modID, QObject *receiver, QVariant userData, const QString &url)
-{
- NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_FILES, userData, url);
- 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,QVariant,int,QString)),
- receiver, SLOT(nxmRequestFailed(int,QVariant,int,QString)), Qt::UniqueConnection);
-
- nextRequest();
- return requestInfo.m_ID;
-}
-
-
-int NexusInterface::requestFileInfo(int modID, int fileID, QObject *receiver, QVariant userData, const QString &url)
-{
- NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_FILEINFO, userData, url);
- 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,QVariant,int,QString)),
- receiver, SLOT(nxmRequestFailed(int,QVariant,int,QString)), Qt::UniqueConnection);
-
- nextRequest();
- return requestInfo.m_ID;
-}
-
-
-int NexusInterface::requestDownloadURL(int modID, int fileID, QObject *receiver, QVariant userData, const QString &url)
-{
- NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_DOWNLOADURL, userData, url);
- 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,QVariant,int,QString)),
- receiver, SLOT(nxmRequestFailed(int,QVariant,int,QString)), Qt::UniqueConnection);
-
- nextRequest();
- return requestInfo.m_ID;
-}
-
-
-int NexusInterface::requestToggleEndorsement(int modID, bool endorse, QObject *receiver, QVariant userData, const QString &url)
-{
- NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLEENDORSEMENT, userData, url);
- 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,QVariant,int,QString)),
- receiver, SLOT(nxmRequestFailed(int,QVariant,int,QString)), Qt::UniqueConnection);
-
- nextRequest();
- return requestInfo.m_ID;
-}
-
-
-void NexusInterface::nextRequest()
-{
- if ((m_ActiveRequest.size() >= MAX_ACTIVE_DOWNLOADS) || (m_RequestQueue.isEmpty())) {
- return;
- }
-
- NXMRequestInfo info = m_RequestQueue.dequeue();
- info.m_Timeout = new QTimer(this);
- info.m_Timeout->setInterval(60000);
-
- QString url;
- 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);
- } 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);
- } break;
- }
-
-/*
- /// <summary>
- /// Finds the mods containing the given search terms.
- /// </summary>
- /// <param name="p_strModNameSearchString">The terms to use to search for mods.</param>
- /// <param name="p_strType">Whether the returned mods' names should include all of
- /// the given search terms, or any of the terms.</param>
- /// <returns>The mod info for the mods matching the given search criteria.</returns>
- [OperationContract]
- [WebGet(
- BodyStyle = WebMessageBodyStyle.Bare,
- UriTemplate = "Mods/?Find&name={p_strModNameSearchString}&type={p_strType}",
- ResponseFormat = WebMessageFormat.Json)]
- List<NexusModInfo> FindMods(string p_strModNameSearchString, string p_strType);
-
-
-
-*/
-
-
- QNetworkRequest request(url);
- request.setHeader(QNetworkRequest::ContentTypeHeader, "application/xml");
- request.setRawHeader("User-Agent", QString("Mod Organizer v0.12.0 (compatible to Nexus Client v%1)").arg(m_NMMVersion).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_UserData, iter->m_ID, reply->errorString());
- } else {
- 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");
- }
-
- emit nxmRequestFailed(iter->m_ModID, iter->m_UserData, iter->m_ID, nexusError);
- } else {
- bool ok;
- QVariant result = Json::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_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 error: %s", reply->errorString().toUtf8().constData());
-}
-
-
-void NexusInterface::requestTimeout()
-{
- QTimer *timer = qobject_cast<QTimer*>(sender());
- if (timer == NULL) {
- qWarning("invalid sender type");
- return;
- }
- qWarning("request timeout");
- 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;
- }
- }
-}
+#include "nexusinterface.h"
+#include "nxmaccessmanager.h"
+#include "utility.h"
+#include "json.h"
+#include "selectiondialog.h"
+#include <utility.h>
+#include <regex>
+
+using QtJson::Json;
+
+
+NexusBridge::NexusBridge()
+{
+ m_Interface = NexusInterface::instance();
+}
+
+
+void NexusBridge::requestDescription(int modID, QVariant userData, const QString &url)
+{
+ m_RequestIDs.insert(m_Interface->requestDescription(modID, this, userData, url));
+}
+
+void NexusBridge::requestFiles(int modID, QVariant userData, const QString &url)
+{
+ m_RequestIDs.insert(m_Interface->requestFiles(modID, this, userData, url));
+}
+
+void NexusBridge::requestFileInfo(int modID, int fileID, QVariant userData, const QString &url)
+{
+ m_RequestIDs.insert(m_Interface->requestFileInfo(modID, fileID, this, userData, url));
+}
+
+void NexusBridge::requestDownloadURL(int modID, int fileID, QVariant userData, const QString &url)
+{
+ m_RequestIDs.insert(m_Interface->requestDownloadURL(modID, fileID, this, userData, url));
+}
+
+void NexusBridge::requestToggleEndorsement(int modID, bool endorse, QVariant userData, const QString &url)
+{
+ m_RequestIDs.insert(m_Interface->requestToggleEndorsement(modID, endorse, this, userData, 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 nxmDescriptionAvailable(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);
+ emit nxmFilesAvailable(modID, userData, resultData);
+ }
+}
+
+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 nxmFileInfoAvailable(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 nxmDownloadURLsAvailable(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 nxmEndorsementToggled(modID, userData, resultData);
+ }
+}
+
+void NexusBridge::nxmRequestFailed(int modID, 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 nxmRequestFailed(modID, userData, errorMessage);
+ }
+}
+
+
+QAtomicInt NexusInterface::NXMRequestInfo::s_NextID(0);
+
+
+NexusInterface::NexusInterface()
+ : m_NMMVersion()
+{
+ m_AccessManager = new NXMAccessManager(this);
+
+ m_DiskCache = new QNetworkDiskCache(this);
+ connect(m_AccessManager, SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString)));
+}
+
+
+NXMAccessManager *NexusInterface::getAccessManager()
+{
+ return m_AccessManager;
+}
+
+
+NexusInterface *NexusInterface::s_Instance = NULL;
+
+
+NexusInterface *NexusInterface::instance()
+{
+ if (s_Instance == NULL) {
+ s_Instance = new NexusInterface;
+ }
+ 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::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_]+)");
+
+// std::tr1::match_results<std::string::const_iterator> result;
+ 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 &url)
+{
+ NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_DESCRIPTION, userData, url);
+ 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,QVariant,int,QString)),
+ receiver, SLOT(nxmRequestFailed(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 &url)
+{
+ NXMRequestInfo requestInfo(modIDs, NXMRequestInfo::TYPE_GETUPDATES, userData, url);
+ 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,QVariant,int,QString)),
+ receiver, SLOT(nxmRequestFailed(int,QVariant,int,QString)), Qt::UniqueConnection);
+
+ nextRequest();
+ return requestInfo.m_ID;
+}
+
+
+int NexusInterface::requestFiles(int modID, QObject *receiver, QVariant userData, const QString &url)
+{
+ NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_FILES, userData, url);
+ 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,QVariant,int,QString)),
+ receiver, SLOT(nxmRequestFailed(int,QVariant,int,QString)), Qt::UniqueConnection);
+
+ nextRequest();
+ return requestInfo.m_ID;
+}
+
+
+int NexusInterface::requestFileInfo(int modID, int fileID, QObject *receiver, QVariant userData, const QString &url)
+{
+ NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_FILEINFO, userData, url);
+ 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,QVariant,int,QString)),
+ receiver, SLOT(nxmRequestFailed(int,QVariant,int,QString)), Qt::UniqueConnection);
+
+ nextRequest();
+ return requestInfo.m_ID;
+}
+
+
+int NexusInterface::requestDownloadURL(int modID, int fileID, QObject *receiver, QVariant userData, const QString &url)
+{
+ NXMRequestInfo requestInfo(modID, fileID, NXMRequestInfo::TYPE_DOWNLOADURL, userData, url);
+ 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,QVariant,int,QString)),
+ receiver, SLOT(nxmRequestFailed(int,QVariant,int,QString)), Qt::UniqueConnection);
+
+ nextRequest();
+ return requestInfo.m_ID;
+}
+
+
+int NexusInterface::requestToggleEndorsement(int modID, bool endorse, QObject *receiver, QVariant userData, const QString &url)
+{
+ NXMRequestInfo requestInfo(modID, NXMRequestInfo::TYPE_TOGGLEENDORSEMENT, userData, url);
+ 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,QVariant,int,QString)),
+ receiver, SLOT(nxmRequestFailed(int,QVariant,int,QString)), Qt::UniqueConnection);
+
+ nextRequest();
+ return requestInfo.m_ID;
+}
+
+
+void NexusInterface::nextRequest()
+{
+ if ((m_ActiveRequest.size() >= MAX_ACTIVE_DOWNLOADS) || (m_RequestQueue.isEmpty())) {
+ return;
+ }
+
+ NXMRequestInfo info = m_RequestQueue.dequeue();
+ info.m_Timeout = new QTimer(this);
+ info.m_Timeout->setInterval(60000);
+
+ QString url;
+ 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);
+ } 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);
+ } break;
+ }
+
+/*
+ /// <summary>
+ /// Finds the mods containing the given search terms.
+ /// </summary>
+ /// <param name="p_strModNameSearchString">The terms to use to search for mods.</param>
+ /// <param name="p_strType">Whether the returned mods' names should include all of
+ /// the given search terms, or any of the terms.</param>
+ /// <returns>The mod info for the mods matching the given search criteria.</returns>
+ [OperationContract]
+ [WebGet(
+ BodyStyle = WebMessageBodyStyle.Bare,
+ UriTemplate = "Mods/?Find&name={p_strModNameSearchString}&type={p_strType}",
+ ResponseFormat = WebMessageFormat.Json)]
+ List<NexusModInfo> FindMods(string p_strModNameSearchString, string p_strType);
+
+
+
+*/
+
+
+ QNetworkRequest request(url);
+ request.setHeader(QNetworkRequest::ContentTypeHeader, "application/xml");
+ request.setRawHeader("User-Agent", QString("Mod Organizer v0.12.0 (compatible to Nexus Client v%1)").arg(m_NMMVersion).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_UserData, iter->m_ID, reply->errorString());
+ } else {
+ 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");
+ }
+
+ emit nxmRequestFailed(iter->m_ModID, iter->m_UserData, iter->m_ID, nexusError);
+ } else {
+ bool ok;
+ QVariant result = Json::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_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 error: %s", reply->errorString().toUtf8().constData());
+}
+
+
+void NexusInterface::requestTimeout()
+{
+ QTimer *timer = qobject_cast<QTimer*>(sender());
+ if (timer == NULL) {
+ qWarning("invalid sender type");
+ return;
+ }
+ qWarning("request timeout");
+ 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;
+ }
+ }
+}
diff --git a/src/nexusview.cpp b/src/nexusview.cpp
index 1099fff2..c8c7f09d 100644
--- a/src/nexusview.cpp
+++ b/src/nexusview.cpp
@@ -17,71 +17,102 @@ 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 "nexusview.h"
-
-#include <QEvent>
-#include <QKeyEvent>
-#include <QWebFrame>
-#include <QWebElement>
-#include <QNetworkDiskCache>
-#include <QMenu>
-#include <Shlwapi.h>
-#include "utility.h"
-
-NexusView::NexusView(QWidget *parent)
- : QWebView(parent), m_LastSeenModID(0)
-{
- installEventFilter(this);
-// this->pageAction(QWebPage::OpenLinkInNewWindow)
- connect(this, SIGNAL(urlChanged(QUrl)), this, SLOT(urlChanged(QUrl)));
-
- page()->settings()->setMaximumPagesInCache(10);
-}
-
-
-void NexusView::urlChanged(const QUrl &url)
-{
- QRegExp modidExp(QString("http://([a-zA-Z0-9]*).nexusmods.com/downloads/file.php\\?id=(\\d+)"), Qt::CaseInsensitive);
- if (modidExp.indexIn(url.toString()) != -1) {
- m_LastSeenModID = modidExp.cap(2).toInt();
- }
-}
-
-
-QWebView *NexusView::createWindow(QWebPage::WebWindowType)
-{
- NexusView *newView = new NexusView(parentWidget());
- emit initTab(newView);
- return newView;
-}
-
-
-bool NexusView::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);
-}
+#include "nexusview.h"
+
+#include <QEvent>
+#include <QKeyEvent>
+#include <QWebFrame>
+#include <QWebElement>
+#include <QNetworkDiskCache>
+#include <QMenu>
+#include <Shlwapi.h>
+#include "utility.h"
+
+NexusView::NexusView(QWidget *parent)
+ : QWebView(parent), m_LastSeenModID(0)
+{
+ installEventFilter(this);
+// this->pageAction(QWebPage::OpenLinkInNewWindow)
+ connect(this, SIGNAL(urlChanged(QUrl)), this, SLOT(urlChanged(QUrl)));
+
+ page()->settings()->setMaximumPagesInCache(10);
+}
+
+
+void NexusView::urlChanged(const QUrl &url)
+{
+ QRegExp modidExp(QString("http://([a-zA-Z0-9]*).nexusmods.com/downloads/file.php\\?id=(\\d+)"), Qt::CaseInsensitive);
+ if (modidExp.indexIn(url.toString()) != -1) {
+ m_LastSeenModID = modidExp.cap(2).toInt();
+ }
+}
+
+void NexusView::openPageExternal()
+{
+ ::ShellExecuteW(NULL, L"open", ToWString(url().toString()).c_str(), NULL, NULL, SW_SHOWNORMAL);
+}
+
+void NexusView::openLinkExternal()
+{
+ ::ShellExecuteW(NULL, L"open", ToWString(m_ContextURL.toString()).c_str(), NULL, NULL, SW_SHOWNORMAL);
+}
+
+
+QWebView *NexusView::createWindow(QWebPage::WebWindowType)
+{
+ NexusView *newView = new NexusView(parentWidget());
+ emit initTab(newView);
+ return newView;
+}
+
+
+bool NexusView::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);
+}
+
+
+void NexusView::contextMenuEvent(QContextMenuEvent *event)
+{
+ if (!page()->swallowContextMenuEvent(event)) {
+ QWebHitTestResult r = page()->mainFrame()->hitTestContent(event->pos());
+
+ QMenu *menu = page()->createStandardContextMenu();
+ QAction *openExternalAction = new QAction("Open in external browser", menu);
+ if (r.linkUrl().isEmpty()) {
+ connect(openExternalAction, SIGNAL(triggered()), this, SLOT(openPageExternal()));
+ } else {
+ m_ContextURL = r.linkUrl();
+ connect(openExternalAction, SIGNAL(triggered()), this, SLOT(openLinkExternal()));
+ }
+
+ menu->addSeparator();
+ menu->addAction(openExternalAction);
+ menu->exec(mapToGlobal(event->pos()));
+ }
+}
diff --git a/src/nexusview.h b/src/nexusview.h
index f0370d27..680a36f1 100644
--- a/src/nexusview.h
+++ b/src/nexusview.h
@@ -17,73 +17,79 @@ 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 NexusView : public QWebView
-{
- Q_OBJECT
-
-public:
-
- explicit NexusView(QWidget *parent = 0);
-
- /**
- * @return last mod id seen in the url
- */
- int getLastSeenModID() const { return m_LastSeenModID; }
-
-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(NexusView *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 slots:
-
- void urlChanged(const QUrl &url);
-
-private:
-
- QString m_FindPattern;
- bool m_MiddleClick;
- int m_LastSeenModID;
-
-};
-
-#endif // NEXUSVIEW_H
+#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 NexusView : public QWebView
+{
+ Q_OBJECT
+
+public:
+
+ explicit NexusView(QWidget *parent = 0);
+
+ /**
+ * @return last mod id seen in the url
+ */
+ int getLastSeenModID() const { return m_LastSeenModID; }
+
+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(NexusView *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);
+
+ virtual void contextMenuEvent(QContextMenuEvent *event);
+
+private slots:
+
+ void urlChanged(const QUrl &url);
+
+ void openPageExternal();
+ void openLinkExternal();
+
+private:
+
+ QString m_FindPattern;
+ bool m_MiddleClick;
+ int m_LastSeenModID;
+ QUrl m_ContextURL;
+
+};
+
+#endif // NEXUSVIEW_H
diff --git a/src/organizer.pro b/src/organizer.pro
index 29c3b5fa..723c1e04 100644
--- a/src/organizer.pro
+++ b/src/organizer.pro
@@ -78,7 +78,9 @@ SOURCES += \
moapplication.cpp \
profileinputdialog.cpp \
icondelegate.cpp \
- gameinfoimpl.cpp
+ gameinfoimpl.cpp \
+ csvbuilder.cpp \
+ savetextasdialog.cpp
HEADERS += \
transfersavesdialog.h \
@@ -144,7 +146,9 @@ HEADERS += \
moapplication.h \
profileinputdialog.h \
icondelegate.h \
- gameinfoimpl.h
+ gameinfoimpl.h \
+ csvbuilder.h \
+ savetextasdialog.h
FORMS += \
transfersavesdialog.ui \
@@ -173,7 +177,8 @@ FORMS += \
categoriesdialog.ui \
baincomplexinstallerdialog.ui \
activatemodsdialog.ui \
- profileinputdialog.ui
+ profileinputdialog.ui \
+ savetextasdialog.ui
INCLUDEPATH += ../shared ../archive ../uibase ../bsatk "$(BOOSTPATH)"
@@ -233,24 +238,23 @@ DEFINES += UNICODE _UNICODE _CRT_SECURE_NO_WARNINGS NOMINMAX
DEFINES += BOOST_DISABLE_ASSERTS NDEBUG
#DEFINES += QMLJSDEBUGGER
-
SRCDIR = $$PWD
SRCDIR ~= s,/,$$QMAKE_DIR_SEP,g
OUTDIR ~= s,/,$$QMAKE_DIR_SEP,g
DSTDIR ~= s,/,$$QMAKE_DIR_SEP,g
-QMAKE_POST_LINK += mkdir -p $$quote($$DSTDIR) $$escape_expand(\\n)
-QMAKE_POST_LINK += mkdir -p $$quote($$DSTDIR\\dlls) $$escape_expand(\\n)
-QMAKE_POST_LINK += xcopy /y /i $$quote($$OUTDIR\\ModOrganizer*.exe) $$quote($$DSTDIR) $$escape_expand(\\n)
-QMAKE_POST_LINK += xcopy /y /i $$quote($$OUTDIR\\ModOrganizer*.pdb) $$quote($$DSTDIR) $$escape_expand(\\n)
-QMAKE_POST_LINK += xcopy /y /s /i $$quote($$SRCDIR\\stylesheets) $$quote($$DSTDIR)\\stylesheets $$escape_expand(\\n)
-QMAKE_POST_LINK += xcopy /y /s /i $$quote($$SRCDIR\\tutorials) $$quote($$DSTDIR)\\tutorials $$escape_expand(\\n)
-QMAKE_POST_LINK += xcopy /y /s /i $$quote($$SRCDIR\\*.qm) $$quote($$DSTDIR)\\translations $$escape_expand(\\n)
+QMAKE_POST_LINK += xcopy /y /I $$quote($$OUTDIR\\ModOrganizer*.exe) $$quote($$DSTDIR) $$escape_expand(\\n)
+QMAKE_POST_LINK += xcopy /y /I $$quote($$OUTDIR\\ModOrganizer*.pdb) $$quote($$DSTDIR) $$escape_expand(\\n)
+QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\stylesheets) $$quote($$DSTDIR)\\stylesheets $$escape_expand(\\n)
+QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\tutorials) $$quote($$DSTDIR)\\tutorials $$escape_expand(\\n)
+QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\*.qm) $$quote($$DSTDIR)\\translations $$escape_expand(\\n)
CONFIG(debug, debug|release) {
- QMAKE_POST_LINK += copy $$quote($$SRCDIR\\..\\dlls.manifest.debug) $$quote($$DSTDIR)\\dlls\\dlls.manifest $$escape_expand(\\n)
+ QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\..\\dlls.*manifest.debug) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n)
+ QMAKE_POST_LINK += copy /y $$quote($$DSTDIR)\\dlls\\dlls.manifest.debug $$quote($$DSTDIR)\\dlls\\dlls.manifest $$escape_expand(\\n)
+ QMAKE_POST_LINK += del $$quote($$DSTDIR)\\dlls\\dlls.manifest.debug $$escape_expand(\\n)
} else {
- QMAKE_POST_LINK += copy $$quote($$SRCDIR\\..\\dlls.manifest) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n)
+ QMAKE_POST_LINK += xcopy /y /s /I $$quote($$SRCDIR\\..\\dlls.*manifest) $$quote($$DSTDIR)\\dlls $$escape_expand(\\n)
}
RESOURCES += \
diff --git a/src/savetextasdialog.cpp b/src/savetextasdialog.cpp
new file mode 100644
index 00000000..465e3d65
--- /dev/null
+++ b/src/savetextasdialog.cpp
@@ -0,0 +1,46 @@
+#include "savetextasdialog.h"
+#include "ui_savetextasdialog.h"
+#include <report.h>
+#include <QClipboard>
+#include <QFileDialog>
+
+SaveTextAsDialog::SaveTextAsDialog(QWidget *parent)
+ : QDialog(parent), ui(new Ui::SaveTextAsDialog)
+{
+ ui->setupUi(this);
+}
+
+SaveTextAsDialog::~SaveTextAsDialog()
+{
+ delete ui;
+}
+
+void SaveTextAsDialog::setText(const QString &text)
+{
+ ui->textEdit->setPlainText(text);
+}
+
+void SaveTextAsDialog::on_closeBtn_clicked()
+{
+ this->close();
+}
+
+void SaveTextAsDialog::on_clipboardBtn_clicked()
+{
+ QClipboard *clipboard = QApplication::clipboard();
+ clipboard->setText(ui->textEdit->toPlainText());
+}
+
+void SaveTextAsDialog::on_saveAsBtn_clicked()
+{
+ QString fileName = QFileDialog::getSaveFileName(this, tr("Save CSV"), QString(), tr("Text Files") + " (*.txt *.csv)");
+ if (!fileName.isEmpty()) {
+ QFile file(fileName);
+ if (!file.open(QIODevice::WriteOnly)) {
+ reportError(tr("failed to open \"%1\" for writing").arg(fileName));
+ return;
+ }
+
+ file.write(ui->textEdit->toPlainText().toUtf8());
+ }
+}
diff --git a/src/savetextasdialog.h b/src/savetextasdialog.h
new file mode 100644
index 00000000..1a17a2db
--- /dev/null
+++ b/src/savetextasdialog.h
@@ -0,0 +1,31 @@
+#ifndef SAVETEXTASDIALOG_H
+#define SAVETEXTASDIALOG_H
+
+#include <QDialog>
+
+namespace Ui {
+class SaveTextAsDialog;
+}
+
+class SaveTextAsDialog : public QDialog
+{
+ Q_OBJECT
+
+public:
+ explicit SaveTextAsDialog(QWidget *parent = 0);
+ ~SaveTextAsDialog();
+
+ void setText(const QString &text);
+
+private slots:
+ void on_closeBtn_clicked();
+
+ void on_clipboardBtn_clicked();
+
+ void on_saveAsBtn_clicked();
+
+private:
+ Ui::SaveTextAsDialog *ui;
+};
+
+#endif // SAVETEXTASDIALOG_H
diff --git a/src/savetextasdialog.ui b/src/savetextasdialog.ui
new file mode 100644
index 00000000..e4834319
--- /dev/null
+++ b/src/savetextasdialog.ui
@@ -0,0 +1,69 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>SaveTextAsDialog</class>
+ <widget class="QDialog" name="SaveTextAsDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>631</width>
+ <height>578</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>Dialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <widget class="QTextEdit" name="textEdit">
+ <property name="lineWrapMode">
+ <enum>QTextEdit::NoWrap</enum>
+ </property>
+ <property name="acceptRichText">
+ <bool>false</bool>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="clipboardBtn">
+ <property name="text">
+ <string>Copy To Clipboard</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="saveAsBtn">
+ <property name="text">
+ <string>Save As...</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="horizontalSpacer">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>40</width>
+ <height>20</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ <item>
+ <widget class="QPushButton" name="closeBtn">
+ <property name="text">
+ <string>Close</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <resources/>
+ <connections/>
+</ui>
diff --git a/src/shared/directoryentry.cpp b/src/shared/directoryentry.cpp
index 748ead99..aa3bce5f 100644
--- a/src/shared/directoryentry.cpp
+++ b/src/shared/directoryentry.cpp
@@ -17,783 +17,783 @@ 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 "directoryentry.h"
-#define WIN32_LEAN_AND_MEAN
-#include <Windows.h>
-#include <sstream>
-#include <algorithm>
-#include <bsatk.h>
-#include "error_report.h"
-#include "util.h"
-#include "windows_error.h"
-#include <boost/bind.hpp>
-
-
-
-class OriginConnection {
-
-public:
-
- typedef int Index;
- static const int INVALID_INDEX = INT_MIN;
-
-public:
-
- OriginConnection()
- : m_NextID(0)
- {}
-
- FilesOrigin& createOrigin(const std::wstring &originName, const std::wstring &directory, int priority,
- boost::shared_ptr<FileRegister> fileRegister, boost::shared_ptr<OriginConnection> originConnection) {
- int newID = createID();
- m_Origins[newID] = FilesOrigin(newID, originName, directory, priority, fileRegister, originConnection);
- m_OriginsNameMap[originName] = newID;
- m_OriginsPriorityMap[priority] = newID;
- return m_Origins[newID];
- }
-
- bool exists(const std::wstring &name) {
- std::map<std::wstring, int>::iterator iter = m_OriginsNameMap.find(name);
- return iter != m_OriginsNameMap.end();
- }
-
- FilesOrigin &getByID(Index ID) {
- return m_Origins[ID];
- }
-
- FilesOrigin &getByName(const std::wstring &name) {
- std::map<std::wstring, int>::iterator iter = m_OriginsNameMap.find(name);
- if (iter != m_OriginsNameMap.end()) {
- return m_Origins[iter->second];
- } else {
- std::ostringstream stream;
- stream << "invalid origin name: " << ToString(name, false);
- throw std::runtime_error(stream.str());
- }
- }
-
- void changePriorityLookup(int oldPriority, int newPriority)
- {
- auto iter = m_OriginsPriorityMap.find(oldPriority);
- if (iter != m_OriginsPriorityMap.end()) {
- Index idx = iter->second;
- m_OriginsPriorityMap.erase(iter);
- m_OriginsPriorityMap[newPriority] = idx;
- }
- }
-
- void changeNameLookup(const std::wstring &oldName, const std::wstring &newName)
- {
- auto iter = m_OriginsNameMap.find(oldName);
- if (iter != m_OriginsNameMap.end()) {
- Index idx = iter->second;
- m_OriginsNameMap.erase(iter);
- m_OriginsNameMap[newName] = idx;
- } else {
- log("failed to change name lookup from %ls to %ls", oldName.c_str(), newName.c_str());
- }
- }
-
-private:
-
- Index createID() {
- return m_NextID++;
- }
-
-private:
-
- Index m_NextID;
-
- std::map<Index, FilesOrigin> m_Origins;
- std::map<std::wstring, Index> m_OriginsNameMap;
- std::map<int, Index> m_OriginsPriorityMap;
-
-};
-
-
-//
-// FilesOrigin
-//
-
-
-void FilesOrigin::enable(bool enabled)
-{
- if (!enabled) {
- std::set<FileEntry::Index> copy = m_Files;
- for (auto iter = copy.begin(); iter != copy.end(); ++iter) {
- m_FileRegister->removeOrigin(*iter, m_ID);
- }
- m_Files.clear();
- }
- m_Disabled = !enabled;
-}
-
-
-void FilesOrigin::removeFile(FileEntry::Index index)
-{
- auto iter = m_Files.find(index);
- if (iter != m_Files.end()) {
- m_Files.erase(iter);
- }
-}
-
-
-
-std::wstring tail(const std::wstring &source, const size_t count)
-{
- if (count >= source.length()) {
- return source;
- }
-
- return source.substr(source.length() - count);
-}
-
-
-void FilesOrigin::setPriority(int priority)
-{
- m_OriginConnection->changePriorityLookup(m_Priority, priority);
-
- m_Priority = priority;
-}
-
-
-void FilesOrigin::setName(const std::wstring &name)
-{
- m_OriginConnection->changeNameLookup(m_Name, name);
- // change path too
- if (tail(m_Path, m_Name.length()) == m_Name) {
- m_Path = m_Path.substr(0, m_Path.length() - m_Name.length()).append(name);
- }
- m_Name = name;
-}
-
-std::vector<FileEntry*> FilesOrigin::getFiles() const
-{
- std::vector<FileEntry*> result;
-
- for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
- result.push_back(m_FileRegister->getFile(*iter));
- }
-
- return result;
-}
-
-
-
-//
-// FileEntry
-//
-
-void FileEntry::addOrigin(int origin, FILETIME fileTime, const std::wstring &archive)
-{
- if (m_Origin == -1) {
- m_Origin = origin;
- m_FileTime = fileTime;
- m_Archive = archive;
-// } else if (FilesOrigin::getByID(origin).getPriority() > FilesOrigin::getByID(m_Origin).getPriority()) {
- } else if (m_Parent->getOriginByID(origin).getPriority() > m_Parent->getOriginByID(m_Origin).getPriority()) {
- if (std::find(m_Alternatives.begin(), m_Alternatives.end(), m_Origin) == m_Alternatives.end()) {
- m_Alternatives.push_back(m_Origin);
- }
- m_Origin = origin;
- m_FileTime = fileTime;
- m_Archive = archive;
- } else {
- bool found = false;
- if (m_Origin == origin) {
- // already an origin
- return;
- }
- for (std::vector<int>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) {
- if (*iter == origin) {
- // already an origin
- return;
- }
- if (m_Parent->getOriginByID(*iter).getPriority() < m_Parent->getOriginByID(origin).getPriority()) {
- m_Alternatives.insert(iter, origin);
- found = true;
- break;
- }
- }
- if (!found) {
- m_Alternatives.push_back(origin);
- }
- }
-}
-
-bool FileEntry::removeOrigin(int origin)
-{
- if (m_Origin == origin) {
- if (!m_Alternatives.empty()) {
- // find alternative with the highest priority
- std::vector<int>::iterator currentIter = m_Alternatives.begin();
- for (std::vector<int>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) {
- if ((m_Parent->getOriginByID(*iter).getPriority() > m_Parent->getOriginByID(*currentIter).getPriority()) &&
- (*iter != origin)) {
- currentIter = iter;
- }
- }
- int currentID = *currentIter;
- m_Alternatives.erase(currentIter);
-
- m_Origin = currentID;
-
- // now we need to update the file time...
- std::wstring filePath = getFullPath();
- HANDLE file = ::CreateFile(filePath.c_str(), GENERIC_READ | GENERIC_WRITE,
- 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
- if (!::GetFileTime(file, NULL, NULL, &m_FileTime)) {
- // maybe this file is in a bsa, but there is no easy way to find out which. User should refresh
- // the view to find out
- m_Archive = L"bsa?";
- } else {
- m_Archive = L"";
- }
-
- ::CloseHandle(file);
-
- } else {
- m_Origin = -1;
- return true;
- }
- } else {
- std::vector<int>::iterator newEnd = std::remove(m_Alternatives.begin(), m_Alternatives.end(), origin);
- m_Alternatives.erase(newEnd, m_Alternatives.end());
- }
- return false;
-}
-
-
-// sorted by priority descending
-static bool ByOriginPriority(DirectoryEntry *entry, int LHS, int RHS)
-{
- return entry->getOriginByID(LHS).getPriority() < entry->getOriginByID(RHS).getPriority();
-}
-
-
-FileEntry::FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent)
- : m_Index(index), m_Name(name), m_Parent(parent), m_Origin(-1), m_Archive(L"")
-{
-}
-
-
-void FileEntry::sortOrigins()
-{
- m_Alternatives.push_back(m_Origin);
- std::sort(m_Alternatives.begin(), m_Alternatives.end(), boost::bind(ByOriginPriority, m_Parent, _1, _2));
- m_Origin = m_Alternatives[m_Alternatives.size() - 1];
- m_Alternatives.pop_back();
-}
-
-
-bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) const
-{
- if (parent == NULL) {
- return false;
- } else {
- // don't append the topmost parent because it is the virtual data-root
- if (recurseParents(path, parent->getParent())) {
- path.append(L"\\").append(parent->getName());
- }
- return true;
- }
-}
-
-std::wstring FileEntry::getFullPath() const
-{
- std::wstring result;
- bool ignore = false;
- result = m_Parent->getOriginByID(getOrigin(ignore)).getPath(); //base directory for origin
- recurseParents(result, m_Parent); // all intermediate directories
- result.append(L"\\").append(m_Name); // the actual filename
- return result;
-}
-
-std::wstring FileEntry::getRelativePath() const
-{
- std::wstring result;
- recurseParents(result, m_Parent); // all intermediate directories
- result.append(L"\\").append(m_Name); // the actual filename
- return result;
-}
-
-
-
-
-
-//
-// DirectoryEntry
-//
-DirectoryEntry::DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID)
- : m_OriginConnection(new OriginConnection),
- m_Name(name), m_Parent(parent), m_Populated(false), m_Origin(originID)
-{
- m_FileRegister.reset(new FileRegister(m_OriginConnection));
-}
-
-DirectoryEntry::DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID,
- boost::shared_ptr<FileRegister> fileRegister, boost::shared_ptr<OriginConnection> originConnection)
- : m_FileRegister(fileRegister), m_OriginConnection(originConnection),
- m_Name(name), m_Parent(parent), m_Populated(false), m_Origin(originID)
-{}
-
-
-DirectoryEntry::~DirectoryEntry()
-{
- clear();
-}
-
-
-const std::wstring &DirectoryEntry::getName() const
-{
- return m_Name;
-}
-
-
-void DirectoryEntry::clear()
-{
- m_Files.clear();
- for (std::vector<DirectoryEntry*>::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
- delete *iter;
- }
- m_SubDirectories.clear();
-}
-
-
-FilesOrigin &DirectoryEntry::createOrigin(const std::wstring &originName, const std::wstring &directory, int priority)
-{
- if (m_OriginConnection->exists(originName)) {
- FilesOrigin &origin = m_OriginConnection->getByName(originName);
- origin.enable(true);
- return origin;
- } else {
- return m_OriginConnection->createOrigin(originName, directory, priority, m_FileRegister, m_OriginConnection);
- }
-}
-
-
-void DirectoryEntry::addFromOrigin(const std::wstring &originName, const std::wstring &directory, int priority)
-{
- FilesOrigin &origin = createOrigin(originName, directory, priority);
- wchar_t *buffer = new wchar_t[MAXPATH_UNICODE + 1];
- memset(buffer, L'\0', MAXPATH_UNICODE + 1);
- try {
- int offset = _snwprintf(buffer, MAXPATH_UNICODE, L"%ls", directory.c_str());
- addFiles(origin, buffer, offset);
- } catch (...) {
- delete [] buffer;
- buffer = NULL;
- }
- delete [] buffer;
- m_Populated = true;
-}
-
-
-void DirectoryEntry::addFromBSA(const std::wstring &originName, std::wstring &directory, const std::wstring &fileName, int priority)
-{
- FilesOrigin &origin = createOrigin(originName, directory, priority);
-
- WIN32_FILE_ATTRIBUTE_DATA fileData;
- if (::GetFileAttributesExW(fileName.c_str(), GetFileExInfoStandard, &fileData) == 0) {
- throw windows_error("failed to determine file time");
- }
-
- BSA::Archive archive;
- BSA::EErrorCode res = archive.read(ToString(fileName, false).c_str());
- if ((res != BSA::ERROR_NONE) && (res != BSA::ERROR_INVALIDHASHES)) {
- std::ostringstream stream;
- stream << "invalid bsa file: " << ToString(fileName, false) << " errorcode " << res << " - " << ::GetLastError();
- throw std::runtime_error(stream.str());
- }
- size_t namePos = fileName.find_last_of(L"\\/");
- if (namePos == std::wstring::npos) {
- namePos = 0;
- } else {
- ++namePos;
- }
-
- addFiles(origin, archive.getRoot(), fileData.ftLastWriteTime, fileName.substr(namePos));
- m_Populated = true;
-}
-
-
-void DirectoryEntry::addFiles(FilesOrigin &origin, wchar_t *buffer, int bufferOffset)
-{
- WIN32_FIND_DATAW findData;
-
- _snwprintf(buffer + bufferOffset, MAXPATH_UNICODE - bufferOffset, L"\\*");
- HANDLE searchHandle = ::FindFirstFileExW(buffer, FindExInfoStandard, &findData, FindExSearchNameMatch, NULL, FIND_FIRST_EX_CASE_SENSITIVE);
- if (searchHandle != INVALID_HANDLE_VALUE) {
- BOOL result = true;
- while (result) {
- if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
- if ((wcscmp(findData.cFileName, L".") != 0) &&
- (wcscmp(findData.cFileName, L"..") != 0)) {
- int offset = _snwprintf(buffer + bufferOffset, MAXPATH_UNICODE, L"\\%ls", findData.cFileName);
- // recurse into subdirectories
- getSubDirectory(findData.cFileName, true, origin.getID())->addFiles(origin, buffer, bufferOffset + offset);
- }
- } else {
- insert(findData.cFileName, origin, findData.ftLastWriteTime, L"");
- }
- result = ::FindNextFileW(searchHandle, &findData);
- }
- }
- ::FindClose(searchHandle);
-}
-
-
-void DirectoryEntry::addFiles(FilesOrigin &origin, BSA::Folder::Ptr archiveFolder, FILETIME &fileTime, const std::wstring &archiveName)
-{
- // add files
- for (unsigned int fileIdx = 0; fileIdx < archiveFolder->getNumFiles(); ++fileIdx) {
- BSA::File::Ptr file = archiveFolder->getFile(fileIdx);
- insert(ToWString(file->getName(), true), origin, fileTime, archiveName);
- }
-
- // recurse into subdirectories
- for (unsigned int folderIdx = 0; folderIdx < archiveFolder->getNumSubFolders(); ++folderIdx) {
- BSA::Folder::Ptr folder = archiveFolder->getSubFolder(folderIdx);
- DirectoryEntry *folderEntry = getSubDirectoryRecursive(ToWString(folder->getName(), true), true, origin.getID());
-
- folderEntry->addFiles(origin, folder, fileTime, archiveName);
- }
-}
-
-
-void DirectoryEntry::removeFile(const std::wstring &filePath, int *origin)
-{
- size_t pos = filePath.find_first_of(L"\\/");
- if (pos == std::string::npos) {
- this->remove(filePath, origin);
- } else {
- std::wstring dirName = filePath.substr(0, pos);
- std::wstring rest = filePath.substr(pos + 1);
- DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false);
- if (entry != NULL) {
- entry->removeFile(rest, origin);
- }
- }
-}
-
-void DirectoryEntry::removeDirRecursive()
-{
- while (!m_Files.empty()) {
- m_FileRegister->removeFile(m_Files.begin()->second);
- }
-
- for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
- (*iter)->removeDirRecursive();
- }
- m_SubDirectories.clear();
-}
-
-void DirectoryEntry::removeDir(const std::wstring &path)
-{
- size_t pos = path.find_first_of(L"\\/");
- if (pos == std::string::npos) {
- for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
- if (_wcsicmp((*iter)->getName().c_str(), path.c_str()) == 0) {
- (*iter)->removeDirRecursive();
- m_SubDirectories.erase(iter);
- break;
- }
- }
- } else {
- std::wstring dirName = path.substr(0, pos);
- std::wstring rest = path.substr(pos + 1);
- DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false);
- if (entry != NULL) {
- entry->removeDir(rest);
- }
- }
-}
-
-
-void DirectoryEntry::insertFile(const std::wstring &filePath, FilesOrigin &origin, FILETIME fileTime)
-{
- size_t pos = filePath.find_first_of(L"\\/");
- if (pos == std::string::npos) {
- this->insert(filePath, origin, fileTime, L"");
- } else {
- std::wstring dirName = filePath.substr(0, pos);
- std::wstring rest = filePath.substr(pos + 1);
- getSubDirectoryRecursive(dirName, true, origin.getID())->insertFile(rest, origin, fileTime);
- }
-}
-
-
-void DirectoryEntry::removeFile(FileEntry::Index index)
-{
- if (m_Files.size() != 0) {
- auto iter = std::find_if(m_Files.begin(), m_Files.end(),
- [&index](const std::pair<std::wstring, FileEntry::Index> &iter) -> bool {
- return iter.second == index; } );
- if (iter != m_Files.end()) {
- m_Files.erase(iter);
- } else {
- log("file \"%ls\" not in directory \"%ls\"",
- m_FileRegister->getFile(index)->getName().c_str(),
- this->getName().c_str());
- }
- } else {
- log("file \"%ls\" not in directory \"%ls\", directory empty",
- m_FileRegister->getFile(index)->getName().c_str(),
- this->getName().c_str());
- }
-}
-
-
-int DirectoryEntry::anyOrigin() const
-{
- bool ignore;
- for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
- FileEntry *entry = m_FileRegister->getFile(iter->second);
- if (!entry->isFromArchive()) {
- return entry->getOrigin(ignore);
- }
- }
-
- // if we got here, no file directly within this directory is a valid indicator for a mod, thus
- // we continue looking in subdirectories
- for (std::vector<DirectoryEntry*>::const_iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
- int res = (*iter)->anyOrigin();
- if (res != -1){
- return res;
- }
- }
- return m_Origin;
-}
-
-
-bool DirectoryEntry::originExists(const std::wstring &name) const
-{
- return m_OriginConnection->exists(name);
-}
-
-
-FilesOrigin &DirectoryEntry::getOriginByID(int ID) const
-{
- return m_OriginConnection->getByID(ID);
-}
-
-
-FilesOrigin &DirectoryEntry::getOriginByName(const std::wstring &name) const
-{
- return m_OriginConnection->getByName(name);
-}
-
-
-int DirectoryEntry::getOrigin(const std::wstring &path, bool &archive)
-{
- const DirectoryEntry *directory = NULL;
- const FileEntry *file = searchFile(path, &directory);
- if (file != NULL) {
- return file->getOrigin(archive);
- } else {
- if (directory != NULL) {
- return directory->anyOrigin();
- } else {
- return -1;
- }
- }
-}
-
-std::vector<FileEntry*> DirectoryEntry::getFiles() const
-{
- std::vector<FileEntry*> result;
- for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
- result.push_back(m_FileRegister->getFile(iter->second));
- }
- return result;
-}
-
-
-const FileEntry *DirectoryEntry::searchFile(const std::wstring &path, const DirectoryEntry **directory) const
-{
- if (directory != NULL) {
- *directory = NULL;
- }
-
- if ((path.length() == 0) ||
- (path == L"*")) {
- // no file name -> the path ended on a (back-)slash
- *directory = this;
-
- return NULL;
- }
-
- size_t len = path.find_first_of(L"\\/");
-
- if (len == std::string::npos) {
- // no more path components
- auto iter = m_Files.find(path);
- if (iter != m_Files.end()) {
- return m_FileRegister->getFile(iter->second);
- } else if (directory != NULL) {
- DirectoryEntry *temp = findSubDirectory(path);
- if (temp != NULL) {
- *directory = temp;
- }
- }
- } else {
- // file is in in a subdirectory, recurse into the matching subdirectory
- std::wstring pathComponent = path.substr(0, len);
- DirectoryEntry *temp = findSubDirectory(pathComponent);
- if (temp != NULL) {
- return temp->searchFile(path.substr(len + 1), directory);
- }
- }
- return NULL;
-}
-
-/*
-void DirectoryEntry::sortOrigins()
-{
- for (std::set<FileEntry>::iterator iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
- const_cast<FileEntry&>(*iter).sortOrigins();
- }
- for (std::vector<DirectoryEntry*>::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
- (*iter)->sortOrigins();
- }
-}
-*/
-
-DirectoryEntry *DirectoryEntry::findSubDirectory(const std::wstring &name) const
-{
- for (std::vector<DirectoryEntry*>::const_iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
- if (_wcsicmp((*iter)->getName().c_str(), name.c_str()) == 0) {
- return *iter;
- }
- }
- return NULL;
-}
-
-
-
-const FileEntry *DirectoryEntry::findFile(const std::wstring &name)
-{
- auto iter = m_Files.find(name);
- if (iter != m_Files.end()) {
- return m_FileRegister->getFile(iter->second);
- } else {
- return NULL;
- }
-}
-
-DirectoryEntry *DirectoryEntry::getSubDirectory(const std::wstring &name, bool create, int originID)
-{
- for (std::vector<DirectoryEntry*>::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
- if (_wcsicmp((*iter)->getName().c_str(), name.c_str()) == 0) {
- return *iter;
- }
- }
- if (create) {
- std::vector<DirectoryEntry*>::iterator iter = m_SubDirectories.insert(m_SubDirectories.end(),
- new DirectoryEntry(name, this, originID, m_FileRegister, m_OriginConnection));
- return *iter;
- } else {
- return NULL;
- }
-}
-
-
-DirectoryEntry *DirectoryEntry::getSubDirectoryRecursive(const std::wstring &path, bool create, int originID)
-{
- if (path.length() == 0) {
- // path ended with a backslash?
- return this;
- }
-
- size_t pos = path.find_first_of(L"\\/");
- if (pos == std::wstring::npos) {
- return getSubDirectory(path, create);
- } else {
- DirectoryEntry *nextChild = getSubDirectory(path.substr(0, pos), create, originID);
- if (nextChild == NULL) {
- return NULL;
- } else {
- return nextChild->getSubDirectoryRecursive(path.substr(pos + 1), create, originID);
- }
- }
-}
-
-
-
-FileRegister::FileRegister(boost::shared_ptr<OriginConnection> originConnection)
- : m_OriginConnection(originConnection)
-{
-}
-
-FileEntry::Index FileRegister::generateIndex()
-{
- static FileEntry::Index sIndex = 0;
- return sIndex++;
-}
-
-bool FileRegister::indexValid(FileEntry::Index index) const
-{
- return m_Files.find(index) != m_Files.end();
-}
-
-FileEntry &FileRegister::createFile(const std::wstring &name, DirectoryEntry *parent)
-{
- FileEntry::Index index = generateIndex();
- m_Files[index] = FileEntry(index, name, parent);
- return m_Files[index];
-}
-
-
-FileEntry *FileRegister::getFile(FileEntry::Index index)
-{
- auto iter = m_Files.find(index);
- if (iter != m_Files.end()) {
- return &iter->second;
- }
- return NULL;
-}
-
-
-void FileRegister::unregisterFile(FileEntry &file)
-{
- bool ignore;
- // unregister from origin
- int originID = file.getOrigin(ignore);
- m_OriginConnection->getByID(originID).removeFile(file.getIndex());
- const std::vector<int> &alternatives = file.getAlternatives();
- for (auto iter = alternatives.begin(); iter != alternatives.end(); ++iter) {
- m_OriginConnection->getByID(*iter).removeFile(file.getIndex());
- }
-
- // unregister from directory
- if (file.getParent() != NULL) {
- file.getParent()->removeFile(file.getIndex());
- }
-}
-
-
-void FileRegister::removeFile(FileEntry::Index index)
-{
- auto iter = m_Files.find(index);
- if (iter != m_Files.end()) {
- unregisterFile(iter->second);
- m_Files.erase(index);
- }
-}
-
-void FileRegister::removeOrigin(FileEntry::Index index, int originID)
-{
- auto iter = m_Files.find(index);
- if (iter != m_Files.end()) {
- if (iter->second.removeOrigin(originID)) {
- unregisterFile(iter->second);
- }
- }
-}
-
-void FileRegister::sortOrigins()
-{
- for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
- iter->second.sortOrigins();
- }
-}
+#include "directoryentry.h"
+#define WIN32_LEAN_AND_MEAN
+#include <Windows.h>
+#include <sstream>
+#include <algorithm>
+#include <bsatk.h>
+#include "error_report.h"
+#include "util.h"
+#include "windows_error.h"
+#include <boost/bind.hpp>
+
+
+
+class OriginConnection {
+
+public:
+
+ typedef int Index;
+ static const int INVALID_INDEX = INT_MIN;
+
+public:
+
+ OriginConnection()
+ : m_NextID(0)
+ {}
+
+ FilesOrigin& createOrigin(const std::wstring &originName, const std::wstring &directory, int priority,
+ boost::shared_ptr<FileRegister> fileRegister, boost::shared_ptr<OriginConnection> originConnection) {
+ int newID = createID();
+ m_Origins[newID] = FilesOrigin(newID, originName, directory, priority, fileRegister, originConnection);
+ m_OriginsNameMap[originName] = newID;
+ m_OriginsPriorityMap[priority] = newID;
+ return m_Origins[newID];
+ }
+
+ bool exists(const std::wstring &name) {
+ std::map<std::wstring, int>::iterator iter = m_OriginsNameMap.find(name);
+ return iter != m_OriginsNameMap.end();
+ }
+
+ FilesOrigin &getByID(Index ID) {
+ return m_Origins[ID];
+ }
+
+ FilesOrigin &getByName(const std::wstring &name) {
+ std::map<std::wstring, int>::iterator iter = m_OriginsNameMap.find(name);
+ if (iter != m_OriginsNameMap.end()) {
+ return m_Origins[iter->second];
+ } else {
+ std::ostringstream stream;
+ stream << "invalid origin name: " << ToString(name, false);
+ throw std::runtime_error(stream.str());
+ }
+ }
+
+ void changePriorityLookup(int oldPriority, int newPriority)
+ {
+ auto iter = m_OriginsPriorityMap.find(oldPriority);
+ if (iter != m_OriginsPriorityMap.end()) {
+ Index idx = iter->second;
+ m_OriginsPriorityMap.erase(iter);
+ m_OriginsPriorityMap[newPriority] = idx;
+ }
+ }
+
+ void changeNameLookup(const std::wstring &oldName, const std::wstring &newName)
+ {
+ auto iter = m_OriginsNameMap.find(oldName);
+ if (iter != m_OriginsNameMap.end()) {
+ Index idx = iter->second;
+ m_OriginsNameMap.erase(iter);
+ m_OriginsNameMap[newName] = idx;
+ } else {
+ log("failed to change name lookup from %ls to %ls", oldName.c_str(), newName.c_str());
+ }
+ }
+
+private:
+
+ Index createID() {
+ return m_NextID++;
+ }
+
+private:
+
+ Index m_NextID;
+
+ std::map<Index, FilesOrigin> m_Origins;
+ std::map<std::wstring, Index> m_OriginsNameMap;
+ std::map<int, Index> m_OriginsPriorityMap;
+
+};
+
+
+//
+// FilesOrigin
+//
+
+
+void FilesOrigin::enable(bool enabled)
+{
+ if (!enabled) {
+ std::set<FileEntry::Index> copy = m_Files;
+ for (auto iter = copy.begin(); iter != copy.end(); ++iter) {
+ m_FileRegister->removeOrigin(*iter, m_ID);
+ }
+ m_Files.clear();
+ }
+ m_Disabled = !enabled;
+}
+
+
+void FilesOrigin::removeFile(FileEntry::Index index)
+{
+ auto iter = m_Files.find(index);
+ if (iter != m_Files.end()) {
+ m_Files.erase(iter);
+ }
+}
+
+
+
+std::wstring tail(const std::wstring &source, const size_t count)
+{
+ if (count >= source.length()) {
+ return source;
+ }
+
+ return source.substr(source.length() - count);
+}
+
+
+void FilesOrigin::setPriority(int priority)
+{
+ m_OriginConnection->changePriorityLookup(m_Priority, priority);
+
+ m_Priority = priority;
+}
+
+
+void FilesOrigin::setName(const std::wstring &name)
+{
+ m_OriginConnection->changeNameLookup(m_Name, name);
+ // change path too
+ if (tail(m_Path, m_Name.length()) == m_Name) {
+ m_Path = m_Path.substr(0, m_Path.length() - m_Name.length()).append(name);
+ }
+ m_Name = name;
+}
+
+std::vector<FileEntry*> FilesOrigin::getFiles() const
+{
+ std::vector<FileEntry*> result;
+
+ for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
+ result.push_back(m_FileRegister->getFile(*iter));
+ }
+
+ return result;
+}
+
+
+
+//
+// FileEntry
+//
+
+void FileEntry::addOrigin(int origin, FILETIME fileTime, const std::wstring &archive)
+{
+ if (m_Origin == -1) {
+ m_Origin = origin;
+ m_FileTime = fileTime;
+ m_Archive = archive;
+// } else if (FilesOrigin::getByID(origin).getPriority() > FilesOrigin::getByID(m_Origin).getPriority()) {
+ } else if (m_Parent->getOriginByID(origin).getPriority() > m_Parent->getOriginByID(m_Origin).getPriority()) {
+ if (std::find(m_Alternatives.begin(), m_Alternatives.end(), m_Origin) == m_Alternatives.end()) {
+ m_Alternatives.push_back(m_Origin);
+ }
+ m_Origin = origin;
+ m_FileTime = fileTime;
+ m_Archive = archive;
+ } else {
+ bool found = false;
+ if (m_Origin == origin) {
+ // already an origin
+ return;
+ }
+ for (std::vector<int>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) {
+ if (*iter == origin) {
+ // already an origin
+ return;
+ }
+ if (m_Parent->getOriginByID(*iter).getPriority() < m_Parent->getOriginByID(origin).getPriority()) {
+ m_Alternatives.insert(iter, origin);
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ m_Alternatives.push_back(origin);
+ }
+ }
+}
+
+bool FileEntry::removeOrigin(int origin)
+{
+ if (m_Origin == origin) {
+ if (!m_Alternatives.empty()) {
+ // find alternative with the highest priority
+ std::vector<int>::iterator currentIter = m_Alternatives.begin();
+ for (std::vector<int>::iterator iter = m_Alternatives.begin(); iter != m_Alternatives.end(); ++iter) {
+ if ((m_Parent->getOriginByID(*iter).getPriority() > m_Parent->getOriginByID(*currentIter).getPriority()) &&
+ (*iter != origin)) {
+ currentIter = iter;
+ }
+ }
+ int currentID = *currentIter;
+ m_Alternatives.erase(currentIter);
+
+ m_Origin = currentID;
+
+ // now we need to update the file time...
+ std::wstring filePath = getFullPath();
+ HANDLE file = ::CreateFile(filePath.c_str(), GENERIC_READ | GENERIC_WRITE,
+ 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
+ if (!::GetFileTime(file, NULL, NULL, &m_FileTime)) {
+ // maybe this file is in a bsa, but there is no easy way to find out which. User should refresh
+ // the view to find out
+ m_Archive = L"bsa?";
+ } else {
+ m_Archive = L"";
+ }
+
+ ::CloseHandle(file);
+
+ } else {
+ m_Origin = -1;
+ return true;
+ }
+ } else {
+ std::vector<int>::iterator newEnd = std::remove(m_Alternatives.begin(), m_Alternatives.end(), origin);
+ m_Alternatives.erase(newEnd, m_Alternatives.end());
+ }
+ return false;
+}
+
+
+// sorted by priority descending
+static bool ByOriginPriority(DirectoryEntry *entry, int LHS, int RHS)
+{
+ return entry->getOriginByID(LHS).getPriority() < entry->getOriginByID(RHS).getPriority();
+}
+
+
+FileEntry::FileEntry(Index index, const std::wstring &name, DirectoryEntry *parent)
+ : m_Index(index), m_Name(name), m_Parent(parent), m_Origin(-1), m_Archive(L"")
+{
+}
+
+
+void FileEntry::sortOrigins()
+{
+ m_Alternatives.push_back(m_Origin);
+ std::sort(m_Alternatives.begin(), m_Alternatives.end(), boost::bind(ByOriginPriority, m_Parent, _1, _2));
+ m_Origin = m_Alternatives[m_Alternatives.size() - 1];
+ m_Alternatives.pop_back();
+}
+
+
+bool FileEntry::recurseParents(std::wstring &path, const DirectoryEntry *parent) const
+{
+ if (parent == NULL) {
+ return false;
+ } else {
+ // don't append the topmost parent because it is the virtual data-root
+ if (recurseParents(path, parent->getParent())) {
+ path.append(L"\\").append(parent->getName());
+ }
+ return true;
+ }
+}
+
+std::wstring FileEntry::getFullPath() const
+{
+ std::wstring result;
+ bool ignore = false;
+ result = m_Parent->getOriginByID(getOrigin(ignore)).getPath(); //base directory for origin
+ recurseParents(result, m_Parent); // all intermediate directories
+ result.append(L"\\").append(m_Name); // the actual filename
+ return result;
+}
+
+std::wstring FileEntry::getRelativePath() const
+{
+ std::wstring result;
+ recurseParents(result, m_Parent); // all intermediate directories
+ result.append(L"\\").append(m_Name); // the actual filename
+ return result;
+}
+
+
+
+
+
+//
+// DirectoryEntry
+//
+DirectoryEntry::DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID)
+ : m_OriginConnection(new OriginConnection),
+ m_Name(name), m_Parent(parent), m_Populated(false), m_Origin(originID)
+{
+ m_FileRegister.reset(new FileRegister(m_OriginConnection));
+}
+
+DirectoryEntry::DirectoryEntry(const std::wstring &name, DirectoryEntry *parent, int originID,
+ boost::shared_ptr<FileRegister> fileRegister, boost::shared_ptr<OriginConnection> originConnection)
+ : m_FileRegister(fileRegister), m_OriginConnection(originConnection),
+ m_Name(name), m_Parent(parent), m_Populated(false), m_Origin(originID)
+{}
+
+
+DirectoryEntry::~DirectoryEntry()
+{
+ clear();
+}
+
+
+const std::wstring &DirectoryEntry::getName() const
+{
+ return m_Name;
+}
+
+
+void DirectoryEntry::clear()
+{
+ m_Files.clear();
+ for (std::vector<DirectoryEntry*>::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
+ delete *iter;
+ }
+ m_SubDirectories.clear();
+}
+
+
+FilesOrigin &DirectoryEntry::createOrigin(const std::wstring &originName, const std::wstring &directory, int priority)
+{
+ if (m_OriginConnection->exists(originName)) {
+ FilesOrigin &origin = m_OriginConnection->getByName(originName);
+ origin.enable(true);
+ return origin;
+ } else {
+ return m_OriginConnection->createOrigin(originName, directory, priority, m_FileRegister, m_OriginConnection);
+ }
+}
+
+
+void DirectoryEntry::addFromOrigin(const std::wstring &originName, const std::wstring &directory, int priority)
+{
+ FilesOrigin &origin = createOrigin(originName, directory, priority);
+ wchar_t *buffer = new wchar_t[MAXPATH_UNICODE + 1];
+ memset(buffer, L'\0', MAXPATH_UNICODE + 1);
+ try {
+ int offset = _snwprintf(buffer, MAXPATH_UNICODE, L"%ls", directory.c_str());
+ addFiles(origin, buffer, offset);
+ } catch (...) {
+ delete [] buffer;
+ buffer = NULL;
+ }
+ delete [] buffer;
+ m_Populated = true;
+}
+
+
+void DirectoryEntry::addFromBSA(const std::wstring &originName, std::wstring &directory, const std::wstring &fileName, int priority)
+{
+ FilesOrigin &origin = createOrigin(originName, directory, priority);
+
+ WIN32_FILE_ATTRIBUTE_DATA fileData;
+ if (::GetFileAttributesExW(fileName.c_str(), GetFileExInfoStandard, &fileData) == 0) {
+ throw windows_error("failed to determine file time");
+ }
+
+ BSA::Archive archive;
+ BSA::EErrorCode res = archive.read(ToString(fileName, false).c_str());
+ if ((res != BSA::ERROR_NONE) && (res != BSA::ERROR_INVALIDHASHES)) {
+ std::ostringstream stream;
+ stream << "invalid bsa file: " << ToString(fileName, false) << " errorcode " << res << " - " << ::GetLastError();
+ throw std::runtime_error(stream.str());
+ }
+ size_t namePos = fileName.find_last_of(L"\\/");
+ if (namePos == std::wstring::npos) {
+ namePos = 0;
+ } else {
+ ++namePos;
+ }
+
+ addFiles(origin, archive.getRoot(), fileData.ftLastWriteTime, fileName.substr(namePos));
+ m_Populated = true;
+}
+
+
+void DirectoryEntry::addFiles(FilesOrigin &origin, wchar_t *buffer, int bufferOffset)
+{
+ WIN32_FIND_DATAW findData;
+
+ _snwprintf(buffer + bufferOffset, MAXPATH_UNICODE - bufferOffset, L"\\*");
+ HANDLE searchHandle = ::FindFirstFileExW(buffer, FindExInfoStandard, &findData, FindExSearchNameMatch, NULL, FIND_FIRST_EX_CASE_SENSITIVE);
+ if (searchHandle != INVALID_HANDLE_VALUE) {
+ BOOL result = true;
+ while (result) {
+ if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
+ if ((wcscmp(findData.cFileName, L".") != 0) &&
+ (wcscmp(findData.cFileName, L"..") != 0)) {
+ int offset = _snwprintf(buffer + bufferOffset, MAXPATH_UNICODE, L"\\%ls", findData.cFileName);
+ // recurse into subdirectories
+ getSubDirectory(findData.cFileName, true, origin.getID())->addFiles(origin, buffer, bufferOffset + offset);
+ }
+ } else {
+ insert(findData.cFileName, origin, findData.ftLastWriteTime, L"");
+ }
+ result = ::FindNextFileW(searchHandle, &findData);
+ }
+ }
+ ::FindClose(searchHandle);
+}
+
+
+void DirectoryEntry::addFiles(FilesOrigin &origin, BSA::Folder::Ptr archiveFolder, FILETIME &fileTime, const std::wstring &archiveName)
+{
+ // add files
+ for (unsigned int fileIdx = 0; fileIdx < archiveFolder->getNumFiles(); ++fileIdx) {
+ BSA::File::Ptr file = archiveFolder->getFile(fileIdx);
+ insert(ToWString(file->getName(), true), origin, fileTime, archiveName);
+ }
+
+ // recurse into subdirectories
+ for (unsigned int folderIdx = 0; folderIdx < archiveFolder->getNumSubFolders(); ++folderIdx) {
+ BSA::Folder::Ptr folder = archiveFolder->getSubFolder(folderIdx);
+ DirectoryEntry *folderEntry = getSubDirectoryRecursive(ToWString(folder->getName(), true), true, origin.getID());
+
+ folderEntry->addFiles(origin, folder, fileTime, archiveName);
+ }
+}
+
+
+void DirectoryEntry::removeFile(const std::wstring &filePath, int *origin)
+{
+ size_t pos = filePath.find_first_of(L"\\/");
+ if (pos == std::string::npos) {
+ this->remove(filePath, origin);
+ } else {
+ std::wstring dirName = filePath.substr(0, pos);
+ std::wstring rest = filePath.substr(pos + 1);
+ DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false);
+ if (entry != NULL) {
+ entry->removeFile(rest, origin);
+ }
+ }
+}
+
+void DirectoryEntry::removeDirRecursive()
+{
+ while (!m_Files.empty()) {
+ m_FileRegister->removeFile(m_Files.begin()->second);
+ }
+
+ for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
+ (*iter)->removeDirRecursive();
+ }
+ m_SubDirectories.clear();
+}
+
+void DirectoryEntry::removeDir(const std::wstring &path)
+{
+ size_t pos = path.find_first_of(L"\\/");
+ if (pos == std::string::npos) {
+ for (auto iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
+ if (_wcsicmp((*iter)->getName().c_str(), path.c_str()) == 0) {
+ (*iter)->removeDirRecursive();
+ m_SubDirectories.erase(iter);
+ break;
+ }
+ }
+ } else {
+ std::wstring dirName = path.substr(0, pos);
+ std::wstring rest = path.substr(pos + 1);
+ DirectoryEntry *entry = getSubDirectoryRecursive(dirName, false);
+ if (entry != NULL) {
+ entry->removeDir(rest);
+ }
+ }
+}
+
+
+void DirectoryEntry::insertFile(const std::wstring &filePath, FilesOrigin &origin, FILETIME fileTime)
+{
+ size_t pos = filePath.find_first_of(L"\\/");
+ if (pos == std::string::npos) {
+ this->insert(filePath, origin, fileTime, L"");
+ } else {
+ std::wstring dirName = filePath.substr(0, pos);
+ std::wstring rest = filePath.substr(pos + 1);
+ getSubDirectoryRecursive(dirName, true, origin.getID())->insertFile(rest, origin, fileTime);
+ }
+}
+
+
+void DirectoryEntry::removeFile(FileEntry::Index index)
+{
+ if (m_Files.size() != 0) {
+ auto iter = std::find_if(m_Files.begin(), m_Files.end(),
+ [&index](const std::pair<std::wstring, FileEntry::Index> &iter) -> bool {
+ return iter.second == index; } );
+ if (iter != m_Files.end()) {
+ m_Files.erase(iter);
+ } else {
+ log("file \"%ls\" not in directory \"%ls\"",
+ m_FileRegister->getFile(index)->getName().c_str(),
+ this->getName().c_str());
+ }
+ } else {
+ log("file \"%ls\" not in directory \"%ls\", directory empty",
+ m_FileRegister->getFile(index)->getName().c_str(),
+ this->getName().c_str());
+ }
+}
+
+
+int DirectoryEntry::anyOrigin() const
+{
+ bool ignore;
+ for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
+ FileEntry *entry = m_FileRegister->getFile(iter->second);
+ if (!entry->isFromArchive()) {
+ return entry->getOrigin(ignore);
+ }
+ }
+
+ // if we got here, no file directly within this directory is a valid indicator for a mod, thus
+ // we continue looking in subdirectories
+ for (std::vector<DirectoryEntry*>::const_iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
+ int res = (*iter)->anyOrigin();
+ if (res != -1){
+ return res;
+ }
+ }
+ return m_Origin;
+}
+
+
+bool DirectoryEntry::originExists(const std::wstring &name) const
+{
+ return m_OriginConnection->exists(name);
+}
+
+
+FilesOrigin &DirectoryEntry::getOriginByID(int ID) const
+{
+ return m_OriginConnection->getByID(ID);
+}
+
+
+FilesOrigin &DirectoryEntry::getOriginByName(const std::wstring &name) const
+{
+ return m_OriginConnection->getByName(name);
+}
+
+
+int DirectoryEntry::getOrigin(const std::wstring &path, bool &archive)
+{
+ const DirectoryEntry *directory = NULL;
+ const FileEntry *file = searchFile(path, &directory);
+ if (file != NULL) {
+ return file->getOrigin(archive);
+ } else {
+ if (directory != NULL) {
+ return directory->anyOrigin();
+ } else {
+ return -1;
+ }
+ }
+}
+
+std::vector<FileEntry*> DirectoryEntry::getFiles() const
+{
+ std::vector<FileEntry*> result;
+ for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
+ result.push_back(m_FileRegister->getFile(iter->second));
+ }
+ return result;
+}
+
+
+const FileEntry *DirectoryEntry::searchFile(const std::wstring &path, const DirectoryEntry **directory) const
+{
+ if (directory != NULL) {
+ *directory = NULL;
+ }
+
+ if ((path.length() == 0) ||
+ (path == L"*")) {
+ // no file name -> the path ended on a (back-)slash
+ *directory = this;
+
+ return NULL;
+ }
+
+ size_t len = path.find_first_of(L"\\/");
+
+ if (len == std::string::npos) {
+ // no more path components
+ auto iter = m_Files.find(path);
+ if (iter != m_Files.end()) {
+ return m_FileRegister->getFile(iter->second);
+ } else if (directory != NULL) {
+ DirectoryEntry *temp = findSubDirectory(path);
+ if (temp != NULL) {
+ *directory = temp;
+ }
+ }
+ } else {
+ // file is in in a subdirectory, recurse into the matching subdirectory
+ std::wstring pathComponent = path.substr(0, len);
+ DirectoryEntry *temp = findSubDirectory(pathComponent);
+ if (temp != NULL) {
+ return temp->searchFile(path.substr(len + 1), directory);
+ }
+ }
+ return NULL;
+}
+
+/*
+void DirectoryEntry::sortOrigins()
+{
+ for (std::set<FileEntry>::iterator iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
+ const_cast<FileEntry&>(*iter).sortOrigins();
+ }
+ for (std::vector<DirectoryEntry*>::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
+ (*iter)->sortOrigins();
+ }
+}
+*/
+
+DirectoryEntry *DirectoryEntry::findSubDirectory(const std::wstring &name) const
+{
+ for (std::vector<DirectoryEntry*>::const_iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
+ if (_wcsicmp((*iter)->getName().c_str(), name.c_str()) == 0) {
+ return *iter;
+ }
+ }
+ return NULL;
+}
+
+
+
+const FileEntry *DirectoryEntry::findFile(const std::wstring &name)
+{
+ auto iter = m_Files.find(name);
+ if (iter != m_Files.end()) {
+ return m_FileRegister->getFile(iter->second);
+ } else {
+ return NULL;
+ }
+}
+
+DirectoryEntry *DirectoryEntry::getSubDirectory(const std::wstring &name, bool create, int originID)
+{
+ for (std::vector<DirectoryEntry*>::iterator iter = m_SubDirectories.begin(); iter != m_SubDirectories.end(); ++iter) {
+ if (_wcsicmp((*iter)->getName().c_str(), name.c_str()) == 0) {
+ return *iter;
+ }
+ }
+ if (create) {
+ std::vector<DirectoryEntry*>::iterator iter = m_SubDirectories.insert(m_SubDirectories.end(),
+ new DirectoryEntry(name, this, originID, m_FileRegister, m_OriginConnection));
+ return *iter;
+ } else {
+ return NULL;
+ }
+}
+
+
+DirectoryEntry *DirectoryEntry::getSubDirectoryRecursive(const std::wstring &path, bool create, int originID)
+{
+ if (path.length() == 0) {
+ // path ended with a backslash?
+ return this;
+ }
+
+ size_t pos = path.find_first_of(L"\\/");
+ if (pos == std::wstring::npos) {
+ return getSubDirectory(path, create);
+ } else {
+ DirectoryEntry *nextChild = getSubDirectory(path.substr(0, pos), create, originID);
+ if (nextChild == NULL) {
+ return NULL;
+ } else {
+ return nextChild->getSubDirectoryRecursive(path.substr(pos + 1), create, originID);
+ }
+ }
+}
+
+
+
+FileRegister::FileRegister(boost::shared_ptr<OriginConnection> originConnection)
+ : m_OriginConnection(originConnection)
+{
+}
+
+FileEntry::Index FileRegister::generateIndex()
+{
+ static FileEntry::Index sIndex = 0;
+ return sIndex++;
+}
+
+bool FileRegister::indexValid(FileEntry::Index index) const
+{
+ return m_Files.find(index) != m_Files.end();
+}
+
+FileEntry &FileRegister::createFile(const std::wstring &name, DirectoryEntry *parent)
+{
+ FileEntry::Index index = generateIndex();
+ m_Files[index] = FileEntry(index, name, parent);
+ return m_Files[index];
+}
+
+
+FileEntry *FileRegister::getFile(FileEntry::Index index)
+{
+ auto iter = m_Files.find(index);
+ if (iter != m_Files.end()) {
+ return &iter->second;
+ }
+ return NULL;
+}
+
+
+void FileRegister::unregisterFile(FileEntry &file)
+{
+ bool ignore;
+ // unregister from origin
+ int originID = file.getOrigin(ignore);
+ m_OriginConnection->getByID(originID).removeFile(file.getIndex());
+ const std::vector<int> &alternatives = file.getAlternatives();
+ for (auto iter = alternatives.begin(); iter != alternatives.end(); ++iter) {
+ m_OriginConnection->getByID(*iter).removeFile(file.getIndex());
+ }
+
+ // unregister from directory
+ if (file.getParent() != NULL) {
+ file.getParent()->removeFile(file.getIndex());
+ }
+}
+
+
+void FileRegister::removeFile(FileEntry::Index index)
+{
+ auto iter = m_Files.find(index);
+ if (iter != m_Files.end()) {
+ unregisterFile(iter->second);
+ m_Files.erase(index);
+ }
+}
+
+void FileRegister::removeOrigin(FileEntry::Index index, int originID)
+{
+ auto iter = m_Files.find(index);
+ if (iter != m_Files.end()) {
+ if (iter->second.removeOrigin(originID)) {
+ unregisterFile(iter->second);
+ }
+ }
+}
+
+void FileRegister::sortOrigins()
+{
+ for (auto iter = m_Files.begin(); iter != m_Files.end(); ++iter) {
+ iter->second.sortOrigins();
+ }
+}
diff --git a/src/textviewer.h b/src/textviewer.h
deleted file mode 100644
index c0d29607..00000000
--- a/src/textviewer.h
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
-Copyright (C) 2012 Sebastian Herbord. All rights reserved.
-
-This file is part of Mod Organizer.
-
-Mod Organizer is free software: you can redistribute it and/or modify
-it under the terms of the GNU General Public License as published by
-the Free Software Foundation, either version 3 of the License, or
-(at your option) any later version.
-
-Mod Organizer is distributed in the hope that it will be useful,
-but WITHOUT ANY WARRANTY; without even the implied warranty of
-MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-GNU General Public License for more details.
-
-You should have received a copy of the GNU General Public License
-along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
-*/
-
-#ifndef TEXTVIEWER_H
-#define TEXTVIEWER_H
-
-#include <QDialog>
-#include <QTabWidget>
-#include <finddialog.h>
-#include <set>
-#include "loghighlighter.h"
-
-namespace Ui {
- class TextViewer;
-}
-
-
-/**
- * @brief tabbed text editor
- **/
-class TextViewer : public QDialog
-{
- Q_OBJECT
-
-public:
-
- /**
- * @brief constructor
- * @param parent parent widget
- **/
- explicit TextViewer(QWidget *parent = 0);
-
- ~TextViewer();
-
- /**
- * @brief set the description text to inform the user what he's editing
- *
- * @param description the description to set
- **/
- void setDescription(const QString &description);
-
- /**
- * @brief add a new tab with the specified file open
- *
- * @param fileName name of the file to open
- * @param writable if true, the file can be modified
- **/
- void addFile(const QString &fileName, bool writable);
-
-protected:
-
- void closeEvent(QCloseEvent *event);
- bool eventFilter(QObject *obj, QEvent *event);
-
-private slots:
-
- void saveFile();
- void modified();
- void patternChanged(QString newPattern);
- void findNext();
-
-private:
-
- void saveFile(const QTextEdit *editor);
- void find();
-
-private:
-
- Ui::TextViewer *ui;
- QTabWidget *m_EditorTabs;
- std::set<QTextEdit*> m_Modified;
- FindDialog *m_FindDialog;
- QString m_FindPattern;
-
-};
-
-#endif // TEXTVIEWER_H
diff --git a/src/version.rc b/src/version.rc
index c1f85d9f..bc952c2a 100644
--- a/src/version.rc
+++ b/src/version.rc
@@ -1,7 +1,7 @@
#include "Winver.h"
-#define VER_FILEVERSION 0,12,6,0
-#define VER_FILEVERSION_STR 0,12,6,0
+#define VER_FILEVERSION 0,12,7,0
+#define VER_FILEVERSION_STR 0,12,7,0
VS_VERSION_INFO VERSIONINFO
FILEVERSION VER_FILEVERSION