From f8c683f700a3fff30540ff343df2bfba15080e86 Mon Sep 17 00:00:00 2001 From: Tannin Date: Thu, 20 Jun 2013 21:55:37 +0200 Subject: - some fixes for qt5 compatibility - hook.dll no longer creates a dump and uninstalls it if an exception is reported that doesn't originate from it - NCC used read-only transactions again because otherwise solid archives become unusably slow. - removed the integrated nexus browser - the mod description and motd are now rendered in QTextBrowser. This (and the above) eliminates the dependency on qtwebkit - removed the direct file download for mod files - reduced CPU usage during downloads by invalidating only one column of the download list. This widget still needs to be replaced - added the complete filename as an option for the modname - applications that require elevation can now be started by invoking an elevated secondary ModOrganizer instance - MO will now register nexus file servers and provides a settings dialog to pick preferred servers. (This preference is not used yet) - worked around 1-2 bugs in QSortFilterProxy - handling of nxm links is now done by an external application. This allows the registration of different applications depending on the game - integrated fomod installer now displays the screenshot in a scalable view - bugfix: integrated fomod installer didn't name output files correctly if the name differs from the source name - bugfix: a successful login to nexus was (sometimes?) not correctly detected as a success - bugfix: top-level entries in QtGroupingProxy were sometimes incorrectly displayed as groups - bugfix: GetPrivateProfile... optimization could cause null-pointer indirection - bugfix: GetCurrentWorkingDirectory caused buffer overflow in case of pre-flighting (buffer size 0) - bugfix: configurator plugin now also uses qt5 (it's currently broken though) --- src/ModOrganizer.pro | 3 +- src/bbcode.cpp | 382 ++++++++++++------------- src/bbcode.h | 42 +-- src/downloadlist.cpp | 175 ++++++------ src/downloadlistwidget.cpp | 2 + src/downloadlistwidgetcompact.cpp | 3 +- src/downloadmanager.cpp | 10 +- src/downloadmanager.h | 2 + src/installationmanager.cpp | 2 + src/main.cpp | 18 +- src/mainwindow.cpp | 89 +++--- src/mainwindow.h | 7 +- src/modeltest.cpp | 583 ++++++++++++++++++++++++++++++++++++++ src/modeltest.h | 94 ++++++ src/modinfodialog.cpp | 5 +- src/modinfodialog.ui | 38 +-- src/modlist.cpp | 18 +- src/modlist.h | 7 +- src/modlistsortproxy.h | 12 + src/motddialog.cpp | 5 +- src/motddialog.ui | 52 +--- src/nexusdialog.cpp | 339 ---------------------- src/nexusdialog.h | 158 ----------- src/nexusdialog.ui | 306 -------------------- src/nexusinterface.h | 9 - src/nexusview.cpp | 121 -------- src/nexusview.h | 95 ------- src/nxmaccessmanager.cpp | 7 +- src/nxmaccessmanager.h | 1 - src/nxmurl.cpp | 34 --- src/nxmurl.h | 64 ----- src/organizer.pro | 17 +- src/qtgroupingproxy.cpp | 18 +- src/settings.cpp | 107 +++++-- src/settings.h | 18 +- src/settingsdialog.ui | 75 +++-- src/shared/windows_error.h | 2 +- src/spawn.cpp | 41 ++- src/spawn.h | 67 +++-- src/version.rc | 4 +- 40 files changed, 1356 insertions(+), 1676 deletions(-) create mode 100644 src/modeltest.cpp create mode 100644 src/modeltest.h delete mode 100644 src/nexusdialog.cpp delete mode 100644 src/nexusdialog.h delete mode 100644 src/nexusdialog.ui delete mode 100644 src/nexusview.cpp delete mode 100644 src/nexusview.h delete mode 100644 src/nxmurl.cpp delete mode 100644 src/nxmurl.h (limited to 'src') diff --git a/src/ModOrganizer.pro b/src/ModOrganizer.pro index 01c9c146..31f135b3 100644 --- a/src/ModOrganizer.pro +++ b/src/ModOrganizer.pro @@ -10,7 +10,8 @@ SUBDIRS = bsatk \ archive \ helper \ plugins \ - proxydll + proxydll \ + nxmhandler hookdll.depends = shared organizer.depends = shared, uibase, plugins diff --git a/src/bbcode.cpp b/src/bbcode.cpp index 5f134d78..fe7decfd 100644 --- a/src/bbcode.cpp +++ b/src/bbcode.cpp @@ -17,193 +17,195 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#include "bbcode.h" - -#include -#include -#include - - -namespace BBCode { - - -class BBCodeMap { - - typedef std::map > TagMap; - -public: - - static BBCodeMap &instance() { - static BBCodeMap s_Instance; - return s_Instance; - } - - QString convertTag(const QString &input, int &length) - { - // extract the tag name - m_TagNameExp.indexIn(input, 1, QRegExp::CaretAtOffset); - QString tagName = m_TagNameExp.cap(0).toLower(); - //qDebug("tag name %s", tagName.toUtf8().constData()); - TagMap::iterator tagIter = m_TagMap.find(tagName); - if (tagIter != m_TagMap.end()) { - // recognized tag - if (tagName.endsWith('=')) { - tagName.chop(1); - } - QString closeTag = tagName == "*" ? "
" - : QString("[/%1]").arg(tagName); - - int closeTagPos = input.indexOf(closeTag, 0, Qt::CaseInsensitive); - //qDebug("close tag %s at %d", closeTag.toUtf8().constData(), closeTagPos); - - if (closeTagPos > -1) { - length = closeTagPos + closeTag.length(); - QString temp = input.mid(0, length); - if (tagIter->second.first.indexIn(temp) == 0) { - return temp.replace(tagIter->second.first, tagIter->second.second); - } else { - // expression doesn't match. either the input string is invalid - // or the expression is - qWarning("%s doesn't match the expression for %s", - temp.toUtf8().constData(), tagName.toUtf8().constData()); - length = 0; - return QString(); - } - } - } - - // not a recognized tag or tag invalid - length = 0; - return QString(); - } - -private: - BBCodeMap() - : m_TagNameExp("^[a-zA-Z*]*=?") - { - m_TagMap["b"] = std::make_pair(QRegExp("\\[b\\](.*)\\[/b\\]"), - "\\1"); - m_TagMap["i"] = std::make_pair(QRegExp("\\[i\\](.*)\\[/i\\]"), - "\\1"); - m_TagMap["u"] = std::make_pair(QRegExp("\\[u\\](.*)\\[/u\\]"), - "\\1"); - m_TagMap["s"] = std::make_pair(QRegExp("\\[s\\](.*)\\[/s\\]"), - "\\1"); - m_TagMap["sub"] = std::make_pair(QRegExp("\\[sub\\](.*)\\[/sub\\]"), - "\\1"); - m_TagMap["sup"] = std::make_pair(QRegExp("\\[sup\\](.*)\\[/sup\\]"), - "\\1"); - m_TagMap["size="] = std::make_pair(QRegExp("\\[size=([^\\]]*)\\](.*)\\[/size\\]"), - "\\2"); - m_TagMap["color="] = std::make_pair(QRegExp("\\[color=([^\\]]*)\\](.*)\\[/color\\]"), - "\\2"); - m_TagMap["font="] = std::make_pair(QRegExp("\\[font=([^\\]]*)\\](.*)\\[/font\\]"), - "\\2"); - m_TagMap["center"] = std::make_pair(QRegExp("\\[center\\](.*)\\[/center\\]"), - "
\\1
"); - m_TagMap["quote"] = std::make_pair(QRegExp("\\[quote\\](.*)\\[/quote\\]"), - "
\"\\1\"
"); - m_TagMap["quote="] = std::make_pair(QRegExp("\\[quote=([^\\]]*)\\](.*)\\[/quote\\]"), - "
\"\\2\"
--\\1

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

\\1

"); - - // lists - m_TagMap["list"] = std::make_pair(QRegExp("\\[list\\](.*)\\[/list\\]"), - "
    \\1
"); - m_TagMap["list="] = std::make_pair(QRegExp("\\[list.*\\](.*)\\[/list\\]"), - "
    \\1
"); - m_TagMap["ul"] = std::make_pair(QRegExp("\\[ul\\](.*)\\[/ul\\]"), - "
    \\1
"); - m_TagMap["ol"] = std::make_pair(QRegExp("\\[ol\\](.*)\\[/ol\\]"), - "
    \\1
