summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/nxmaccessmanager.cpp2
-rw-r--r--src/nxmaccessmanager.h1
-rw-r--r--src/settingsdialog.cpp321
-rw-r--r--src/settingsdialog.h62
-rw-r--r--src/settingsdialog.ui351
5 files changed, 531 insertions, 206 deletions
diff --git a/src/nxmaccessmanager.cpp b/src/nxmaccessmanager.cpp
index cabe07a9..8bbfd536 100644
--- a/src/nxmaccessmanager.cpp
+++ b/src/nxmaccessmanager.cpp
@@ -43,7 +43,7 @@ using namespace MOBase;
using namespace std::chrono_literals;
const QString NexusBaseUrl("https://api.nexusmods.com/v1");
-const auto ValidationTimeout = 10s;
+const std::chrono::seconds NXMAccessManager::ValidationTimeout = 10s;
ValidationProgressDialog::ValidationProgressDialog(std::chrono::seconds t)
diff --git a/src/nxmaccessmanager.h b/src/nxmaccessmanager.h
index f53f6648..cbec0530 100644
--- a/src/nxmaccessmanager.h
+++ b/src/nxmaccessmanager.h
@@ -67,6 +67,7 @@ class NXMAccessManager : public QNetworkAccessManager
{
Q_OBJECT
public:
+ static const std::chrono::seconds ValidationTimeout;
explicit NXMAccessManager(QObject *parent, const QString &moVersion);
diff --git a/src/settingsdialog.cpp b/src/settingsdialog.cpp
index 95e4ceb0..d1ace6a5 100644
--- a/src/settingsdialog.cpp
+++ b/src/settingsdialog.cpp
@@ -49,6 +49,8 @@ along with Mod Organizer. If not, see <http://www.gnu.org/licenses/>.
using namespace MOBase;
+const QString NexusSSO("wss://sso.nexusmods.com");
+const QString NexusSSOPage("https://www.nexusmods.com/sso?id=%1&application=modorganizer2");
class NexusManualKeyDialog : public QDialog
{
@@ -98,35 +100,177 @@ private:
};
+
+NexusSSOLogin::NexusSSOLogin()
+ : m_keyReceived(false), m_active(false)
+{
+ QObject::connect(
+ &m_socket, &QWebSocket::connected,
+ [&]{ onConnected(); });
+
+ QObject::connect(
+ &m_socket, qOverload<QAbstractSocket::SocketError>(&QWebSocket::error),
+ [&](auto&& e){ onError(e); });
+
+ QObject::connect(
+ &m_socket, &QWebSocket::textMessageReceived,
+ [&](auto&& s){ onMessage(s); });
+
+ QObject::connect(
+ &m_socket, &QWebSocket::disconnected,
+ [&]{ onDisconnected(); });
+
+ QObject::connect(&m_timeout, &QTimer::timeout, [&]{ onTimeout(); });
+}
+
+void NexusSSOLogin::start()
+{
+ m_active = true;
+ setState(ConnectingToSSO);
+ m_timeout.start(NXMAccessManager::ValidationTimeout);
+ m_socket.open(NexusSSO);
+}
+
+void NexusSSOLogin::cancel()
+{
+ abort();
+ setState(Cancelled);
+}
+
+void NexusSSOLogin::close()
+{
+ m_active = false;
+ m_timeout.stop();
+ m_socket.close();
+}
+
+void NexusSSOLogin::abort()
+{
+ m_active = false;
+ m_timeout.stop();
+ m_socket.abort();
+}
+
+bool NexusSSOLogin::isActive() const
+{
+ return m_active;
+}
+
+void NexusSSOLogin::setState(States s, const QString& error)
+{
+ if (stateChanged) {
+ stateChanged(s, error);
+ }
+}
+
+void NexusSSOLogin::onConnected()
+{
+ setState(WaitingForToken);
+
+ m_keyReceived = false;
+
+ //if (m_guid.isEmpty()) {
+ boost::uuids::random_generator generator;
+ boost::uuids::uuid sessionId = generator();
+ m_guid = boost::uuids::to_string(sessionId).c_str();
+ //}
+
+ QJsonObject data;
+ data.insert(QString("id"), QJsonValue(m_guid));
+ //data.insert(QString("token"), QJsonValue(m_token));
+ data.insert(QString("protocol"), 2);
+
+ const QString message = QJsonDocument(data).toJson();
+ m_socket.sendTextMessage(message);
+}
+
+void NexusSSOLogin::onMessage(const QString& s)
+{
+ const QJsonDocument doc = QJsonDocument::fromJson(s.toUtf8());
+ const QVariantMap root = doc.object().toVariantMap();
+
+ if (!root["success"].toBool()) {
+ close();
+
+ setState(Error, QString("There was a problem with SSO initialization: %1")
+ .arg(root["error"].toString()));
+
+ return;
+ }
+
+ const QVariantMap data = root["data"].toMap();
+
+ if (data.contains("connection_token")) {
+ // first answer
+ m_token = data["connection_token"].toString();
+
+ // open browser
+ const auto url = NexusSSOPage.arg(m_guid);
+ shell::OpenLink(url);
+
+ m_timeout.stop();
+ setState(WaitingForBrowser);
+ } else {
+ // second answer
+ const auto key = data["api_key"].toString();
+ close();
+
+ if (keyChanged) {
+ keyChanged(key);
+ }
+
+ setState(Finished);
+ }
+}
+
+void NexusSSOLogin::onDisconnected()
+{
+ if (m_active) {
+ m_active = false;
+
+ if (!m_keyReceived) {
+ setState(ClosedByRemote);
+ }
+ }
+}
+
+void NexusSSOLogin::onError(QAbstractSocket::SocketError e)
+{
+ if (m_active) {
+ setState(Error, m_socket.errorString());
+ }
+}
+
+void NexusSSOLogin::onTimeout()
+{
+ abort();
+ setState(Timeout);
+}
+
+
SettingsDialog::SettingsDialog(PluginContainer *pluginContainer, Settings* settings, QWidget *parent)
: TutorableDialog("SettingsDialog", parent)
, ui(new Ui::SettingsDialog)
, m_settings(settings)
, m_PluginContainer(pluginContainer)
- , m_nexusLogin(new QWebSocket)
- , m_KeyReceived(false)
- , m_KeyCleared(false)
+ , m_keyChanged(false)
, m_GeometriesReset(false)
{
+ m_nexusLogin.keyChanged = [&](auto&& s){ onKeyChanged(s); };
+ m_nexusLogin.stateChanged = [&](auto&& s, auto&& e){ onStateChanged(s, e); };
+
ui->setupUi(this);
ui->pluginSettingsList->setStyleSheet("QTreeWidget::item {padding-right: 10px;}");
QShortcut *delShortcut
= new QShortcut(QKeySequence(Qt::Key_Delete), ui->pluginBlacklist);
connect(delShortcut, SIGNAL(activated()), this, SLOT(deleteBlacklistItem()));
- connect(m_nexusLogin, SIGNAL(connected()), this, SLOT(dispatchLogin()));
- connect(m_nexusLogin, SIGNAL(error(QAbstractSocket::SocketError)), this, SLOT(authError(QAbstractSocket::SocketError)));
- connect(m_nexusLogin, SIGNAL(textMessageReceived(const QString &)), this, SLOT(receiveApiKey(const QString &)));
- connect(m_nexusLogin, SIGNAL(disconnected()), this, SLOT(completeApiConnection()));
- m_loginTimer.callOnTimeout(this, &SettingsDialog::loginPing);
updateNexusButtons();
}
SettingsDialog::~SettingsDialog()
{
- m_loginTimer.stop();
- m_nexusLogin->close();
disconnect(this);
delete ui;
}
@@ -188,7 +332,7 @@ bool SettingsDialog::getResetGeometries()
bool SettingsDialog::getApiKeyChanged()
{
- return m_KeyReceived || m_KeyCleared;
+ return m_keyChanged;
}
void SettingsDialog::on_categoriesBtn_clicked()
@@ -391,7 +535,11 @@ void SettingsDialog::on_resetDialogsButton_clicked()
void SettingsDialog::on_nexusConnect_clicked()
{
- fetchNexusApiKey();
+ if (m_nexusLogin.isActive()) {
+ m_nexusLogin.cancel();
+ } else {
+ fetchNexusApiKey();
+ }
}
void SettingsDialog::on_nexusManualKey_clicked()
@@ -415,92 +563,95 @@ void SettingsDialog::on_nexusManualKey_clicked()
void SettingsDialog::fetchNexusApiKey()
{
- QUrl url = QUrl("wss://sso.nexusmods.com");
- m_nexusLogin->open(url);
+ ui->nexusLog->clear();
+ m_nexusLogin.start();
updateNexusButtons();
}
-void SettingsDialog::dispatchLogin()
+void SettingsDialog::onKeyChanged(const QString& key)
{
- m_KeyReceived = false;
- QJsonObject login;
- if (m_UUID.isEmpty()) {
- boost::uuids::random_generator generator;
- boost::uuids::uuid sessionId = generator();
- m_UUID = boost::uuids::to_string(sessionId).c_str();
+ if (key.isEmpty()) {
+ clearKey();
+ } else {
+ setKey(key);
}
- login.insert(QString("id"), QJsonValue(m_UUID));
- login.insert(QString("token"), QJsonValue(m_AuthToken));
- login.insert(QString("protocol"), 2);
- QJsonDocument loginDoc(login);
- QString finalMessage(loginDoc.toJson());
- m_nexusLogin->sendTextMessage(finalMessage);
- QDesktopServices::openUrl(QUrl(QString("https://www.nexusmods.com/sso?id=%1&application=%2").arg(m_UUID).arg("modorganizer2")));
- m_loginTimer.start(30000);
}
-void SettingsDialog::loginPing()
+void SettingsDialog::onStateChanged(NexusSSOLogin::States s, const QString& e)
{
- if (m_nexusLogin->isValid()) {
- m_nexusLogin->ping();
- m_totalPings++;
- }
- if (m_totalPings >= 60) {
- m_loginTimer.stop();
- m_totalPings = 0;
- m_nexusLogin->close(QWebSocketProtocol::CloseCodeGoingAway, "Timeout: No response received after thirty minutes. Cancelling request.");
- }
-}
+ QString log;
-void SettingsDialog::authError(QAbstractSocket::SocketError error)
-{
- auto errorInfo = m_nexusLogin->errorString();
- qCritical() << "An error occurred: " << errorInfo;
-}
+ switch (s)
+ {
+ case NexusSSOLogin::Idle:
+ {
+ break;
+ }
-void SettingsDialog::receiveApiKey(const QString &response)
-{
- QJsonDocument responseDoc = QJsonDocument::fromJson(response.toUtf8());
- QVariantMap responseData = responseDoc.object().toVariantMap();
- if (responseData["success"].toBool()) {
- QVariantMap data = responseData["data"].toMap();
- if (data.contains("connection_token")) {
- m_AuthToken = data["connection_token"].toString();
- } else {
- const auto key = data["api_key"].toString();
+ case NexusSSOLogin::ConnectingToSSO:
+ {
+ log = tr("Connecting to Nexus...");
+ break;
+ }
+
+ case NexusSSOLogin::WaitingForToken:
+ {
+ log = tr("Waiting for Nexus...");
+ break;
+ }
- m_nexusLogin->close();
- m_loginTimer.stop();
- m_totalPings = 0;
+ case NexusSSOLogin::WaitingForBrowser:
+ {
+ log = tr("Opened browser, waiting for user...");
+ break;
+ }
- if (key.isEmpty()) {
- clearKey();
- } else {
- setKey(key);
- }
+ case NexusSSOLogin::Finished:
+ {
+ log = tr("Connected.");
+ break;
}
- } else {
- QString error("There was a problem with SSO initialization: %1");
- qCritical() << error.arg(responseData["error"].toString());
- m_nexusLogin->close();
- }
-}
-void SettingsDialog::completeApiConnection()
-{
- if (!m_KeyReceived && !m_loginTimer.isActive()) {
- QMessageBox::warning(qApp->activeWindow(), tr("Error"),
- tr("Failed to retrieve a Nexus API key! Please try again. "
- "A browser window should open asking you to authorize."));
+ case NexusSSOLogin::Timeout:
+ {
+ log = QObject::tr(
+ "No answer from Nexus.\n"
+ "A firewall might be blocking Mod Organizer.");
- // try again
- fetchNexusApiKey();
+ break;
+ }
+
+ case NexusSSOLogin::ClosedByRemote:
+ {
+ log = QObject::tr("Nexus closed the connection.");
+ break;
+ }
+
+ case NexusSSOLogin::Cancelled:
+ {
+ log = QObject::tr("Cancelled.");
+ break;
+ }
+
+ case NexusSSOLogin::Error:
+ {
+ log = tr("Error: %1.").arg(e);
+ break;
+ }
}
+
+ if (!log.isEmpty()) {
+ for (auto&& line : log.split("\n")) {
+ ui->nexusLog->addItem(line);
+ }
+ }
+
+ updateNexusButtons();
}
bool SettingsDialog::setKey(const QString& key)
{
- m_KeyReceived = true;
+ m_keyChanged = true;
const bool ret = m_settings->setNexusApiKey(key);
updateNexusButtons();
return ret;
@@ -508,7 +659,7 @@ bool SettingsDialog::setKey(const QString& key)
bool SettingsDialog::clearKey()
{
- m_KeyCleared = true;
+ m_keyChanged = true;
const auto ret = m_settings->clearNexusApiKey();
updateNexusButtons();
@@ -530,22 +681,22 @@ void SettingsDialog::testApiKey()
void SettingsDialog::updateNexusButtons()
{
- if (m_nexusLogin->state() != QAbstractSocket::UnconnectedState) {
+ if (m_nexusLogin.isActive()) {
// api key is in the process of being retrieved
- ui->nexusConnect->setText("Connecting the API. Please login within the browser and accept the request. This will time out after 30 minutes.");
- ui->nexusConnect->setEnabled(false);
+ ui->nexusConnect->setText(tr("Cancel"));
+ ui->nexusConnect->setEnabled(true);
ui->nexusDisconnect->setEnabled(false);
ui->nexusManualKey->setEnabled(false);
}
else if (m_settings->hasNexusApiKey()) {
// api key is present
- ui->nexusConnect->setText("Nexus API Key Stored");
+ ui->nexusConnect->setText(tr("Connect to Nexus"));
ui->nexusConnect->setEnabled(false);
ui->nexusDisconnect->setEnabled(true);
ui->nexusManualKey->setEnabled(false);
} else {
// api key not present
- ui->nexusConnect->setText("Connect to Nexus");
+ ui->nexusConnect->setText(tr("Connect to Nexus"));
ui->nexusConnect->setEnabled(true);
ui->nexusDisconnect->setEnabled(false);
ui->nexusManualKey->setEnabled(true);
@@ -622,6 +773,8 @@ void SettingsDialog::on_clearCacheButton_clicked()
void SettingsDialog::on_nexusDisconnect_clicked()
{
clearKey();
+ ui->nexusLog->clear();
+ ui->nexusLog->addItem(tr("Disconnected."));
}
void SettingsDialog::normalizePath(QLineEdit *lineEdit)
diff --git a/src/settingsdialog.h b/src/settingsdialog.h
index 858a36d4..aee447d7 100644
--- a/src/settingsdialog.h
+++ b/src/settingsdialog.h
@@ -35,6 +35,52 @@ namespace Ui {
class SettingsDialog;
}
+class NexusSSOLogin
+{
+public:
+ enum States
+ {
+ Idle,
+ ConnectingToSSO,
+ WaitingForToken,
+ WaitingForBrowser,
+ Finished,
+ Timeout,
+ ClosedByRemote,
+ Cancelled,
+ Error
+ };
+
+ std::function<void (QString)> keyChanged;
+ std::function<void (States, QString)> stateChanged;
+
+ NexusSSOLogin();
+
+ void start();
+ void cancel();
+
+ bool isActive() const;
+
+private:
+ QWebSocket m_socket;
+ QString m_guid;
+ bool m_keyReceived;
+ QString m_token;
+ bool m_active;
+ QTimer m_timeout;
+
+ void setState(States s, const QString& error={});
+
+ void close();
+ void abort();
+
+ void onConnected();
+ void onMessage(const QString& s);
+ void onDisconnected();
+ void onError(QAbstractSocket::SocketError e);
+ void onTimeout();
+};
+
/**
* dialog used to change settings for Mod Organizer. On top of the
* settings managed by the "Settings" class, this offers a button to open the
@@ -127,11 +173,6 @@ private slots:
void on_resetGeometryBtn_clicked();
void deleteBlacklistItem();
- void dispatchLogin();
- void loginPing();
- void authError(QAbstractSocket::SocketError error);
- void receiveApiKey(const QString &apiKey);
- void completeApiConnection();
private:
Ui::SettingsDialog *ui;
@@ -145,16 +186,11 @@ private:
QColor m_ContainsColor;
QColor m_ContainedColor;
- bool m_KeyReceived;
- bool m_KeyCleared;
bool m_GeometriesReset;
- QString m_UUID;
- QString m_AuthToken;
+ bool m_keyChanged;
QString m_ExecutableBlacklist;
- QWebSocket *m_nexusLogin;
- QTimer m_loginTimer;
- int m_totalPings = 0;
+ NexusSSOLogin m_nexusLogin;
bool setKey(const QString& key);
bool clearKey();
@@ -162,6 +198,8 @@ private:
void fetchNexusApiKey();
void testApiKey();
+ void onKeyChanged(const QString& key);
+ void onStateChanged(NexusSSOLogin::States s, const QString& e);
};
#endif // SETTINGSDIALOG_H
diff --git a/src/settingsdialog.ui b/src/settingsdialog.ui
index faaf1653..dfbde943 100644
--- a/src/settingsdialog.ui
+++ b/src/settingsdialog.ui
@@ -451,112 +451,245 @@ If you use pre-releases, never contact me directly by e-mail or via private mess
<attribute name="title">
<string>Nexus</string>
</attribute>
- <layout class="QVBoxLayout" name="verticalLayout_4">
+ <layout class="QVBoxLayout" name="verticalLayout_4" stretch="1,0,0,0,0,1">
<item>
- <widget class="QGroupBox" name="nexusBox">
- <property name="toolTip">
- <string>Allows Mod Organizer to connect to the Nexus for downloading mods, checking for updates, and other such things.</string>
- </property>
- <property name="whatsThis">
- <string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
-&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
-p, li { white-space: pre-wrap; }
-&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'MS Shell Dlg 2'; font-size:8pt; font-weight:400; font-style:normal;&quot;&gt;
-&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;Allows Mod Organizer to connect to the Nexus for downloading mods, checking for updates, and other such things. Clicking &amp;quot;Connect to Nexus&amp;quot; will open a Nexus webpage to authorise Mod Organizer. You will need to be logged into your Nexus account. The authorisation is stored in the Windows Credential Manager. Your Nexus username and password are not required or stored by Mod Organizer.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
- </property>
+ <widget class="QGroupBox" name="groupBox_4">
<property name="title">
- <string>Nexus</string>
+ <string>Nexus Connection</string>
</property>
- <layout class="QVBoxLayout" name="verticalLayout_2">
+ <layout class="QHBoxLayout" name="horizontalLayout_14">
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_10">
- <item>
- <widget class="QPushButton" name="nexusConnect">
- <property name="text">
- <string>Connect to Nexus</string>
- </property>
- </widget>
- </item>
- </layout>
+ <widget class="QWidget" name="widget_2" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_11">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QPushButton" name="nexusConnect">
+ <property name="text">
+ <string>Connect to Nexus</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="nexusManualKey">
+ <property name="toolTip">
+ <string>Manually enter the API key and try to login</string>
+ </property>
+ <property name="text">
+ <string>Enter API Key Manually</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="nexusDisconnect">
+ <property name="toolTip">
+ <string>Clear the stored Nexus API key and force reauthorization.</string>
+ </property>
+ <property name="text">
+ <string>Disconnect from Nexus</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/edit_clear</normaloff>:/MO/gui/edit_clear</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <spacer name="verticalSpacer_3">
+ <property name="orientation">
+ <enum>Qt::Vertical</enum>
+ </property>
+ <property name="sizeHint" stdset="0">
+ <size>
+ <width>0</width>
+ <height>0</height>
+ </size>
+ </property>
+ </spacer>
+ </item>
+ </layout>
+ </widget>
</item>
<item>
- <layout class="QHBoxLayout" name="horizontalLayout_11">
- <item>
- <spacer name="horizontalSpacer_7">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- <item>
- <widget class="QPushButton" name="nexusManualKey">
- <property name="sizePolicy">
- <sizepolicy hsizetype="Minimum" vsizetype="Preferred">
- <horstretch>0</horstretch>
- <verstretch>0</verstretch>
- </sizepolicy>
- </property>
- <property name="toolTip">
- <string>Manually enter the API key and try to login</string>
- </property>
- <property name="text">
- <string>Enter API Key Manually</string>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="nexusDisconnect">
- <property name="toolTip">
- <string>Clear the stored Nexus API key and force reauthorization.</string>
- </property>
- <property name="text">
- <string>Disconnect from Nexus</string>
- </property>
- <property name="icon">
- <iconset resource="resources.qrc">
- <normaloff>:/MO/gui/edit_clear</normaloff>:/MO/gui/edit_clear</iconset>
- </property>
- </widget>
- </item>
- <item>
- <widget class="QPushButton" name="clearCacheButton">
- <property name="toolTip">
- <string>Remove cache and cookies.</string>
- </property>
- <property name="text">
- <string>Clear Cache</string>
- </property>
- <property name="icon">
- <iconset resource="resources.qrc">
- <normaloff>:/MO/gui/edit_clear</normaloff>:/MO/gui/edit_clear</iconset>
- </property>
- </widget>
- </item>
- <item>
- <spacer name="horizontalSpacer_5">
- <property name="orientation">
- <enum>Qt::Horizontal</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>40</width>
- <height>20</height>
- </size>
- </property>
- </spacer>
- </item>
- </layout>
+ <widget class="QWidget" name="widget_3" native="true">
+ <layout class="QVBoxLayout" name="verticalLayout_12">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QListWidget" name="nexusLog">
+ <property name="sizeAdjustPolicy">
+ <enum>QAbstractScrollArea::AdjustToContents</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QWidget" name="widget" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_5">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ <item>
+ <widget class="QGroupBox" name="groupBox_2">
+ <property name="title">
+ <string>Nexus Account</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_2">
+ <property name="text">
+ <string>User ID</string>
+ </property>
+ <property name="textInteractionFlags">
+ <set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse</set>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="nexusUserID">
+ <property name="text">
+ <string>id</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_3">
+ <property name="text">
+ <string>Username</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="nexusUsername">
+ <property name="text">
+ <string>username</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_11">
+ <property name="text">
+ <string>Account</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="nexusAccount">
+ <property name="text">
+ <string>account</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
+ </item>
+ <item>
+ <widget class="QGroupBox" name="groupBox_3">
+ <property name="title">
+ <string>Statistics</string>
+ </property>
+ <layout class="QFormLayout" name="formLayout_3">
+ <item row="0" column="0">
+ <widget class="QLabel" name="label_30">
+ <property name="text">
+ <string>Daily requests</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="0">
+ <widget class="QLabel" name="label_31">
+ <property name="text">
+ <string>Hourly requests</string>
+ </property>
+ </widget>
+ </item>
+ <item row="1" column="1">
+ <widget class="QLabel" name="nexusHourlyRequests">
+ <property name="text">
+ <string>hourly requests</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="0">
+ <widget class="QLabel" name="label_32">
+ <property name="text">
+ <string>Requests queued</string>
+ </property>
+ </widget>
+ </item>
+ <item row="2" column="1">
+ <widget class="QLabel" name="nexusRequestsQueued">
+ <property name="text">
+ <string>queued</string>
+ </property>
+ </widget>
+ </item>
+ <item row="0" column="1">
+ <widget class="QLabel" name="nexusDailyRequests">
+ <property name="text">
+ <string>daily requests</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </widget>
</item>
</layout>
</widget>
</item>
<item>
+ <widget class="QWidget" name="widget_4" native="true">
+ <layout class="QHBoxLayout" name="horizontalLayout_10">
+ <property name="leftMargin">
+ <number>0</number>
+ </property>
+ <property name="topMargin">
+ <number>0</number>
+ </property>
+ <property name="rightMargin">
+ <number>0</number>
+ </property>
+ <property name="bottomMargin">
+ <number>0</number>
+ </property>
+ </layout>
+ </widget>
+ </item>
+ <item>
<layout class="QGridLayout" name="gridLayout_8">
<item row="0" column="0">
<widget class="QCheckBox" name="offlineBox">
@@ -619,6 +752,20 @@ p, li { white-space: pre-wrap; }
</widget>
</item>
<item>
+ <widget class="QPushButton" name="clearCacheButton">
+ <property name="toolTip">
+ <string>Remove cache and cookies.</string>
+ </property>
+ <property name="text">
+ <string>Clear Cache</string>
+ </property>
+ <property name="icon">
+ <iconset resource="resources.qrc">
+ <normaloff>:/MO/gui/edit_clear</normaloff>:/MO/gui/edit_clear</iconset>
+ </property>
+ </widget>
+ </item>
+ <item>
<spacer name="horizontalSpacer_4">
<property name="orientation">
<enum>Qt::Horizontal</enum>
@@ -682,19 +829,6 @@ p, li { white-space: pre-wrap; }
</item>
</layout>
</item>
- <item>
- <spacer name="verticalSpacer_3">
- <property name="orientation">
- <enum>Qt::Vertical</enum>
- </property>
- <property name="sizeHint" stdset="0">
- <size>
- <width>20</width>
- <height>40</height>
- </size>
- </property>
- </spacer>
- </item>
</layout>
</widget>
<widget class="QWidget" name="steamTab">
@@ -1359,7 +1493,6 @@ programs you are intentionally running.</string>
<tabstop>browseProfilesDirBtn</tabstop>
<tabstop>overwriteDirEdit</tabstop>
<tabstop>browseOverwriteDirBtn</tabstop>
- <tabstop>clearCacheButton</tabstop>
<tabstop>associateButton</tabstop>
<tabstop>knownServersList</tabstop>
<tabstop>preferredServersList</tabstop>