diff options
Diffstat (limited to 'src')
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 ¤tDirectory, 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 ¤tDirectory)
-{
- 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 ¤tDirectory, 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 ¤tDirectory, 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 ¤tDirectory) +{ + 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 ¤tDirectory, 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 ¤tDirectory);
-
- 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 ¤tDirectory, const QString &steamAppID);
- void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir ¤tDirectory = QDir(), bool closeAfterStart = true, const QString &steamAppID = "");
-
- void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const 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 ¤tDirectory); + + 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 ¤tDirectory, const QString &steamAppID); + void spawnBinary(const QFileInfo &binary, const QString &arguments = "", const QDir ¤tDirectory = QDir(), bool closeAfterStart = true, const QString &steamAppID = ""); + + void updateTo(QTreeWidgetItem *subTree, const std::wstring &directorySoFar, const 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 ¬es)
-{
- 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 ¬es) +{ + 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 ¬es) = 0;
-
- /**
- * @brief set/change the nexus mod id of this mod
- *
- * @param modID the nexus mod id
- **/
- virtual void setNexusID(int modID) = 0;
-
- /**
- * @brief set/change the version of this mod
- * @param version new version of the mod
- */
- virtual void setVersion(const 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 ¬es);
-
- /**
- * @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 ¬es) = 0; + + /** + * @brief set/change the nexus mod id of this mod + * + * @param modID the nexus mod id + **/ + virtual void setNexusID(int modID) = 0; + + /** + * @brief set/change the version of this mod + * @param version new version of the mod + */ + virtual void setVersion(const 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 ¬es); + + /** + * @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
|