"); - m_TagMap["li"] = std::make_pair(QRegExp("\\[li\\](.*)\\[/li\\]"), - "
  • \\1
  • "); - m_TagMap["*"] = std::make_pair(QRegExp("\\[\\*\\](.*)
    "), - "
  • \\1
  • "); - - // tables - m_TagMap["table"] = std::make_pair(QRegExp("\\[table\\](.*)\\[/table\\]"), - "\\1
    "); - m_TagMap["tr"] = std::make_pair(QRegExp("\\[tr\\](.*)\\[/tr\\]"), - "\\1"); - m_TagMap["th"] = std::make_pair(QRegExp("\\[th\\](.*)\\[/th\\]"), - "\\1"); - m_TagMap["td"] = std::make_pair(QRegExp("\\[td\\](.*)\\[/td\\]"), - "\\1"); - - // web content - m_TagMap["url"] = std::make_pair(QRegExp("\\[url\\](.*)\\[/url\\]"), - "\\1"); - m_TagMap["url="] = std::make_pair(QRegExp("\\[url=([^\\]]*)\\](.*)\\[/url\\]"), - "\\2"); - m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), - ""); - m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), - ""); - m_TagMap["email="] = std::make_pair(QRegExp("\\[email=\"?([^\\]]*)\"?\\](.*)\\[/email\\]"), - "\\2"); - m_TagMap["youtube"] = std::make_pair(QRegExp("\\[youtube\\](.*)\\[/youtube\\]"), - "http://www.youtube.com/v/\\1"); - - // make all patterns non-greedy and case-insensitive - for (TagMap::iterator iter = m_TagMap.begin(); iter != m_TagMap.end(); ++iter) { - iter->second.first.setCaseSensitivity(Qt::CaseInsensitive); - iter->second.first.setMinimal(true); - } - } - - -private: - QRegExp m_TagNameExp; - TagMap m_TagMap; -}; - - -QString convertToHTML(const QString &inputParam) -{ - // this code goes over the input string once and replaces all bbtags - // it encounters. This function is called recursively for every replaced - // string to convert nested tags. - // - // This could be implemented simpler by applying a set of regular expressions - // for each recognized bb-tag one after the other but that would probably be - // very inefficient (O(n^2)). - - QString input = inputParam.mid(0).replace("\r\n", "
    "); - input.replace("\\\"", "\"").replace("\\'", "'"); - - QString result; - int lastBlock = 0; - int pos = 0; - - // iterate over the input buffer - while ((pos = input.indexOf('[', lastBlock)) != -1) { - // append everything between the previous tag-block and the current one - result.append(input.midRef(lastBlock, pos - lastBlock)); - - // convert the tag and content if necessary - int length = -1; - QString replacement = BBCodeMap::instance().convertTag(input.mid(pos), length); - if (length != 0) { - QString temp = convertToHTML(replacement); - result.append(temp); - // length contains the number of characters in the original tag - pos += length; - } else { - // nothing replaced - result.append('['); - ++pos; - } - lastBlock = pos; - } - - // append the remainder (everything after the last tag) - result.append(input.midRef(lastBlock)); - return result; -} - -} // namespace BBCode - +#include "bbcode.h" + +#include +#include +#include + + +namespace BBCode { + + +class BBCodeMap { + + typedef std::map > TagMap; + +public: + + static BBCodeMap &instance() { + static BBCodeMap s_Instance; + return s_Instance; + } + + QString convertTag(const QString &input, int &length) + { + // extract the tag name + m_TagNameExp.indexIn(input, 1, QRegExp::CaretAtOffset); + QString tagName = m_TagNameExp.cap(0).toLower(); + //qDebug("tag name %s", tagName.toUtf8().constData()); + TagMap::iterator tagIter = m_TagMap.find(tagName); + if (tagIter != m_TagMap.end()) { + // recognized tag + if (tagName.endsWith('=')) { + tagName.chop(1); + } + QString closeTag = tagName == "*" ? "
    " + : QString("[/%1]").arg(tagName); + + int closeTagPos = input.indexOf(closeTag, 0, Qt::CaseInsensitive); + //qDebug("close tag %s at %d", closeTag.toUtf8().constData(), closeTagPos); + + if (closeTagPos > -1) { + length = closeTagPos + closeTag.length(); + QString temp = input.mid(0, length); + if (tagIter->second.first.indexIn(temp) == 0) { + return temp.replace(tagIter->second.first, tagIter->second.second); + } else { + // expression doesn't match. either the input string is invalid + // or the expression is + qWarning("%s doesn't match the expression for %s", + temp.toUtf8().constData(), tagName.toUtf8().constData()); + length = 0; + return QString(); + } + } + } + + // not a recognized tag or tag invalid + length = 0; + return QString(); + } + +private: + BBCodeMap() + : m_TagNameExp("^[a-zA-Z*]*=?") + { + m_TagMap["b"] = std::make_pair(QRegExp("\\[b\\](.*)\\[/b\\]"), + "\\1"); + m_TagMap["i"] = std::make_pair(QRegExp("\\[i\\](.*)\\[/i\\]"), + "\\1"); + m_TagMap["u"] = std::make_pair(QRegExp("\\[u\\](.*)\\[/u\\]"), + "\\1"); + m_TagMap["s"] = std::make_pair(QRegExp("\\[s\\](.*)\\[/s\\]"), + "\\1"); + m_TagMap["sub"] = std::make_pair(QRegExp("\\[sub\\](.*)\\[/sub\\]"), + "\\1"); + m_TagMap["sup"] = std::make_pair(QRegExp("\\[sup\\](.*)\\[/sup\\]"), + "\\1"); + m_TagMap["size="] = std::make_pair(QRegExp("\\[size=([^\\]]*)\\](.*)\\[/size\\]"), + "\\2"); + m_TagMap["color="] = std::make_pair(QRegExp("\\[color=([^\\]]*)\\](.*)\\[/color\\]"), + "\\2"); + m_TagMap["font="] = std::make_pair(QRegExp("\\[font=([^\\]]*)\\](.*)\\[/font\\]"), + "\\2"); + m_TagMap["center"] = std::make_pair(QRegExp("\\[center\\](.*)\\[/center\\]"), + "
    \\1
    "); + m_TagMap["quote"] = std::make_pair(QRegExp("\\[quote\\](.*)\\[/quote\\]"), + "
    \"\\1\"
    "); + m_TagMap["quote="] = std::make_pair(QRegExp("\\[quote=([^\\]]*)\\](.*)\\[/quote\\]"), + "
    \"\\2\"
    --\\1

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

    \\1

    "); + + // lists + m_TagMap["list"] = std::make_pair(QRegExp("\\[list\\](.*)\\[/list\\]"), + "
      \\1
    "); + m_TagMap["list="] = std::make_pair(QRegExp("\\[list.*\\](.*)\\[/list\\]"), + "
      \\1
    "); + m_TagMap["ul"] = std::make_pair(QRegExp("\\[ul\\](.*)\\[/ul\\]"), + "
      \\1
    "); + m_TagMap["ol"] = std::make_pair(QRegExp("\\[ol\\](.*)\\[/ol\\]"), + "
      \\1
    "); + m_TagMap["li"] = std::make_pair(QRegExp("\\[li\\](.*)\\[/li\\]"), + "
  • \\1
  • "); + m_TagMap["*"] = std::make_pair(QRegExp("\\[\\*\\](.*)
    "), + "
  • \\1
  • "); + + // tables + m_TagMap["table"] = std::make_pair(QRegExp("\\[table\\](.*)\\[/table\\]"), + "\\1
    "); + m_TagMap["tr"] = std::make_pair(QRegExp("\\[tr\\](.*)\\[/tr\\]"), + "\\1"); + m_TagMap["th"] = std::make_pair(QRegExp("\\[th\\](.*)\\[/th\\]"), + "\\1"); + m_TagMap["td"] = std::make_pair(QRegExp("\\[td\\](.*)\\[/td\\]"), + "\\1"); + + // web content + m_TagMap["url"] = std::make_pair(QRegExp("\\[url\\](.*)\\[/url\\]"), + "\\1"); + m_TagMap["url="] = std::make_pair(QRegExp("\\[url=([^\\]]*)\\](.*)\\[/url\\]"), + "\\2"); +/* m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), + ""); + m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), + "");*/ + m_TagMap["img"] = std::make_pair(QRegExp("\\[img\\](.*)\\[/img\\]"), ""); + m_TagMap["img="] = std::make_pair(QRegExp("\\[img=([^\\]]*)\\](.*)\\[/img\\]"), ""); + m_TagMap["email="] = std::make_pair(QRegExp("\\[email=\"?([^\\]]*)\"?\\](.*)\\[/email\\]"), + "\\2"); + m_TagMap["youtube"] = std::make_pair(QRegExp("\\[youtube\\](.*)\\[/youtube\\]"), + "http://www.youtube.com/v/\\1"); + + // make all patterns non-greedy and case-insensitive + for (TagMap::iterator iter = m_TagMap.begin(); iter != m_TagMap.end(); ++iter) { + iter->second.first.setCaseSensitivity(Qt::CaseInsensitive); + iter->second.first.setMinimal(true); + } + } + + +private: + QRegExp m_TagNameExp; + TagMap m_TagMap; +}; + + +QString convertToHTML(const QString &inputParam) +{ + // this code goes over the input string once and replaces all bbtags + // it encounters. This function is called recursively for every replaced + // string to convert nested tags. + // + // This could be implemented simpler by applying a set of regular expressions + // for each recognized bb-tag one after the other but that would probably be + // very inefficient (O(n^2)). + + QString input = inputParam.mid(0).replace("\r\n", "
    "); + input.replace("\\\"", "\"").replace("\\'", "'"); + + QString result; + int lastBlock = 0; + int pos = 0; + + // iterate over the input buffer + while ((pos = input.indexOf('[', lastBlock)) != -1) { + // append everything between the previous tag-block and the current one + result.append(input.midRef(lastBlock, pos - lastBlock)); + + // convert the tag and content if necessary + int length = -1; + QString replacement = BBCodeMap::instance().convertTag(input.mid(pos), length); + if (length != 0) { + QString temp = convertToHTML(replacement); + result.append(temp); + // length contains the number of characters in the original tag + pos += length; + } else { + // nothing replaced + result.append('['); + ++pos; + } + lastBlock = pos; + } + + // append the remainder (everything after the last tag) + result.append(input.midRef(lastBlock)); + return result; +} + +} // namespace BBCode + diff --git a/src/bbcode.h b/src/bbcode.h index eec8e657..0d4d8003 100644 --- a/src/bbcode.h +++ b/src/bbcode.h @@ -17,24 +17,24 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#ifndef BBCODE_H -#define BBCODE_H - - -#include - - -namespace BBCode { - -/** - * @brief convert a string with BB Code-Tags to HTML - * @param input the input string with BB tags - * @param replaceOccured if not NULL, this parameter will be set to true if any bb tags were replaced - * @return the same string in html representation - **/ -QString convertToHTML(const QString &input); - -} - - -#endif // BBCODE_H +#ifndef BBCODE_H +#define BBCODE_H + + +#include + + +namespace BBCode { + +/** + * @brief convert a string with BB Code-Tags to HTML + * @param input the input string with BB tags + * @param replaceOccured if not NULL, this parameter will be set to true if any bb tags were replaced + * @return the same string in html representation + **/ +QString convertToHTML(const QString &input); + +} + + +#endif // BBCODE_H diff --git a/src/downloadlist.cpp b/src/downloadlist.cpp index a7f0869f..305aee91 100644 --- a/src/downloadlist.cpp +++ b/src/downloadlist.cpp @@ -17,90 +17,91 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#include "downloadlist.h" -#include "downloadmanager.h" -#include - -#include - - -DownloadList::DownloadList(DownloadManager *manager, QObject *parent) - : QAbstractTableModel(parent), m_Manager(manager) -{ - connect(m_Manager, SIGNAL(update(int)), this, SLOT(update(int))); - connect(m_Manager, SIGNAL(aboutToUpdate()), this, SLOT(aboutToUpdate())); -} - - -int DownloadList::rowCount(const QModelIndex&) const -{ - return m_Manager->numTotalDownloads(); -} - -int DownloadList::columnCount(const QModelIndex&) const -{ - return 3; -} - - -QModelIndex DownloadList::index(int row, int column, const QModelIndex&) const -{ - return createIndex(row, column, row); -} - - -QModelIndex DownloadList::parent(const QModelIndex&) const -{ - return QModelIndex(); -} - - -QVariant DownloadList::headerData(int section, Qt::Orientation orientation, int role) const -{ - if ((role == Qt::DisplayRole) && - (orientation == Qt::Horizontal)) { - switch (section) { - case 0: return tr("Name"); - case 1: return tr("Filetime"); - default: return tr("Done"); - } - } else { - return QAbstractItemModel::headerData(section, orientation, role); - } -} - - -QVariant DownloadList::data(const QModelIndex &index, int role) const -{ - if (role == Qt::DisplayRole) { - return index.row(); - } else if (role == Qt::ToolTipRole) { - if (m_Manager->isInfoIncomplete(index.row())) { - return tr("Information missing, please select \"Query Info\" from the context menu to re-retrieve."); - } else { - NexusInfo info = m_Manager->getNexusInfo(index.row()); - return QString("%1 (ID %2) %3").arg(info.m_ModName).arg(m_Manager->getModID(index.row())).arg(info.m_Version); - } - } else { - return QVariant(); - } -} - - -void DownloadList::aboutToUpdate() -{ - emit beginResetModel(); -} - - -void DownloadList::update(int row) -{ - if (row < 0) { - emit endResetModel(); - } else if (row < this->rowCount()) { - emit dataChanged(this->index(row, 0, QModelIndex()), this->index(row, 1, QModelIndex())); - } else { - qCritical("invalid row %d in download list, update failed", row); - } -} - +#include "downloadlist.h" +#include "downloadmanager.h" +#include + +#include + + +DownloadList::DownloadList(DownloadManager *manager, QObject *parent) + : QAbstractTableModel(parent), m_Manager(manager) +{ + connect(m_Manager, SIGNAL(update(int)), this, SLOT(update(int))); + connect(m_Manager, SIGNAL(aboutToUpdate()), this, SLOT(aboutToUpdate())); +} + + +int DownloadList::rowCount(const QModelIndex&) const +{ + return m_Manager->numTotalDownloads(); +} + +int DownloadList::columnCount(const QModelIndex&) const +{ + return 3; +} + + +QModelIndex DownloadList::index(int row, int column, const QModelIndex&) const +{ + return createIndex(row, column, row); +} + + +QModelIndex DownloadList::parent(const QModelIndex&) const +{ + return QModelIndex(); +} + + +QVariant DownloadList::headerData(int section, Qt::Orientation orientation, int role) const +{ + if ((role == Qt::DisplayRole) && + (orientation == Qt::Horizontal)) { + switch (section) { + case 0: return tr("Name"); + case 1: return tr("Filetime"); + default: return tr("Done"); + } + } else { + return QAbstractItemModel::headerData(section, orientation, role); + } +} + + +QVariant DownloadList::data(const QModelIndex &index, int role) const +{ + if (role == Qt::DisplayRole) { + return index.row(); + } else if (role == Qt::ToolTipRole) { + if (m_Manager->isInfoIncomplete(index.row())) { + return tr("Information missing, please select \"Query Info\" from the context menu to re-retrieve."); + } else { + NexusInfo info = m_Manager->getNexusInfo(index.row()); + return QString("%1 (ID %2) %3").arg(info.m_ModName).arg(m_Manager->getModID(index.row())).arg(info.m_Version); + } + } else { + return QVariant(); + } +} + + +void DownloadList::aboutToUpdate() +{ + emit beginResetModel(); +} + + +void DownloadList::update(int row) +{ + if (row < 0) { + emit endResetModel(); + } else if (row < this->rowCount()) { +#pragma message("updating only the one column is a hack") + emit dataChanged(this->index(row, 2, QModelIndex()), this->index(row, 2, QModelIndex())); + } else { + qCritical("invalid row %d in download list, update failed", row); + } +} + diff --git a/src/downloadlistwidget.cpp b/src/downloadlistwidget.cpp index 09187489..656b224d 100644 --- a/src/downloadlistwidget.cpp +++ b/src/downloadlistwidget.cpp @@ -60,6 +60,8 @@ DownloadListWidgetDelegate::~DownloadListWidgetDelegate() void DownloadListWidgetDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { try { + if (index.column() != 2) return; + m_ItemWidget->resize(QSize(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2), option.rect.height())); int downloadIndex = index.data().toInt(); diff --git a/src/downloadlistwidgetcompact.cpp b/src/downloadlistwidgetcompact.cpp index d4491dfb..68ed6a00 100644 --- a/src/downloadlistwidgetcompact.cpp +++ b/src/downloadlistwidgetcompact.cpp @@ -59,8 +59,9 @@ DownloadListWidgetCompactDelegate::~DownloadListWidgetCompactDelegate() void DownloadListWidgetCompactDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const { +#pragma message("This is quite costy - room for optimization?") + if (index.column() != 2) return; try { -// m_ItemWidget->resize(option.rect.size()); m_ItemWidget->resize(QSize(m_View->columnWidth(0) + m_View->columnWidth(1) + m_View->columnWidth(2), option.rect.height())); if (index.row() % 2 == 1) { m_ItemWidget->setBackgroundRole(QPalette::AlternateBase); diff --git a/src/downloadmanager.cpp b/src/downloadmanager.cpp index 82a6cb9f..662034c2 100644 --- a/src/downloadmanager.cpp +++ b/src/downloadmanager.cpp @@ -21,6 +21,7 @@ along with Mod Organizer. If not, see . #include "report.h" #include "nxmurl.h" #include +#include #include "utility.h" #include "json.h" #include "selectiondialog.h" @@ -673,7 +674,8 @@ void DownloadManager::setState(DownloadManager::DownloadInfo *info, DownloadMana DownloadManager::DownloadInfo *DownloadManager::findDownload(QObject *reply, int *index) const { - for (int i = 0; i < m_ActiveDownloads.size(); ++i) { + // reverse search as newer, thus more relevant, downloads are at the end + for (int i = m_ActiveDownloads.size() - 1; i >= 0; --i) { if (m_ActiveDownloads[i]->m_Reply == reply) { if (index != NULL) { *index = i; @@ -702,8 +704,11 @@ void DownloadManager::downloadProgress(qint64 bytesReceived, qint64 bytesTotal) qDebug("file size %s: %lld", qPrintable(info->m_FileName), bytesTotal); info->m_TotalSize = bytesTotal; } + int oldProgress = info->m_Progress; info->m_Progress = ((info->m_ResumePos + bytesReceived) * 100) / (info->m_ResumePos + bytesTotal); - emit update(index); + if (oldProgress != info->m_Progress) { + emit update(index); + } } } } @@ -972,7 +977,6 @@ void DownloadManager::downloadFinished() 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(); diff --git a/src/downloadmanager.h b/src/downloadmanager.h index e44a0be2..07219f08 100644 --- a/src/downloadmanager.h +++ b/src/downloadmanager.h @@ -107,6 +107,8 @@ private: **/ void setName(QString newName, bool renameFile); + unsigned int downloadID() { return m_DownloadID; } + bool isPausedState(); QString currentURL(); diff --git a/src/installationmanager.cpp b/src/installationmanager.cpp index 7b09c7b6..38173d8b 100644 --- a/src/installationmanager.cpp +++ b/src/installationmanager.cpp @@ -640,6 +640,8 @@ bool InstallationManager::install(const QString &fileName, GuessedValue modName.setFilter(&fixDirectoryName); + modName.update(QFileInfo(fileName).completeBaseName(), GUESS_FALLBACK); + // read out meta information from the download if available int modID = 0; QString version = ""; diff --git a/src/main.cpp b/src/main.cpp index 93b9d5c6..f93e9dec 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -179,7 +179,17 @@ void cleanupDir() "QtNetwork4.dll", "QtXml4.dll", "QtWebKit4.dll", - "qjpeg4.dll" + "qjpeg4.dll", + "dlls/phonon4.dll", + "dlls/QtCore4.dll", + "dlls/QtGui4.dll", + "dlls/QtNetwork4.dll", + "dlls/QtXml4.dll", + "dlls/QtXmlPatterns4.dll", + "dlls/QtWebKit4.dll", + "dlls/QtDeclarative4.dll", + "dlls/QtScript4.dll", + "dlls/QtSql4.dll" }; static const int NUM_FILES = sizeof(fileNames) / sizeof(QString); @@ -304,14 +314,14 @@ int main(int argc, char *argv[]) QStringList arguments = application.arguments(); - bool update = false; + bool forcePrimary = false; if (arguments.contains("update")) { arguments.removeAll("update"); - update = true; + forcePrimary = true; } try { - SingleInstance instance(update); + SingleInstance instance(forcePrimary); if (!instance.primaryInstance()) { if ((arguments.size() == 2) && isNxmLink(arguments.at(1))) { qDebug("not primary instance, sending download message"); diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 509be36d..9132751b 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -126,7 +126,7 @@ static bool isOnline() #ifdef TEST_MODELS -#include +#include "modeltest.h" #endif // TEST_MODELS @@ -134,9 +134,8 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget : 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_ModListSortProxy(NULL), + m_ModList(NexusInterface::instance()), m_ModListGroupingProxy(NULL), m_ModListSortProxy(NULL), 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()), @@ -262,10 +261,12 @@ MainWindow::MainWindow(const QString &exeName, QSettings &initSettings, QWidget connect(&m_Updater, SIGNAL(updateAvailable()), this, SLOT(updateAvailable())); connect(&m_Updater, SIGNAL(motdAvailable(QString)), this, SLOT(motdReceived(QString))); - connect(&m_NexusDialog, SIGNAL(requestDownload(QNetworkReply*, int, QString)), this, SLOT(downloadRequested(QNetworkReply*, int, QString))); + connect(ExitProxy::instance(), SIGNAL(exit()), this, SLOT(close())); + connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginSuccessful(bool)), this, SLOT(loginSuccessful(bool))); connect(NexusInterface::instance()->getAccessManager(), SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString))); connect(NexusInterface::instance(), SIGNAL(requestNXMDownload(QString)), this, SLOT(downloadRequestedNXM(QString))); + connect(NexusInterface::instance(), SIGNAL(nxmDownloadURLsAvailable(int,int,QVariant,QVariant,int)), this, SLOT(nxmDownloadURLs(int,int,QVariant,QVariant,int))); connect(&TutorialManager::instance(), SIGNAL(windowTutorialFinished(QString)), this, SLOT(windowTutorialFinished(QString))); @@ -302,7 +303,6 @@ MainWindow::~MainWindow() { m_RefresherThread.exit(); m_RefresherThread.wait(); - m_NexusDialog.close(); delete ui; delete m_GameInfo; } @@ -750,8 +750,6 @@ void MainWindow::closeEvent(QCloseEvent* event) setCursor(Qt::WaitCursor); - m_NexusDialog.close(); - storeSettings(); // profile has to be cleaned up before the modinfo-buffer is cleared @@ -774,12 +772,6 @@ void MainWindow::createFirstProfile() } -void MainWindow::setBrowserGeometry(const QByteArray &geometry) -{ - m_NexusDialog.restoreGeometry(geometry); -} - - SaveGameGamebryo *MainWindow::getSaveGame(const QString &name) { return new SaveGameGamebryo(this, name); @@ -1496,19 +1488,23 @@ void MainWindow::refreshDirectoryStructure() } +#if QT_VERSION >= 0x050000 +extern QPixmap qt_pixmapFromWinHICON(HICON icon); +#else +#define qt_pixmapFromWinHICON(icon) QPixmap::fromWinHICON(icon) +#endif + QIcon MainWindow::iconForExecutable(const QString &filePath) { -/* HICON winIcon; + HICON winIcon; UINT res = ::ExtractIconExW(ToWString(filePath).c_str(), 0, &winIcon, NULL, 1); if (res == 1) { - QIcon result = QIcon(QPixmap::fromWinHICON(winIcon)); + QIcon result = QIcon(qt_pixmapFromWinHICON(winIcon)); ::DestroyIcon(winIcon); return result; } else { return QIcon(":/MO/gui/executable"); - }*/ - - return QIcon(":/MO/gui/executable"); + } } @@ -1768,10 +1764,6 @@ void MainWindow::readSettings() 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); @@ -1807,7 +1799,6 @@ void MainWindow::storeSettings() 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()); @@ -2593,7 +2584,7 @@ void MainWindow::endorseMod(ModInfo::Ptr mod) QString username, password; if (m_Settings.getNexusLogin(username, password)) { m_PostLoginTasks.push_back(boost::bind(&MainWindow::endorseMod, _1, mod)); - m_NexusDialog.login(username, password); + NexusInterface::instance()->getAccessManager()->login(username, password); } else { MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); } @@ -2620,7 +2611,7 @@ void MainWindow::unendorse_clicked() } else { if (m_Settings.getNexusLogin(username, password)) { m_PostLoginTasks.push_back(boost::mem_fn(&MainWindow::unendorse_clicked)); - m_NexusDialog.login(username, password); + NexusInterface::instance()->getAccessManager()->login(username, password); } else { MessageDialog::showMessage(tr("You need to be logged in with Nexus to endorse"), this); } @@ -2962,7 +2953,6 @@ void MainWindow::saveCategories() int max = INT_MIN; QStringList selectedMods; - for (int i = 0; i < selected.size(); ++i) { QModelIndex temp = mapToModel(&m_ModList, selected.at(i)); selectedMods.append(temp.data().toString()); @@ -3027,7 +3017,7 @@ void MainWindow::checkModsForUpdates() QString username, password; if (m_Settings.getNexusLogin(username, password)) { m_PostLoginTasks.push_back(boost::mem_fn(&MainWindow::checkModsForUpdates)); - m_NexusDialog.login(username, password); + NexusInterface::instance()->getAccessManager()->login(username, password); } else { // otherwise there will be no endorsement info m_ModsToUpdate = ModInfo::checkAllForUpdate(this); } @@ -3442,7 +3432,7 @@ void MainWindow::on_actionSettings_triggered() void MainWindow::on_actionNexus_triggered() { - QString username, password; +/* QString username, password; m_NexusDialog.openUrl(ToQString(GameInfo::instance().getNexusPage())); if (m_Settings.getNexusLogin(username, password)) { @@ -3454,28 +3444,17 @@ void MainWindow::on_actionNexus_triggered() m_NexusDialog.activateWindow(); QTabWidget *tabWidget = findChild("tabWidget"); - tabWidget->setCurrentIndex(4); + tabWidget->setCurrentIndex(4);*/ + + ::ShellExecuteW(NULL, L"open", GameInfo::instance().getNexusPage().c_str(), NULL, NULL, SW_SHOWNORMAL); + ui->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("tabWidget"); - tabWidget->setCurrentIndex(4); - } + ::ShellExecuteW(NULL, L"open", ToWString(link).c_str(), NULL, NULL, SW_SHOWNORMAL); + ui->tabWidget->setCurrentIndex(4); } @@ -4000,7 +3979,7 @@ void MainWindow::nxmUpdatesAvailable(const std::vector &modIDs, QVariant us } } - +/* void MainWindow::nxmEndorsementToggled(int, QVariant, QVariant resultData, int) { if (resultData.toBool()) { @@ -4012,6 +3991,24 @@ void MainWindow::nxmEndorsementToggled(int, QVariant, QVariant resultData, int) this, SLOT(nxmEndorsementToggled(int, QVariant, QVariant, int)))) { qCritical("failed to disconnect endorsement slot"); } +}*/ + +void MainWindow::nxmDownloadURLs(int, int, QVariant, QVariant resultData, int) +{ + QVariantList serverList = resultData.toList(); + + QList servers; + foreach (const QVariant &server, serverList) { + QVariantMap serverInfo = server.toMap(); + ServerInfo info; + info.name = serverInfo["Name"].toString(); + info.premium = serverInfo["IsPremium"].toBool(); + info.lastSeen = QDate::currentDate(); + info.preferred = 0; + // other keys: ConnectedUsers, Country, URI + servers.append(info); + } + m_Settings.updateServers(servers); } diff --git a/src/mainwindow.h b/src/mainwindow.h index 73d174c2..d718c537 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -41,7 +41,6 @@ along with Mod Organizer. If not, see . #include #include #include "settings.h" -#include "nexusdialog.h" #include "downloadmanager.h" #include "installationmanager.h" #include "selfupdater.h" @@ -163,7 +162,6 @@ private: void updateToolBar(); void activateSelectedProfile(); - void setBrowserGeometry(const QByteArray &geometry); void setExecutableIndex(int index); bool nexusLogin(); @@ -250,6 +248,7 @@ private: bool m_Refreshing; ModList m_ModList; + QAbstractItemModel *m_ModListGroupingProxy; ModListSortProxy *m_ModListSortProxy; PluginList m_PluginList; @@ -266,7 +265,6 @@ private: Settings m_Settings; - NexusDialog m_NexusDialog; DownloadManager m_DownloadManager; InstallationManager m_InstallationManager; @@ -384,7 +382,8 @@ private slots: void modlistChanged(int row); void nxmUpdatesAvailable(const std::vector &modIDs, QVariant userData, QVariant resultData, int requestID); - void nxmEndorsementToggled(int, QVariant, QVariant resultData, int); +// void nxmEndorsementToggled(int, QVariant, QVariant resultData, int); + void nxmDownloadURLs(int modID, int fileID, QVariant userData, QVariant resultData, int requestID); void nxmRequestFailed(int modID, QVariant userData, int requestID, const QString &errorString); void editCategories(); diff --git a/src/modeltest.cpp b/src/modeltest.cpp new file mode 100644 index 00000000..b2e495b6 --- /dev/null +++ b/src/modeltest.cpp @@ -0,0 +1,583 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include + +#include "modeltest.h" + +#include + +#undef QVERIFY +#define QVERIFY Q_ASSERT +#undef QCOMPARE +#define QCOMPARE(x, y) Q_ASSERT(x == y) + +Q_DECLARE_METATYPE ( QModelIndex ) + +/*! + Connect to all of the models signals. Whenever anything happens recheck everything. +*/ +ModelTest::ModelTest ( QAbstractItemModel *_model, QObject *parent ) : QObject ( parent ), model ( _model ), fetchingMore ( false ) +{ + if (!model) + qFatal("%s: model must not be null", Q_FUNC_INFO); + + connect ( model, SIGNAL ( columnsAboutToBeInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( columnsAboutToBeRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( columnsInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( columnsRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( dataChanged ( const QModelIndex &, const QModelIndex & ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( headerDataChanged ( Qt::Orientation, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( layoutAboutToBeChanged () ), this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( layoutChanged () ), this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( modelReset () ), this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( rowsAboutToBeInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( rowsAboutToBeRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( rowsInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + connect ( model, SIGNAL ( rowsRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( runAllTests() ) ); + + // Special checks for inserting/removing + connect ( model, SIGNAL ( layoutAboutToBeChanged() ), + this, SLOT ( layoutAboutToBeChanged() ) ); + connect ( model, SIGNAL ( layoutChanged() ), + this, SLOT ( layoutChanged() ) ); + + connect ( model, SIGNAL ( rowsAboutToBeInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( rowsAboutToBeInserted ( const QModelIndex &, int, int ) ) ); + connect ( model, SIGNAL ( rowsAboutToBeRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( rowsAboutToBeRemoved ( const QModelIndex &, int, int ) ) ); + connect ( model, SIGNAL ( rowsInserted ( const QModelIndex &, int, int ) ), + this, SLOT ( rowsInserted ( const QModelIndex &, int, int ) ) ); + connect ( model, SIGNAL ( rowsRemoved ( const QModelIndex &, int, int ) ), + this, SLOT ( rowsRemoved ( const QModelIndex &, int, int ) ) ); + + runAllTests(); +} + +void ModelTest::runAllTests() +{ + if ( fetchingMore ) + return; + nonDestructiveBasicTest(); + rowCount(); + columnCount(); + hasIndex(); + index(); + parent(); + data(); +} + +/*! + nonDestructiveBasicTest tries to call a number of the basic functions (not all) + to make sure the model doesn't outright segfault, testing the functions that makes sense. +*/ +void ModelTest::nonDestructiveBasicTest() +{ + QVERIFY( model->buddy ( QModelIndex() ) == QModelIndex() ); + model->canFetchMore ( QModelIndex() ); + QVERIFY( model->columnCount ( QModelIndex() ) >= 0 ); + QVERIFY( model->data ( QModelIndex() ) == QVariant() ); + fetchingMore = true; + model->fetchMore ( QModelIndex() ); + fetchingMore = false; + Qt::ItemFlags flags = model->flags ( QModelIndex() ); + QVERIFY( flags == Qt::ItemIsDropEnabled || flags == 0 ); + model->hasChildren ( QModelIndex() ); + model->hasIndex ( 0, 0 ); + model->headerData ( 0, Qt::Horizontal ); + model->index ( 0, 0 ); + model->itemData ( QModelIndex() ); + QVariant cache; + model->match ( QModelIndex(), -1, cache ); + model->mimeTypes(); + QVERIFY( model->parent ( QModelIndex() ) == QModelIndex() ); + QVERIFY( model->rowCount() >= 0 ); + QVariant variant; + model->setData ( QModelIndex(), variant, -1 ); + model->setHeaderData ( -1, Qt::Horizontal, QVariant() ); + model->setHeaderData ( 999999, Qt::Horizontal, QVariant() ); + QMap roles; + model->sibling ( 0, 0, QModelIndex() ); + model->span ( QModelIndex() ); + model->supportedDropActions(); +} + +/*! + Tests model's implementation of QAbstractItemModel::rowCount() and hasChildren() + + Models that are dynamically populated are not as fully tested here. + */ +void ModelTest::rowCount() +{ +// qDebug() << "rc"; + // check top row + + QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); + if (!model->hasChildren ( topIndex ) && model->rowCount(topIndex) > 0) { + qDebug() << "it's gonna blow: " << topIndex; + } + + int rows = model->rowCount ( topIndex ); + QVERIFY( rows >= 0 ); + if ( rows > 0 ) { + QVERIFY( model->hasChildren ( topIndex ) ); + } + + QModelIndex secondLevelIndex = model->index ( 0, 0, topIndex ); + if ( secondLevelIndex.isValid() ) { // not the top level + // check a row count where parent is valid + rows = model->rowCount ( secondLevelIndex ); + QVERIFY( rows >= 0 ); + if ( rows > 0 ) + QVERIFY( model->hasChildren ( secondLevelIndex ) ); + } + + // The models rowCount() is tested more extensively in checkChildren(), + // but this catches the big mistakes +} + +/*! + Tests model's implementation of QAbstractItemModel::columnCount() and hasChildren() + */ +void ModelTest::columnCount() +{ + // check top row + QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); + QVERIFY( model->columnCount ( topIndex ) >= 0 ); + + // check a column count where parent is valid + QModelIndex childIndex = model->index ( 0, 0, topIndex ); + if ( childIndex.isValid() ) + QVERIFY( model->columnCount ( childIndex ) >= 0 ); + + // columnCount() is tested more extensively in checkChildren(), + // but this catches the big mistakes +} + +/*! + Tests model's implementation of QAbstractItemModel::hasIndex() + */ +void ModelTest::hasIndex() +{ +// qDebug() << "hi"; + // Make sure that invalid values returns an invalid index + QVERIFY( !model->hasIndex ( -2, -2 ) ); + QVERIFY( !model->hasIndex ( -2, 0 ) ); + QVERIFY( !model->hasIndex ( 0, -2 ) ); + + int rows = model->rowCount(); + int columns = model->columnCount(); + + // check out of bounds + QVERIFY( !model->hasIndex ( rows, columns ) ); + QVERIFY( !model->hasIndex ( rows + 1, columns + 1 ) ); + + if ( rows > 0 ) + QVERIFY( model->hasIndex ( 0, 0 ) ); + + // hasIndex() is tested more extensively in checkChildren(), + // but this catches the big mistakes +} + +/*! + Tests model's implementation of QAbstractItemModel::index() + */ +void ModelTest::index() +{ +// qDebug() << "i"; + // Make sure that invalid values returns an invalid index +/* QVERIFY( model->index ( -2, -2 ) == QModelIndex() ); + QVERIFY( model->index ( -2, 0 ) == QModelIndex() ); + QVERIFY( model->index ( 0, -2 ) == QModelIndex() );*/ + QVERIFY( !model->index ( -2, -2 ).isValid() ); + QVERIFY( !model->index ( -2, 0 ).isValid() ); + QVERIFY( !model->index ( 0, -2 ).isValid() ); + + int rows = model->rowCount(); + int columns = model->columnCount(); + + if ( rows == 0 ) + return; + + // Catch off by one errors + //QVERIFY( model->index ( rows, columns ) == QModelIndex() ); + QVERIFY( !model->index ( rows, columns ).isValid() ); + QVERIFY( model->index ( 0, 0 ).isValid() ); + + // Make sure that the same index is *always* returned + QModelIndex a = model->index ( 0, 0 ); + QModelIndex b = model->index ( 0, 0 ); + QVERIFY( a == b ); + + // index() is tested more extensively in checkChildren(), + // but this catches the big mistakes +} + +/*! + Tests model's implementation of QAbstractItemModel::parent() + */ +void ModelTest::parent() +{ +// qDebug() << "p"; + // Make sure the model wont crash and will return an invalid QModelIndex + // when asked for the parent of an invalid index. + QVERIFY( model->parent ( QModelIndex() ) == QModelIndex() ); + + if ( model->rowCount() == 0 ) + return; + + // Column 0 | Column 1 | + // QModelIndex() | | + // \- topIndex | topIndex1 | + // \- childIndex | childIndex1 | + + // Common error test #1, make sure that a top level index has a parent + // that is a invalid QModelIndex. + QModelIndex topIndex = model->index ( 0, 0, QModelIndex() ); + QVERIFY( model->parent ( topIndex ) == QModelIndex() ); + + // Common error test #2, make sure that a second level index has a parent + // that is the first level index. + if ( model->rowCount ( topIndex ) > 0 ) { + QModelIndex childIndex = model->index ( 0, 0, topIndex ); + QVERIFY( model->parent ( childIndex ) == topIndex ); + } + + // Common error test #3, the second column should NOT have the same children + // as the first column in a row. + // Usually the second column shouldn't have children. + QModelIndex topIndex1 = model->index ( 0, 1, QModelIndex() ); + if ( model->rowCount ( topIndex1 ) > 0 ) { + QModelIndex childIndex = model->index ( 0, 0, topIndex ); + QModelIndex childIndex1 = model->index ( 0, 0, topIndex1 ); + QVERIFY( childIndex != childIndex1 ); + } + + // Full test, walk n levels deep through the model making sure that all + // parent's children correctly specify their parent. + checkChildren ( QModelIndex() ); +} + +/*! + Called from the parent() test. + + A model that returns an index of parent X should also return X when asking + for the parent of the index. + + This recursive function does pretty extensive testing on the whole model in an + effort to catch edge cases. + + This function assumes that rowCount(), columnCount() and index() already work. + If they have a bug it will point it out, but the above tests should have already + found the basic bugs because it is easier to figure out the problem in + those tests then this one. + */ +void ModelTest::checkChildren ( const QModelIndex &parent, int currentDepth ) +{ + // First just try walking back up the tree. + QModelIndex p = parent; + while ( p.isValid() ) + p = p.parent(); + + // For models that are dynamically populated + if ( model->canFetchMore ( parent ) ) { + fetchingMore = true; + model->fetchMore ( parent ); + fetchingMore = false; + } + + int rows = model->rowCount ( parent ); + int columns = model->columnCount ( parent ); + + if ( rows > 0 ) + QVERIFY( model->hasChildren ( parent ) ); + + // Some further testing against rows(), columns(), and hasChildren() + QVERIFY( rows >= 0 ); + QVERIFY( columns >= 0 ); + if ( rows > 0 ) + QVERIFY( model->hasChildren ( parent ) ); + + //qDebug() << "parent:" << model->data(parent).toString() << "rows:" << rows + // << "columns:" << columns << "parent column:" << parent.column(); + + QVERIFY( !model->hasIndex ( rows + 1, 0, parent ) ); + for ( int r = 0; r < rows; ++r ) { + if ( model->canFetchMore ( parent ) ) { + fetchingMore = true; + model->fetchMore ( parent ); + fetchingMore = false; + } + QVERIFY( !model->hasIndex ( r, columns + 1, parent ) ); + for ( int c = 0; c < columns; ++c ) { + QVERIFY( model->hasIndex ( r, c, parent ) ); + QModelIndex index = model->index ( r, c, parent ); + // rowCount() and columnCount() said that it existed... + QVERIFY( index.isValid() ); + + // index() should always return the same index when called twice in a row + QModelIndex modifiedIndex = model->index ( r, c, parent ); + QVERIFY( index == modifiedIndex ); + + // Make sure we get the same index if we request it twice in a row + QModelIndex a = model->index ( r, c, parent ); + QModelIndex b = model->index ( r, c, parent ); + QVERIFY( a == b ); + + // Some basic checking on the index that is returned + QVERIFY( index.model() == model ); + QCOMPARE( index.row(), r ); + QCOMPARE( index.column(), c ); + // While you can technically return a QVariant usually this is a sign + // of a bug in data(). Disable if this really is ok in your model. +// QVERIFY( model->data ( index, Qt::DisplayRole ).isValid() ); + + // If the next test fails here is some somewhat useful debug you play with. + + if (model->parent(index) != parent) { + qDebug() << r << c << currentDepth << model->data(index).toString() + << model->data(parent).toString(); + qDebug() << index << parent << model->parent(index); +// And a view that you can even use to show the model. +// QTreeView view; +// view.setModel(model); +// view.show(); + } + + // Check that we can get back our real parent. + QCOMPARE( model->parent ( index ), parent ); + + // recursively go down the children + if ( model->hasChildren ( index ) && currentDepth < 10 ) { + //qDebug() << r << c << "has children" << model->rowCount(index); + checkChildren ( index, ++currentDepth ); + }/* else { if (currentDepth >= 10) qDebug() << "checked 10 deep"; };*/ + + // make sure that after testing the children that the index doesn't change. + QModelIndex newerIndex = model->index ( r, c, parent ); + QVERIFY( index == newerIndex ); + } + } +} + +/*! + Tests model's implementation of QAbstractItemModel::data() + */ +void ModelTest::data() +{ + // Invalid index should return an invalid qvariant + QVERIFY( !model->data ( QModelIndex() ).isValid() ); + + if ( model->rowCount() == 0 ) + return; + + // A valid index should have a valid QVariant data + QVERIFY( model->index ( 0, 0 ).isValid() ); + + // shouldn't be able to set data on an invalid index + QVERIFY( !model->setData ( QModelIndex(), QLatin1String ( "foo" ), Qt::DisplayRole ) ); + + // General Purpose roles that should return a QString + QVariant variant = model->data ( model->index ( 0, 0 ), Qt::ToolTipRole ); + if ( variant.isValid() ) { + QVERIFY( variant.canConvert(QMetaType::QString) ); + } + variant = model->data ( model->index ( 0, 0 ), Qt::StatusTipRole ); + if ( variant.isValid() ) { + QVERIFY( variant.canConvert(QMetaType::QString) ); + } + variant = model->data ( model->index ( 0, 0 ), Qt::WhatsThisRole ); + if ( variant.isValid() ) { + QVERIFY( variant.canConvert(QMetaType::QString) ); + } + + // General Purpose roles that should return a QSize + variant = model->data ( model->index ( 0, 0 ), Qt::SizeHintRole ); + if ( variant.isValid() ) { + QVERIFY( variant.canConvert(QMetaType::QSize) ); + } + + // General Purpose roles that should return a QFont + QVariant fontVariant = model->data ( model->index ( 0, 0 ), Qt::FontRole ); + if ( fontVariant.isValid() ) { + QVERIFY( fontVariant.canConvert(QMetaType::QFont) ); + } + + // Check that the alignment is one we know about + QVariant textAlignmentVariant = model->data ( model->index ( 0, 0 ), Qt::TextAlignmentRole ); + if ( textAlignmentVariant.isValid() ) { + int alignment = textAlignmentVariant.toInt(); + QCOMPARE( alignment, ( alignment & ( Qt::AlignHorizontal_Mask | Qt::AlignVertical_Mask ) ) ); + } + + // General Purpose roles that should return a QColor + QVariant colorVariant = model->data ( model->index ( 0, 0 ), Qt::BackgroundColorRole ); + if ( colorVariant.isValid() ) { + QVERIFY( colorVariant.canConvert(QMetaType::QColor) ); + } + + colorVariant = model->data ( model->index ( 0, 0 ), Qt::TextColorRole ); + if ( colorVariant.isValid() ) { + QVERIFY( colorVariant.canConvert(QMetaType::QColor) ); + } + + // Check that the "check state" is one we know about. + QVariant checkStateVariant = model->data ( model->index ( 0, 0 ), Qt::CheckStateRole ); + if ( checkStateVariant.isValid() ) { + int state = checkStateVariant.toInt(); + QVERIFY( state == Qt::Unchecked || + state == Qt::PartiallyChecked || + state == Qt::Checked ); + } +} + +/*! + Store what is about to be inserted to make sure it actually happens + + \sa rowsInserted() + */ +void ModelTest::rowsAboutToBeInserted ( const QModelIndex &parent, int start, int end ) +{ +// Q_UNUSED(end); +// qDebug() << "rowsAboutToBeInserted" << "start=" << start << "end=" << end << "parent=" << model->data ( parent ).toString() +// << "current count of parent=" << model->rowCount ( parent ); // << "display of last=" << model->data( model->index(start-1, 0, parent) ); +// qDebug() << model->index(start-1, 0, parent) << model->data( model->index(start-1, 0, parent) ); + Changing c; + c.parent = parent; + c.oldSize = model->rowCount ( parent ); + c.last = model->data ( model->index ( start - 1, 0, parent ) ); + c.next = model->data ( model->index ( start, 0, parent ) ); +qDebug() << start << " - " << parent << " - " << model->data ( model->index ( start, 0, parent ), Qt::UserRole ); + insert.push ( c ); +} + +/*! + Confirm that what was said was going to happen actually did + + \sa rowsAboutToBeInserted() + */ +void ModelTest::rowsInserted ( const QModelIndex & parent, int start, int end ) +{ + Changing c = insert.pop(); + QVERIFY( c.parent == parent ); +// qDebug() << "rowsInserted" << "start=" << start << "end=" << end << "oldsize=" << c.oldSize +// << "parent=" << model->data ( parent ).toString() << "current rowcount of parent=" << model->rowCount ( parent ); + +// for (int ii=start; ii <= end; ii++) +// { +// qDebug() << "itemWasInserted:" << ii << model->data ( model->index ( ii, 0, parent )); +// } +// qDebug(); + + QVERIFY( c.oldSize + ( end - start + 1 ) == model->rowCount ( parent ) ); + QVERIFY( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) ); + + if (c.next != model->data(model->index(end + 1, 0, c.parent))) { + qDebug() << start << end; + for (int i=0; i < model->rowCount(); ++i) + qDebug() << model->index(i, 0).data().toString(); + qDebug() << c.next << model->data(model->index(end + 1, 0, c.parent)); + } + +if (c.next != model->data ( model->index ( end + 1, 0, c.parent ) )) { + qDebug("break"); +} + QVERIFY( c.next == model->data ( model->index ( end + 1, 0, c.parent ) ) ); +} + +void ModelTest::layoutAboutToBeChanged() +{ + for ( int i = 0; i < qBound ( 0, model->rowCount(), 100 ); ++i ) + changing.append ( QPersistentModelIndex ( model->index ( i, 0 ) ) ); +} + +void ModelTest::layoutChanged() +{ + for ( int i = 0; i < changing.count(); ++i ) { + QPersistentModelIndex p = changing[i]; + QVERIFY( p == model->index ( p.row(), p.column(), p.parent() ) ); + } + changing.clear(); +} + +/*! + Store what is about to be inserted to make sure it actually happens + + \sa rowsRemoved() + */ +void ModelTest::rowsAboutToBeRemoved ( const QModelIndex &parent, int start, int end ) +{ +qDebug() << "ratbr" << parent << start << end; + Changing c; + c.parent = parent; + c.oldSize = model->rowCount ( parent ); + c.last = model->data ( model->index ( start - 1, 0, parent ) ); + c.next = model->data ( model->index ( end + 1, 0, parent ) ); + remove.push ( c ); +} + +/*! + Confirm that what was said was going to happen actually did + + \sa rowsAboutToBeRemoved() + */ +void ModelTest::rowsRemoved ( const QModelIndex & parent, int start, int end ) +{ + qDebug() << "rr" << parent << start << end; + Changing c = remove.pop(); + QVERIFY( c.parent == parent ); + QVERIFY( c.oldSize - ( end - start + 1 ) == model->rowCount ( parent ) ); + QVERIFY( c.last == model->data ( model->index ( start - 1, 0, c.parent ) ) ); + QVERIFY( c.next == model->data ( model->index ( start, 0, c.parent ) ) ); +} + + diff --git a/src/modeltest.h b/src/modeltest.h new file mode 100644 index 00000000..7e8c8267 --- /dev/null +++ b/src/modeltest.h @@ -0,0 +1,94 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef MODELTEST_H +#define MODELTEST_H + +#include +#include +#include + +class ModelTest : public QObject +{ + Q_OBJECT + +public: + ModelTest( QAbstractItemModel *model, QObject *parent = 0 ); + +private Q_SLOTS: + void nonDestructiveBasicTest(); + void rowCount(); + void columnCount(); + void hasIndex(); + void index(); + void parent(); + void data(); + +protected Q_SLOTS: + void runAllTests(); + void layoutAboutToBeChanged(); + void layoutChanged(); + void rowsAboutToBeInserted( const QModelIndex &parent, int start, int end ); + void rowsInserted( const QModelIndex & parent, int start, int end ); + void rowsAboutToBeRemoved( const QModelIndex &parent, int start, int end ); + void rowsRemoved( const QModelIndex & parent, int start, int end ); + +private: + void checkChildren( const QModelIndex &parent, int currentDepth = 0 ); + + QAbstractItemModel *model; + + struct Changing { + QModelIndex parent; + int oldSize; + QVariant last; + QVariant next; + }; + QStack insert; + QStack remove; + + bool fetchingMore; + + QList changing; +}; + +#endif diff --git a/src/modinfodialog.cpp b/src/modinfodialog.cpp index 4f2ad9da..cf299530 100644 --- a/src/modinfodialog.cpp +++ b/src/modinfodialog.cpp @@ -119,8 +119,9 @@ ModInfoDialog::ModInfoDialog(ModInfo::Ptr modInfo, const DirectoryEntry *directo 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))); +// ui->descriptionView->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); +// QObject::connect(ui->descriptionView, SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl))); + connect(ui->descriptionView, SIGNAL(anchorClicked(QUrl)), this, SLOT(linkClicked(QUrl))); if (directory->originExists(ToWString(modInfo->name()))) { m_Origin = &directory->getOriginByName(ToWString(modInfo->name())); diff --git a/src/modinfodialog.ui b/src/modinfodialog.ui index c9d413f6..343d4646 100644 --- a/src/modinfodialog.ui +++ b/src/modinfodialog.ui @@ -20,7 +20,7 @@ QTabWidget::Rounded - 6 + 0 @@ -654,19 +654,26 @@ p, li { white-space: pre-wrap; } - - - - 8 - + + + + 0 + 200 + + + + <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> +<html><head><meta name="qrichtext" content="1" /><style type="text/css"> +p, li { white-space: pre-wrap; } +</style></head><body style=" font-family:'MS Shell Dlg 2'; font-size:8.25pt; font-weight:400; font-style:normal;"> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p> +<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-size:8pt;"><br /></p></body></html> - - - about:blank - + + Qt::TextBrowserInteraction - - 0.800000011920929 + + false @@ -789,13 +796,6 @@ p, li { white-space: pre-wrap; } - - - QWebView - QWidget -
    QtWebKitWidgets/QWebView
    -
    -
    diff --git a/src/modlist.cpp b/src/modlist.cpp index f87687bb..21aca0bf 100644 --- a/src/modlist.cpp +++ b/src/modlist.cpp @@ -70,6 +70,16 @@ int ModList::rowCount(const QModelIndex &parent) const } +bool ModList::hasChildren(const QModelIndex &parent) const +{ + if (!parent.isValid()) { + return ModInfo::getNumMods() > 0; + } else { + return false; + } +} + + int ModList::columnCount(const QModelIndex &) const { return COL_LASTCOLUMN + 1; @@ -456,7 +466,7 @@ Qt::ItemFlags ModList::flags(const QModelIndex &modelIndex) const } } std::vector flags = modInfo->getFlags(); - if ((m_DropOnItems) && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end())) { + if ((m_DropOnItems) && (std::find(flags.begin(), flags.end(), ModInfo::FLAG_OVERWRITE) == flags.end())) { result |= Qt::ItemIsDropEnabled; } } else { @@ -667,7 +677,11 @@ void ModList::notifyChange(int rowStart, int rowEnd) QModelIndex ModList::index(int row, int column, const QModelIndex&) const { - return createIndex(row, column, row); + if ((row < 0) || (row >= rowCount()) || (column < 0) || (column >= columnCount())) { + return QModelIndex(); + } + QModelIndex res = createIndex(row, column, row); + return res; } diff --git a/src/modlist.h b/src/modlist.h index 99f43b5c..aeff799a 100644 --- a/src/modlist.h +++ b/src/modlist.h @@ -80,11 +80,6 @@ public: **/ int getCurrentSortingMode() const; - /** - * @brief force a refresh of the mod list - **/ - void refresh(); - /** * @brief remove the specified mod without asking for confirmation * @param row the row to remove @@ -92,7 +87,6 @@ public: void removeRowForce(int row); void notifyChange(int rowStart, int rowEnd = -1); - static QString getColumnName(int column); void changeModPriority(int sourceIndex, int newPriority); @@ -100,6 +94,7 @@ public: public: // implementation of virtual functions of QAbstractItemModel virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; + virtual bool hasChildren(const QModelIndex &parent = QModelIndex()) const; virtual int columnCount(const QModelIndex &parent = QModelIndex()) const; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; virtual bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole); diff --git a/src/modlistsortproxy.h b/src/modlistsortproxy.h index 21dc9f4d..dd968b9e 100644 --- a/src/modlistsortproxy.h +++ b/src/modlistsortproxy.h @@ -54,6 +54,18 @@ public: bool filterMatches(ModInfo::Ptr info, bool enabled) const; +/* + virtual int rowCount( const QModelIndex & parent = QModelIndex() ) const { + int rc = QSortFilterProxyModel::rowCount(parent); + qDebug() << parent << " - " << rc; + return rc; + }*/ + + virtual bool hasChildren ( const QModelIndex & parent = QModelIndex() ) const { + return rowCount(parent) > 0; + } + + public slots: void displayColumnSelection(const QPoint &pos); diff --git a/src/motddialog.cpp b/src/motddialog.cpp index 1bf11a7f..84c67aa5 100644 --- a/src/motddialog.cpp +++ b/src/motddialog.cpp @@ -28,8 +28,9 @@ MotDDialog::MotDDialog(const QString &message, QWidget *parent) { ui->setupUi(this); ui->motdView->setHtml(BBCode::convertToHTML(message)); - ui->motdView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); - connect(ui->motdView->page(), SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl))); +// ui->motdView->page()->setLinkDelegationPolicy(QWebPage::DelegateAllLinks); +// connect(ui->motdView->page(), SIGNAL(linkClicked(QUrl)), this, SLOT(linkClicked(QUrl))); + connect(ui->motdView, SIGNAL(anchorClicked(QUrl)), this, SLOT(linkClicked(QUrl))); } MotDDialog::~MotDDialog() diff --git a/src/motddialog.ui b/src/motddialog.ui index e40cb21f..4d37968c 100644 --- a/src/motddialog.ui +++ b/src/motddialog.ui @@ -15,48 +15,9 @@ - - - - - - - - 240 - 240 - 240 - - - - - - - - - 240 - 240 - 240 - - - - - - - - - 240 - 240 - 240 - - - - - - - - - about:blank - + + + false @@ -86,13 +47,6 @@ - - - QWebView - QWidget -
    QtWebKit/QWebView
    -
    -
    diff --git a/src/nexusdialog.cpp b/src/nexusdialog.cpp deleted file mode 100644 index 2569798e..00000000 --- a/src/nexusdialog.cpp +++ /dev/null @@ -1,339 +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 . -*/ - -#include "nexusdialog.h" -#include "ui_nexusdialog.h" - -#include "messagedialog.h" -#include "report.h" -#include "json.h" - -#include -#include -#include -#include -#include -#include -#include - - -using namespace MOBase; -using namespace MOShared; - -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("browserTabWidget"); - - m_View = new NexusView(this); - - initTab(m_View); - - m_LoadProgress = this->findChild("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(m_Tabs->currentWidget()); -} - - -void NexusDialog::urlChanged(const QUrl &url) -{ - NexusView *sendingView = qobject_cast(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)), Qt::UniqueConnection); - connect(m_AccessManager, SIGNAL(loginFailed(QString)), this, SLOT(loginFailed(QString)), Qt::UniqueConnection); -} - - -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() -{ - if (m_View != NULL) { - 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(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(sender()); - if (page == NULL) { - qCritical("sender not a page"); - return; - } - NexusView *view = qobject_cast(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(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 deleted file mode 100644 index 8fb22029..00000000 --- a/src/nexusdialog.h +++ /dev/null @@ -1,158 +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 . -*/ - -#ifndef NEXUSDIALOG_H -#define NEXUSDIALOG_H - -#include "nexusview.h" -#include "nxmaccessmanager.h" -#include "tutorialcontrol.h" -#include -#include -#include -#include -#include -#include -#include -#include -#include - - -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 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 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; - - MOBase::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/nexusdialog.ui b/src/nexusdialog.ui deleted file mode 100644 index 10b08d92..00000000 --- a/src/nexusdialog.ui +++ /dev/null @@ -1,306 +0,0 @@ - - - NexusDialog - - - - 0 - 0 - 1008 - 750 - - - - Nexus - - - - 0 - - - 0 - - - - - - 16777215 - 22 - - - - - - - - - 226 - 226 - 226 - - - - - - - 106 - 106 - 106 - - - - - - - 199 - 199 - 199 - - - - - - - 255 - 255 - 255 - - - - - - - 81 - 81 - 81 - - - - - - - - - 226 - 226 - 226 - - - - - - - 106 - 106 - 106 - - - - - - - 199 - 199 - 199 - - - - - - - 255 - 255 - 255 - - - - - - - 81 - 81 - 81 - - - - - - - - - 120 - 120 - 120 - - - - - - - 106 - 106 - 106 - - - - - - - 120 - 120 - 120 - - - - - - - 81 - 81 - 81 - - - - - - - 81 - 81 - 81 - - - - - - - - true - - - - 6 - - - 0 - - - - - false - - - - - - - :/MO/gui/previous:/MO/gui/previous - - - false - - - true - - - - - - - - - - - :/MO/gui/next:/MO/gui/next - - - false - - - true - - - - - - - - - - - :/MO/gui/refresh:/MO/gui/refresh - - - false - - - true - - - - - - - Qt::Horizontal - - - - 40 - 20 - - - - - - - - Mod ID - - - - - - - - 100 - 16777215 - - - - - - - - Search - - - - - - - - - - - - - Qt::DefaultContextMenu - - - true - - - - - - - 0 - - - - - - - - - - diff --git a/src/nexusinterface.h b/src/nexusinterface.h index d69275e1..0c304a71 100644 --- a/src/nexusinterface.h +++ b/src/nexusinterface.h @@ -97,15 +97,6 @@ public: * @param userData user data to be returned with the result */ virtual void requestToggleEndorsement(int modID, bool endorse, QVariant userData); -/* -signals: - - void descriptionAvailable(int modID, QVariant userData, QVariant resultData); - void filesAvailable(int modID, QVariant userData, const QList &resultData); - void fileInfoAvailable(int modID, int fileID, QVariant userData, QVariant resultData); - void downloadURLsAvailable(int modID, int fileID, QVariant userData, QVariant resultData); - void endorsementToggled(int modID, QVariant userData, QVariant resultData); - void requestFailed(int modID, QVariant userData, const QString &errorMessage);*/ public slots: diff --git a/src/nexusview.cpp b/src/nexusview.cpp deleted file mode 100644 index 6d70d830..00000000 --- a/src/nexusview.cpp +++ /dev/null @@ -1,121 +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 . -*/ - -#include "nexusview.h" - -#include -#include -#include -#include -#include -#include -#include -#include "utility.h" - - -using namespace MOBase; - - -NexusView::NexusView(QWidget *parent) - : QWebView(parent), m_LastSeenModID(0) -{ - installEventFilter(this); - 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(event); - if (keyEvent->matches(QKeySequence::Find)) { - emit startFind(); - } else if (keyEvent->matches(QKeySequence::FindNext)) { - emit findAgain(); - } - } else if (event->type() == QEvent::MouseButtonPress) { - QMouseEvent *mouseEvent = static_cast(event); - if (mouseEvent->button() == Qt::MidButton) { - mouseEvent->ignore(); - return true; - } - } else if (event->type() == QEvent::MouseButtonRelease) { - QMouseEvent *mouseEvent = static_cast(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 deleted file mode 100644 index 680a36f1..00000000 --- a/src/nexusview.h +++ /dev/null @@ -1,95 +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 . -*/ - -#ifndef NEXUSVIEW_H -#define NEXUSVIEW_H - -#include "finddialog.h" - -#include -#include -#include - -/** - * @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/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp index 3f30f171..dce2131a 100644 --- a/src/nxmaccessmanager.cpp +++ b/src/nxmaccessmanager.cpp @@ -66,7 +66,7 @@ QNetworkReply *NXMAccessManager::createRequest( void NXMAccessManager::showCookies() { - QList cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage()))); + QList cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage()) + "/")); foreach (QNetworkCookie cookie, cookies) { qDebug("%s - %s", cookie.name().constData(), cookie.value().constData()); } @@ -98,7 +98,8 @@ void NXMAccessManager::login(const QString &username, const QString &password) void NXMAccessManager::pageLogin() { - QString requestString = QString("http://gatekeeper.nexusmods.com/Sessions/?Login"); + QString requestString = QString("http://gatekeeper.nexusmods.com/Sessions/?Login&uri=%1") + .arg(QString(QUrl::toPercentEncoding(ToQString(GameInfo::instance().getNexusPage())))); QNetworkRequest request(requestString); request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded"); @@ -149,7 +150,7 @@ void NXMAccessManager::loginError(QNetworkReply::NetworkError) bool NXMAccessManager::hasLoginCookies() const { bool sidCookie = false; - QList cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage()))); + QList cookies = cookieJar()->cookiesForUrl(QUrl(ToQString(GameInfo::instance().getNexusPage()) + "/")); foreach (QNetworkCookie cookie, cookies) { if (cookie.name() == "sid") { sidCookie = true; diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h index 32829d69..4e2b24ce 100644 --- a/src/nxmaccessmanager.h +++ b/src/nxmaccessmanager.h @@ -62,7 +62,6 @@ signals: private slots: -// void pageLoginFinished(); void loginFinished(); void loginError(QNetworkReply::NetworkError errorCode); void loginTimeout(); diff --git a/src/nxmurl.cpp b/src/nxmurl.cpp deleted file mode 100644 index 003b3c58..00000000 --- a/src/nxmurl.cpp +++ /dev/null @@ -1,34 +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 . -*/ - -#include "nxmurl.h" -#include -#include -#include - -NXMUrl::NXMUrl(const QString &url) -{ - QRegExp exp("nxm://([a-z]+)/mods/(\\d+)/files/(\\d+)", Qt::CaseInsensitive); - exp.indexIn(url); - if (exp.captureCount() != 3) { - throw MOBase::MyException(tr("invalid nxm-link: %1").arg(url)); - } - m_ModId = exp.cap(2).toInt(); - m_FileId = exp.cap(3).toInt(); -} diff --git a/src/nxmurl.h b/src/nxmurl.h deleted file mode 100644 index a38e1910..00000000 --- a/src/nxmurl.h +++ /dev/null @@ -1,64 +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 . -*/ - -#ifndef NXMURL_H -#define NXMURL_H - -#include -#include - - -/** - * @brief represents a nxm:// url - * @todo the game name encoded into the url is not interpreted - **/ -class NXMUrl : public QObject -{ - Q_OBJECT - -public: - - /** - * @brief constructor - * - * @param url url following the nxm-protocol - **/ - NXMUrl(const QString &url); - - /** - * @brief retrieve the mod id encoded into the url - * - * @return mod id - **/ - int getModId() const { return m_ModId; } - - /** - * @brief retrieve the file id encoded into the url - * - * @return file id - **/ - int getFileId() const { return m_FileId; } - -private: - - int m_ModId; - int m_FileId; -}; - -#endif // NXMURL_H diff --git a/src/organizer.pro b/src/organizer.pro index 2e51622c..957a9266 100644 --- a/src/organizer.pro +++ b/src/organizer.pro @@ -5,9 +5,9 @@ #------------------------------------------------- contains(QT_VERSION, "^5.*") { - QT += core gui widgets webkitwidgets network declarative script xml sql xmlpatterns + QT += core gui widgets network declarative script xml sql xmlpatterns } else { - QT += core gui webkit network xml declarative script sql xmlpatterns + QT += core gui network xml declarative script sql xmlpatterns } TARGET = ModOrganizer @@ -34,11 +34,8 @@ SOURCES += \ pluginlistsortproxy.cpp \ pluginlist.cpp \ overwriteinfodialog.cpp \ - nxmurl.cpp \ nxmaccessmanager.cpp \ - nexusview.cpp \ nexusinterface.cpp \ - nexusdialog.cpp \ motddialog.cpp \ modlistsortproxy.cpp \ modlist.cpp \ @@ -79,7 +76,8 @@ SOURCES += \ savetextasdialog.cpp \ qtgroupingproxy.cpp \ modlistview.cpp \ - problemsdialog.cpp + problemsdialog.cpp \ + serverinfo.cpp HEADERS += \ transfersavesdialog.h \ @@ -102,11 +100,8 @@ HEADERS += \ pluginlistsortproxy.h \ pluginlist.h \ overwriteinfodialog.h \ - nxmurl.h \ nxmaccessmanager.h \ - nexusview.h \ nexusinterface.h \ - nexusdialog.h \ motddialog.h \ modlistsortproxy.h \ modlist.h \ @@ -146,7 +141,8 @@ HEADERS += \ savetextasdialog.h \ qtgroupingproxy.h \ modlistview.h \ - problemsdialog.h + problemsdialog.h \ + serverinfo.h FORMS += \ transfersavesdialog.ui \ @@ -159,7 +155,6 @@ FORMS += \ queryoverwritedialog.ui \ profilesdialog.ui \ overwriteinfodialog.ui \ - nexusdialog.ui \ motddialog.ui \ modinfodialog.ui \ messagedialog.ui \ diff --git a/src/qtgroupingproxy.cpp b/src/qtgroupingproxy.cpp index 3891cc12..f62d45d3 100644 --- a/src/qtgroupingproxy.cpp +++ b/src/qtgroupingproxy.cpp @@ -183,7 +183,7 @@ QtGroupingProxy::buildTree() for (auto iter = m_groupHash.begin(); iter != m_groupHash.end(); ++iter) { if ((iter.key() == quint32max) || - (iter->count() == 1)) { + (iter->count() < 2)) { temp[quint32max].append(iter.value()); if (iter.key() != quint32max) { rmgroups.push_back(iter.key()); @@ -194,15 +194,15 @@ QtGroupingProxy::buildTree() } m_groupHash = temp; - // second loop is necessary because qt containers can't be iterated from end to - // removing by index from begin to end is ugly - for (auto iter = rmgroups.rbegin(); iter != rmgroups.rend(); ++iter) { + // second loop is necessary because qt containers can't be iterated from end to front + // and removing by index from begin to end is ugly + std::sort(rmgroups.begin(), rmgroups.end(), [] (int lhs, int rhs) { return rhs < lhs; }); + for (auto iter = rmgroups.begin(); iter != rmgroups.end(); ++iter) { m_groupMaps.removeAt(*iter); } } endResetModel(); - // restore expand-state for( int row = 0; row < rowCount(); row++ ) { QModelIndex idx = index( row, 0, QModelIndex() ); @@ -215,6 +215,8 @@ QtGroupingProxy::buildTree() QList QtGroupingProxy::addSourceRow( const QModelIndex &idx ) { + // TODO: modeltest reports a discrepance between the "rowAboutToBeInserted" and "rowInserted" events + QList updatedGroups; QList groupData = belongsTo( idx ); @@ -624,8 +626,9 @@ QModelIndex QtGroupingProxy::mapToSource( const QModelIndex &index ) const { //qDebug() << "mapToSource: " << index; - if( !index.isValid() ) + if( !index.isValid() ) { return m_rootNode; + } if( isGroup( index ) ) { @@ -856,8 +859,9 @@ QtGroupingProxy::removeGroup( const QModelIndex &idx ) bool QtGroupingProxy::hasChildren( const QModelIndex &parent ) const { - if( !parent.isValid() ) + if( !parent.isValid() ) { return true; + } if( isGroup( parent ) ) { return !m_groupHash.value( parent.row() ).isEmpty(); diff --git a/src/settings.cpp b/src/settings.cpp index 27dea0dc..991f1f59 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -38,6 +38,20 @@ using namespace MOBase; using namespace MOShared; +template +class QListWidgetItemEx : public QListWidgetItem { +public: + QListWidgetItemEx(const QString &text, int sortRole = Qt::DisplayRole, QListWidget *parent = 0, int type = Type) + : QListWidgetItem(text, parent, type), m_SortRole(sortRole) {} + + virtual bool operator< ( const QListWidgetItem & other ) const { + return this->data(m_SortRole).value() < other.data(m_SortRole).value(); + } +private: + int m_SortRole; +}; + + static const unsigned char Key2[20] = { 0x99, 0xb8, 0x76, 0x42, 0x3e, 0xc1, 0x60, 0xa4, 0x5b, 0x01, 0xdb, 0xf8, 0x43, 0x3a, 0xb7, 0xb6, 0x98, 0xd4, 0x7d, 0xa2 }; @@ -201,24 +215,12 @@ void Settings::setupLoadMechanism() } -bool Settings::preferIntegratedInstallers() -{ - return m_Settings.value("Settings/prefer_integrated_installer").toBool(); -} - - bool Settings::enableQuickInstaller() { return m_Settings.value("Settings/enable_quick_installer").toBool(); } -bool Settings::preferExternalBrowser() -{ - return m_Settings.value("Settings/prefer_external_browser").toBool(); -} - - void Settings::setMotDHash(uint hash) { m_Settings.setValue("motd_hash", hash); @@ -259,6 +261,32 @@ QString Settings::language() return result; } +void Settings::updateServers(const QList &servers) +{ + m_Settings.beginGroup("Servers"); + QStringList oldServerKeys = m_Settings.childKeys(); + + foreach (const ServerInfo &server, servers) { + if (!oldServerKeys.contains(server.name)) { + // not yet known server + QVariantMap newVal; + newVal["premium"] = server.premium; + newVal["preferred"] = server.preferred; + newVal["lastSeen"] = server.lastSeen; + m_Settings.setValue(server.name, newVal); + } else { + QVariantMap data = m_Settings.value(server.name).toMap(); + data["lastSeen"] = server.lastSeen; + data["premium"] = server.premium; + + m_Settings.setValue(server.name, data); + } + } + + m_Settings.endGroup(); + m_Settings.sync(); +} + void Settings::addLanguages(QComboBox *languageBox) { @@ -299,7 +327,7 @@ void Settings::addStyles(QComboBox *styleBox) bool Settings::isNXMHandler(bool *modifyable) { - QSettings handlerReg("HKEY_CLASSES_ROOT\\nxm\\shell\\open\\command", + QSettings handlerReg("HKEY_CURRENT_USER\\Software\\Classes\\nxm\\shell\\open\\command", QSettings::NativeFormat); QString currentExe = handlerReg.value("Default", "").toString().toUtf8().constData(); @@ -317,8 +345,8 @@ bool Settings::isNXMHandler(bool *modifyable) void Settings::setNXMHandlerActive(bool active, bool writable) { - QSettings handlerReg("HKEY_CLASSES_ROOT\\nxm\\", - QSettings::NativeFormat); +// QSettings handlerReg("HKEY_CLASSES_ROOT\\nxm\\", QSettings::NativeFormat); + QSettings handlerReg("HKEY_CURRENT_USER\\Software\\Classes\\nxm\\", QSettings::NativeFormat); if (writable) { QString myExe = QString("\"%1\" ").arg(QDir::toNativeSeparators(QCoreApplication::applicationFilePath())).append("\"%1\""); @@ -363,18 +391,20 @@ void Settings::query(QWidget *parent) QComboBox *languageBox = dialog.findChild("languageBox"); QComboBox *styleBox = dialog.findChild("styleBox"); QComboBox *logLevelBox = dialog.findChild("logLevelBox"); - QCheckBox *handleNXMBox = dialog.findChild("handleNXMBox"); +// QCheckBox *handleNXMBox = dialog.findChild("handleNXMBox"); QLineEdit *downloadDirEdit = dialog.findChild("downloadDirEdit"); QLineEdit *modDirEdit = dialog.findChild("modDirEdit"); QLineEdit *cacheDirEdit = dialog.findChild("cacheDirEdit"); - QCheckBox *preferExternalBox = dialog.findChild("preferExternalBox"); QCheckBox *offlineBox = dialog.findChild("offlineBox"); QLineEdit *nmmVersionEdit = dialog.findChild("nmmVersionEdit"); QListWidget *pluginsList = dialog.findChild("pluginsList"); + QListWidget *knownServersList = dialog.findChild("knownServersList"); + QListWidget *preferredServersList = dialog.findChild("preferredServersList"); + // // set up current settings // @@ -437,18 +467,17 @@ void Settings::query(QWidget *parent) passwordEdit->setText(deObfuscate(m_Settings.value("Settings/nexus_password", "").toString())); } - bool registryWritable = false; +/* bool registryWritable = false; bool nxmHandler = isNXMHandler(®istryWritable); handleNXMBox->setChecked(nxmHandler); if (!registryWritable) { handleNXMBox->setIcon(QIcon(":/MO/gui/locked")); handleNXMBox->setToolTip(tr("Administrative rights required to change this.")); - } + }*/ downloadDirEdit->setText(getDownloadDirectory()); modDirEdit->setText(getModDirectory()); cacheDirEdit->setText(getCacheDirectory()); - preferExternalBox->setChecked(m_Settings.value("Settings/prefer_external_browser", false).toBool()); offlineBox->setChecked(m_Settings.value("Settings/offline_mode", false).toBool()); nmmVersionEdit->setText(m_Settings.value("Settings/nmm_version", "0.33.1").toString()); logLevelBox->setCurrentIndex(logLevel()); @@ -460,6 +489,22 @@ void Settings::query(QWidget *parent) pluginsList->addItem(listItem); } + m_Settings.beginGroup("Servers"); + foreach (const QString &key, m_Settings.childKeys()) { + QVariantMap val = m_Settings.value(key).toMap(); + QString type = val["premium"].toBool() ? "(premium)" : "(free)"; + QListWidgetItem *newItem = new QListWidgetItemEx(key + " " + type, Qt::UserRole + 1); + newItem->setData(Qt::UserRole, key); + newItem->setData(Qt::UserRole + 1, val["preferred"].toInt()); + if (val["preferred"].toInt() > 0) { + preferredServersList->addItem(newItem); + } else { + knownServersList->addItem(newItem); + } + preferredServersList->sortItems(Qt::DescendingOrder); + } + m_Settings.endGroup(); + if (dialog.exec() == QDialog::Accepted) { // // transfer modified settings to configuration file @@ -514,10 +559,9 @@ void Settings::query(QWidget *parent) m_Settings.remove("Settings/nexus_username"); m_Settings.remove("Settings/nexus_password"); } - if (nxmHandler != handleNXMBox->isChecked()) { +/* if (nxmHandler != handleNXMBox->isChecked()) { setNXMHandlerActive(handleNXMBox->isChecked(), registryWritable); - } - m_Settings.setValue("Settings/prefer_external_browser", preferExternalBox->isChecked()); + }*/ m_Settings.setValue("Settings/offline_mode", offlineBox->isChecked()); m_Settings.setValue("Settings/nmm_version", nmmVersionEdit->text()); @@ -533,5 +577,22 @@ void Settings::query(QWidget *parent) m_Settings.setValue("Plugins/" + iterPlugins.key() + "/" + iterSettings.key(), iterSettings.value()); } } + + // store server preference + m_Settings.beginGroup("Servers"); + for (int i = 0; i < knownServersList->count(); ++i) { + QString key = knownServersList->item(i)->data(Qt::UserRole).toString(); + QVariantMap val = m_Settings.value(key).toMap(); + val["preferred"] = 0; + m_Settings.setValue(key, val); + } + int count = preferredServersList->count(); + for (int i = 0; i < count; ++i) { + QString key = preferredServersList->item(i)->data(Qt::UserRole).toString(); + QVariantMap val = m_Settings.value(key).toMap(); + val["preferred"] = count - i; + m_Settings.setValue(key, val); + } + m_Settings.endGroup(); } } diff --git a/src/settings.h b/src/settings.h index 38f22b71..56a4a2a4 100644 --- a/src/settings.h +++ b/src/settings.h @@ -21,7 +21,7 @@ along with Mod Organizer. If not, see . #define WORKAROUNDS_H #include "loadmechanism.h" - +#include "serverinfo.h" #include #include @@ -152,16 +152,6 @@ public: **/ void setupLoadMechanism(); - /** - * @return true if the user prefers the integrated installer over external variants - **/ - bool preferIntegratedInstallers(); - - /** - * @return true if the user prefers to use an external browser over the integrated one - **/ - bool preferExternalBrowser(); - /** * @return true if the user has enabled the quick installer (default true) **/ @@ -197,6 +187,12 @@ public: */ QString language(); + /** + * @brief updates the list of known servers + * @param list of servers from a recent query + */ + void updateServers(const QList &servers); + private: QString obfuscate(const QString &password) const; diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui index a35e0c1c..590f30e0 100644 --- a/src/settingsdialog.ui +++ b/src/settingsdialog.ui @@ -315,33 +315,6 @@ p, li { white-space: pre-wrap; } - - - - Sets up MO as the global handler for NXM links. - - - NXM Links are the green Download-buttons on Nexus. If this is checked, MO will be set up to handle those links. -On some systems this will require administrative rights. - - - Handle NXM Links - - - - - - - If checked, MO will use an external browser for buttons like "Visit on Nexus" instead of the integrated one. - - - If checked, MO will use an external browser for buttons like "Visit on Nexus" instead of the integrated one. - - - Prefer external browser - - - @@ -355,6 +328,52 @@ On some systems this will require administrative rights. + + + + + + + + Known Servers (Dynamically updated every download) + + + + + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + + + + + + + + + Preferred Servers (Drag & Drop) + + + + + + + QAbstractItemView::DragDrop + + + Qt::MoveAction + + + + + + + @@ -606,7 +625,7 @@ Please note that MO does identify itself as MO to the webserver, it's not lying tl;dr-version: If Nexus-features don't work, insert the current version number of NMM here and try again. - 009.009.009; + 009.009.009 Qt::AlignCenter diff --git a/src/shared/windows_error.h b/src/shared/windows_error.h index 20b19027..03ce0abd 100644 --- a/src/shared/windows_error.h +++ b/src/shared/windows_error.h @@ -29,7 +29,7 @@ namespace MOShared { class windows_error : public std::runtime_error { public: - windows_error(const std::string& message, int errorcode = -1) + windows_error(const std::string& message, int errorcode = ::GetLastError()) : runtime_error(constructMessage(message, errorcode)), m_ErrorCode(errorcode) {} int getErrorCode() const { return m_ErrorCode; } diff --git a/src/spawn.cpp b/src/spawn.cpp index e83c8e21..ab43b687 100644 --- a/src/spawn.cpp +++ b/src/spawn.cpp @@ -23,9 +23,11 @@ along with Mod Organizer. If not, see . #include #include #include +#include #include #include #include +#include using namespace MOBase; @@ -105,8 +107,27 @@ HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QStr return INVALID_HANDLE_VALUE; } } catch (const windows_error &e) { - reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(e.what())); - return INVALID_HANDLE_VALUE; + if (e.getErrorCode() == ERROR_ELEVATION_REQUIRED) { + // TODO: check if this is really correct. Are all settings updated that the secondary instance may use? + + if (QMessageBox::question(NULL, QObject::tr("Elevation required"), + QObject::tr("This process requires elevation to run.\n" + "This is a potential security risk so I highly advice you to investigate if\n" + "\"%1\"\n" + "can be installed to work without elevation.\n\n" + "Start elevated anyway? " + "(you will be asked if you want to allow ModOrganizer.exe to make changes to the system)").arg( + QDir::toNativeSeparators(binary.absoluteFilePath()))) == QMessageBox::Yes) { + ::ShellExecuteW(NULL, L"runas", ToWString(QCoreApplication::applicationFilePath()).c_str(), + (binaryName + L" " + ToWString(arguments)).c_str(), currentDirectoryName.c_str(), SW_SHOWNORMAL); + return INVALID_HANDLE_VALUE; + } else { + return INVALID_HANDLE_VALUE; + } + } else { + reportError(QObject::tr("failed to spawn \"%1\": %2").arg(binary.fileName()).arg(e.what())); + return INVALID_HANDLE_VALUE; + } } if (hooked) { @@ -134,3 +155,19 @@ HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QStr } return processHandle; } + + +ExitProxy *ExitProxy::s_Instance = NULL; + +ExitProxy *ExitProxy::instance() +{ + if (s_Instance == NULL) { + s_Instance = new ExitProxy(); + } + return s_Instance; +} + +void ExitProxy::emitExit() +{ + emit exit(); +} diff --git a/src/spawn.h b/src/spawn.h index 1ccfb307..e0d1f958 100644 --- a/src/spawn.h +++ b/src/spawn.h @@ -17,27 +17,46 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#ifndef SPAWN_H -#define SPAWN_H - -#define WIN32_LEAN_AND_MEAN -#include -#include -#include -#include - -/** - * @brief spawn a binary with Mod Organizer injected - * - * @param binary the binary to spawn - * @param arguments arguments to pass to the binary - * @param profileName name of the active profile - * @param currentDirectory the directory to use as the working directory to run in - * @param hooked if set, the binary is started with mo injected - * @return the process handle - * @todo is the profile name even used any more? - * @todo is the hooked parameter used? - **/ -HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString &profileName, int logLevel, const QDir ¤tDirectory, bool hooked); - -#endif // SPAWN_H +#ifndef SPAWN_H +#define SPAWN_H + +#define WIN32_LEAN_AND_MEAN +#include +#include +#include +#include + + +/** + * @brief a dirty little trick so we can issue a clean restart from startBinary + * @note unused + */ +class ExitProxy : public QObject { + Q_OBJECT +public: + static ExitProxy *instance(); + void emitExit(); +signals: + void exit(); +private: + ExitProxy() {} +private: + static ExitProxy *s_Instance; +}; + + +/** + * @brief spawn a binary with Mod Organizer injected + * + * @param binary the binary to spawn + * @param arguments arguments to pass to the binary + * @param profileName name of the active profile + * @param currentDirectory the directory to use as the working directory to run in + * @param hooked if set, the binary is started with mo injected + * @return the process handle + * @todo is the profile name even used any more? + * @todo is the hooked parameter used? + **/ +HANDLE startBinary(const QFileInfo &binary, const QString &arguments, const QString &profileName, int logLevel, const QDir ¤tDirectory, bool hooked); + +#endif // SPAWN_H diff --git a/src/version.rc b/src/version.rc index 3d0632b8..4a13f65b 100644 --- a/src/version.rc +++ b/src/version.rc @@ -1,7 +1,7 @@ #include "Winver.h" -#define VER_FILEVERSION 0,99,0,0 -#define VER_FILEVERSION_STR "0,99,0,0\0" +#define VER_FILEVERSION 0,99,1,0 +#define VER_FILEVERSION_STR "0,99,1,0\0" VS_VERSION_INFO VERSIONINFO FILEVERSION VER_FILEVERSION -- cgit v1.3.1