Work on issue #4

This commit is contained in:
Alex Spataru 2016-11-04 09:35:13 -06:00
parent fbb37ed225
commit c25b106280
9 changed files with 171 additions and 79 deletions

View File

@ -68,11 +68,13 @@ class QSU_DECL QSimpleUpdater : public QObject {
signals: signals:
void checkingFinished (const QString& url); void checkingFinished (const QString& url);
void appcastDownloaded (const QString& url, const QByteArray& data);
void downloadFinished (const QString& url, const QString& filepath); void downloadFinished (const QString& url, const QString& filepath);
public: public:
static QSimpleUpdater* getInstance(); static QSimpleUpdater* getInstance();
bool usesCustomAppcast (const QString& url) const;
bool getNotifyOnUpdate (const QString& url) const; bool getNotifyOnUpdate (const QString& url) const;
bool getNotifyOnFinish (const QString& url) const; bool getNotifyOnFinish (const QString& url) const;
bool getUpdateAvailable (const QString& url) const; bool getUpdateAvailable (const QString& url) const;
@ -90,12 +92,13 @@ class QSU_DECL QSimpleUpdater : public QObject {
public slots: public slots:
void checkForUpdates (const QString& url); void checkForUpdates (const QString& url);
void setModuleName (const QString& url, const QString& name); void setModuleName (const QString& url, const QString& name);
void setNotifyOnUpdate (const QString& url, const bool& notify); void setNotifyOnUpdate (const QString& url, const bool notify);
void setNotifyOnFinish (const QString& url, const bool& notify); void setNotifyOnFinish (const QString& url, const bool notify);
void setPlatformKey (const QString& url, const QString& platform); void setPlatformKey (const QString& url, const QString& platform);
void setModuleVersion (const QString& url, const QString& version); void setModuleVersion (const QString& url, const QString& version);
void setDownloaderEnabled (const QString& url, const bool& enabled); void setDownloaderEnabled (const QString& url, const bool enabled);
void setUseCustomInstallProcedures (const QString& url, const bool& custom); void setUseCustomAppcast (const QString& url, const bool customAppcast);
void setUseCustomInstallProcedures (const QString& url, const bool custom);
protected: protected:
~QSimpleUpdater(); ~QSimpleUpdater();

View File

@ -201,7 +201,8 @@ void Downloader::onDownloadFinished() {
QString name = m_reply->url().toString().split ("/").last(); QString name = m_reply->url().toString().split ("/").last();
/* Check if we need to redirect */ /* Check if we need to redirect */
QUrl url = m_reply->attribute (QNetworkRequest::RedirectionTargetAttribute).toUrl(); QUrl url = m_reply->attribute (
QNetworkRequest::RedirectionTargetAttribute).toUrl();
if (!url.isEmpty()) { if (!url.isEmpty()) {
startDownload (url); startDownload (url);
return; return;
@ -326,6 +327,6 @@ qreal Downloader::round (const qreal& input) {
* Use the signals fired by the \c QSimpleUpdater to implement your own install * Use the signals fired by the \c QSimpleUpdater to implement your own install
* procedures. * procedures.
*/ */
void Downloader::setUseCustomInstallProcedures (const bool& custom) { void Downloader::setUseCustomInstallProcedures (const bool custom) {
m_useCustomProcedures = custom; m_useCustomProcedures = custom;
} }

View File

@ -57,7 +57,7 @@ class Downloader : public QWidget {
public slots: public slots:
void startDownload (const QUrl& url); void startDownload (const QUrl& url);
void setUseCustomInstallProcedures (const bool& custom); void setUseCustomInstallProcedures (const bool custom);
private slots: private slots:
void openDownload(); void openDownload();

View File

@ -35,6 +35,10 @@ static QList<Updater*> UPDATERS;
QSimpleUpdater::~QSimpleUpdater() { QSimpleUpdater::~QSimpleUpdater() {
URLS.clear(); URLS.clear();
foreach (Updater* updater, UPDATERS)
updater->deleteLater();
UPDATERS.clear(); UPDATERS.clear();
} }
@ -46,6 +50,18 @@ QSimpleUpdater* QSimpleUpdater::getInstance() {
return &updater; return &updater;
} }
/**
* Returns \c true if the \c Updater instance registered with the given \a url
* uses a custom appcast format and/or allows the application to read and
* interpret the downloaded appcast file
*
* \note If an \c Updater instance registered with the given \a url is not
* found, that \c Updater instance will be initialized automatically
*/
bool QSimpleUpdater::usesCustomAppcast (const QString& url) const {
return getUpdater (url)->customAppcast();
}
/** /**
* Returns \c true if the \c Updater instance registered with the given \a url * Returns \c true if the \c Updater instance registered with the given \a url
* shall notify the user when an update is available. * shall notify the user when an update is available.
@ -230,7 +246,7 @@ void QSimpleUpdater::setModuleName (const QString& url, const QString& name) {
* found, that \c Updater instance will be initialized automatically * found, that \c Updater instance will be initialized automatically
*/ */
void QSimpleUpdater::setNotifyOnUpdate (const QString& url, void QSimpleUpdater::setNotifyOnUpdate (const QString& url,
const bool& notify) { const bool notify) {
getUpdater (url)->setNotifyOnUpdate (notify); getUpdater (url)->setNotifyOnUpdate (notify);
} }
@ -243,7 +259,7 @@ void QSimpleUpdater::setNotifyOnUpdate (const QString& url,
* found, that \c Updater instance will be initialized automatically * found, that \c Updater instance will be initialized automatically
*/ */
void QSimpleUpdater::setNotifyOnFinish (const QString& url, void QSimpleUpdater::setNotifyOnFinish (const QString& url,
const bool& notify) { const bool notify) {
getUpdater (url)->setNotifyOnFinish (notify); getUpdater (url)->setNotifyOnFinish (notify);
} }
@ -288,10 +304,15 @@ void QSimpleUpdater::setModuleVersion (const QString& url,
* found, that \c Updater instance will be initialized automatically * found, that \c Updater instance will be initialized automatically
*/ */
void QSimpleUpdater::setDownloaderEnabled (const QString& url, void QSimpleUpdater::setDownloaderEnabled (const QString& url,
const bool& enabled) { const bool enabled) {
getUpdater (url)->setDownloaderEnabled (enabled); getUpdater (url)->setDownloaderEnabled (enabled);
} }
void QSimpleUpdater::setUseCustomAppcast (const QString& url,
const bool customAppcast) {
getUpdater (url)->setUseCustomAppcast (customAppcast);
}
/** /**
* If the \a custom parameter is set to \c true, the \c Updater instance * If the \a custom parameter is set to \c true, the \c Updater instance
* registered with the given \a url will not try to open the downloaded file. * registered with the given \a url will not try to open the downloaded file.
@ -304,7 +325,7 @@ void QSimpleUpdater::setDownloaderEnabled (const QString& url,
* found, that \c Updater instance will be initialized automatically * found, that \c Updater instance will be initialized automatically
*/ */
void QSimpleUpdater::setUseCustomInstallProcedures (const QString& url, void QSimpleUpdater::setUseCustomInstallProcedures (const QString& url,
const bool& custom) { const bool custom) {
getUpdater (url)->setUseCustomInstallProcedures (custom); getUpdater (url)->setUseCustomInstallProcedures (custom);
} }
@ -326,6 +347,8 @@ Updater* QSimpleUpdater::getUpdater (const QString& url) const {
this, SIGNAL (checkingFinished (QString))); this, SIGNAL (checkingFinished (QString)));
connect (updater, SIGNAL (downloadFinished (QString, QString)), connect (updater, SIGNAL (downloadFinished (QString, QString)),
this, SIGNAL (downloadFinished (QString, QString))); this, SIGNAL (downloadFinished (QString, QString)));
connect (updater, SIGNAL (appcastDownloaded (QString, QByteArray)),
this, SIGNAL (appcastDownloaded (QString, QByteArray)));
} }
return UPDATERS.at (URLS.indexOf (url)); return UPDATERS.at (URLS.indexOf (url));

View File

@ -43,6 +43,7 @@ Updater::Updater() {
m_changelog = ""; m_changelog = "";
m_downloadUrl = ""; m_downloadUrl = "";
m_latestVersion = ""; m_latestVersion = "";
m_customAppcast = false;
m_notifyOnUpdate = true; m_notifyOnUpdate = true;
m_notifyOnFinish = false; m_notifyOnFinish = false;
m_updateAvailable = false; m_updateAvailable = false;
@ -144,6 +145,15 @@ QString Updater::moduleVersion() const {
return m_moduleVersion; return m_moduleVersion;
} }
/**
* Returns \c true if the updater should NOT interpret the downloaded appcast.
* This is useful if you need to store more variables (or information) in the
* JSON file or use another appcast format (e.g. XML)
*/
bool Updater::customAppcast() const {
return m_customAppcast;
}
/** /**
* Returns \c true if the updater should notify the user when an update is * Returns \c true if the updater should notify the user when an update is
* available. * available.
@ -218,7 +228,7 @@ void Updater::setModuleName (const QString& name) {
* If \a notify is set to \c true, then the \c Updater will notify the user * If \a notify is set to \c true, then the \c Updater will notify the user
* when an update is available. * when an update is available.
*/ */
void Updater::setNotifyOnUpdate (const bool& notify) { void Updater::setNotifyOnUpdate (const bool notify) {
m_notifyOnUpdate = notify; m_notifyOnUpdate = notify;
} }
@ -226,7 +236,7 @@ void Updater::setNotifyOnUpdate (const bool& notify) {
* If \a notify is set to \c true, then the \c Updater will notify the user * If \a notify is set to \c true, then the \c Updater will notify the user
* when it has finished interpreting the update definitions file. * when it has finished interpreting the update definitions file.
*/ */
void Updater::setNotifyOnFinish (const bool& notify) { void Updater::setNotifyOnFinish (const bool notify) {
m_notifyOnFinish = notify; m_notifyOnFinish = notify;
} }
@ -244,7 +254,7 @@ void Updater::setModuleVersion (const QString& version) {
* If the \a enabled parameter is set to \c true, the \c Updater will open the * If the \a enabled parameter is set to \c true, the \c Updater will open the
* integrated downloader if the user agrees to install the update (if any) * integrated downloader if the user agrees to install the update (if any)
*/ */
void Updater::setDownloaderEnabled (const bool& enabled) { void Updater::setDownloaderEnabled (const bool enabled) {
m_downloaderEnabled = enabled; m_downloaderEnabled = enabled;
} }
@ -261,12 +271,22 @@ void Updater::setPlatformKey (const QString& platformKey) {
m_platform = platformKey; m_platform = platformKey;
} }
/**
* If the \a customAppcast parameter is set to \c true, then the \c Updater
* will not try to read the network reply from the server, instead, it will
* emit the \c appcastDownloaded() signal, which allows the application to
* read and interpret the appcast file by itself
*/
void Updater::setUseCustomAppcast (const bool customAppcast) {
m_customAppcast = customAppcast;
}
/** /**
* If the \a custom parameter is set to \c true, the \c Updater will not try * If the \a custom parameter is set to \c true, the \c Updater will not try
* to open the downloaded file. Use the signals fired by the \c QSimpleUpdater * to open the downloaded file. Use the signals fired by the \c QSimpleUpdater
* to install the update from the downloaded file by yourself. * to install the update from the downloaded file by yourself.
*/ */
void Updater::setUseCustomInstallProcedures (const bool& custom) { void Updater::setUseCustomInstallProcedures (const bool custom) {
m_downloader->setUseCustomInstallProcedures (custom); m_downloader->setUseCustomInstallProcedures (custom);
} }
@ -274,23 +294,44 @@ void Updater::setUseCustomInstallProcedures (const bool& custom) {
* Called when the download of the update definitions file is finished. * Called when the download of the update definitions file is finished.
*/ */
void Updater::onReply (QNetworkReply* reply) { void Updater::onReply (QNetworkReply* reply) {
if (reply->error() == QNetworkReply::NoError) { /* Check if we need to redirect */
QUrl redirect = reply->attribute (
QNetworkRequest::RedirectionTargetAttribute).toUrl();
if (!redirect.isEmpty()) {
setUrl (redirect.toString());
checkForUpdates();
return;
}
/* There was a network error */
if (reply->error() != QNetworkReply::NoError)
return;
/* The application wants to interpret the appcast by itself */
if (customAppcast()) {
emit appcastDownloaded (url(), reply->readAll());
return;
}
/* Try to create a JSON document from downloaded data */
QJsonDocument document = QJsonDocument::fromJson (reply->readAll()); QJsonDocument document = QJsonDocument::fromJson (reply->readAll());
/* JSON is invalid */
if (document.isNull()) if (document.isNull())
return; return;
/* Get the platform information */
QJsonObject updates = document.object().value ("updates").toObject(); QJsonObject updates = document.object().value ("updates").toObject();
QJsonObject platform = updates.value (platformKey()).toObject(); QJsonObject platform = updates.value (platformKey()).toObject();
/* Get update information */
m_openUrl = platform.value ("open-url").toString(); m_openUrl = platform.value ("open-url").toString();
m_changelog = platform.value ("changelog").toString(); m_changelog = platform.value ("changelog").toString();
m_downloadUrl = platform.value ("download-url").toString(); m_downloadUrl = platform.value ("download-url").toString();
m_latestVersion = platform.value ("latest-version").toString(); m_latestVersion = platform.value ("latest-version").toString();
/* Compare latest and current version */
setUpdateAvailable (compare (latestVersion(), moduleVersion())); setUpdateAvailable (compare (latestVersion(), moduleVersion()));
}
emit checkingFinished (url()); emit checkingFinished (url());
} }
@ -298,7 +339,7 @@ void Updater::onReply (QNetworkReply* reply) {
* Prompts the user based on the value of the \a available parameter and the * Prompts the user based on the value of the \a available parameter and the
* settings of this instance of the \c Updater class. * settings of this instance of the \c Updater class.
*/ */
void Updater::setUpdateAvailable (const bool& available) { void Updater::setUpdateAvailable (const bool available) {
m_updateAvailable = available; m_updateAvailable = available;
QMessageBox box; QMessageBox box;

View File

@ -48,6 +48,7 @@ class QSU_DECL Updater : public QObject {
signals: signals:
void checkingFinished (const QString& url); void checkingFinished (const QString& url);
void downloadFinished (const QString& url, const QString& filepath); void downloadFinished (const QString& url, const QString& filepath);
void appcastDownloaded (const QString& url, const QByteArray& data);
public: public:
Updater(); Updater();
@ -62,6 +63,7 @@ class QSU_DECL Updater : public QObject {
QString moduleVersion() const; QString moduleVersion() const;
QString latestVersion() const; QString latestVersion() const;
bool customAppcast() const;
bool notifyOnUpdate() const; bool notifyOnUpdate() const;
bool notifyOnFinish() const; bool notifyOnFinish() const;
bool updateAvailable() const; bool updateAvailable() const;
@ -72,16 +74,17 @@ class QSU_DECL Updater : public QObject {
void checkForUpdates(); void checkForUpdates();
void setUrl (const QString& url); void setUrl (const QString& url);
void setModuleName (const QString& name); void setModuleName (const QString& name);
void setNotifyOnUpdate (const bool& notify); void setNotifyOnUpdate (const bool notify);
void setNotifyOnFinish (const bool& notify); void setNotifyOnFinish (const bool notify);
void setModuleVersion (const QString& version); void setModuleVersion (const QString& version);
void setDownloaderEnabled (const bool& enabled); void setDownloaderEnabled (const bool enabled);
void setPlatformKey (const QString& platformKey); void setPlatformKey (const QString& platformKey);
void setUseCustomInstallProcedures (const bool& custom); void setUseCustomAppcast (const bool customAppcast);
void setUseCustomInstallProcedures (const bool custom);
private slots: private slots:
void onReply (QNetworkReply* reply); void onReply (QNetworkReply* reply);
void setUpdateAvailable (const bool& available); void setUpdateAvailable (const bool available);
private: private:
bool compare (const QString& x, const QString& y); bool compare (const QString& x, const QString& y);
@ -89,6 +92,7 @@ class QSU_DECL Updater : public QObject {
private: private:
QString m_url; QString m_url;
bool m_customAppcast;
bool m_notifyOnUpdate; bool m_notifyOnUpdate;
bool m_notifyOnFinish; bool m_notifyOnFinish;
bool m_updateAvailable; bool m_updateAvailable;

View File

@ -35,6 +35,8 @@ Window::Window (QWidget* parent) : QMainWindow (parent) {
/* Check for updates when the "Check For Updates" button is clicked */ /* Check for updates when the "Check For Updates" button is clicked */
connect (m_updater, SIGNAL (checkingFinished (QString)), connect (m_updater, SIGNAL (checkingFinished (QString)),
this, SLOT (updateChangelog (QString))); this, SLOT (updateChangelog (QString)));
connect (m_updater, SIGNAL (appcastDownloaded (QString, QByteArray)),
this, SLOT (displayAppcast (QString, QByteArray)));
/* React to button clicks */ /* React to button clicks */
connect (m_ui->resetButton, SIGNAL (clicked()), connect (m_ui->resetButton, SIGNAL (clicked()),
@ -45,7 +47,8 @@ Window::Window (QWidget* parent) : QMainWindow (parent) {
this, SLOT (checkForUpdates())); this, SLOT (checkForUpdates()));
/* Resize the dialog to fit */ /* Resize the dialog to fit */
setFixedSize (minimumSizeHint()); setMinimumSize (minimumSizeHint());
resize (minimumSizeHint());
/* Reset the UI state */ /* Reset the UI state */
resetFields(); resetFields();
@ -65,6 +68,7 @@ Window::~Window() {
void Window::resetFields() { void Window::resetFields() {
m_ui->installedVersion->setText ("0.1"); m_ui->installedVersion->setText ("0.1");
m_ui->customAppcast->setChecked (false);
m_ui->enableDownloader->setChecked (true); m_ui->enableDownloader->setChecked (true);
m_ui->showAllNotifcations->setChecked (false); m_ui->showAllNotifcations->setChecked (false);
m_ui->showUpdateNotifications->setChecked (true); m_ui->showUpdateNotifications->setChecked (true);
@ -77,6 +81,7 @@ void Window::resetFields() {
void Window::checkForUpdates() { void Window::checkForUpdates() {
/* Get settings from the UI */ /* Get settings from the UI */
QString version = m_ui->installedVersion->text(); QString version = m_ui->installedVersion->text();
bool customAppcast = m_ui->customAppcast->isChecked();
bool downloaderEnabled = m_ui->enableDownloader->isChecked(); bool downloaderEnabled = m_ui->enableDownloader->isChecked();
bool notifyOnFinish = m_ui->showAllNotifcations->isChecked(); bool notifyOnFinish = m_ui->showAllNotifcations->isChecked();
bool notifyOnUpdate = m_ui->showUpdateNotifications->isChecked(); bool notifyOnUpdate = m_ui->showUpdateNotifications->isChecked();
@ -85,6 +90,7 @@ void Window::checkForUpdates() {
m_updater->setModuleVersion (DEFS_URL, version); m_updater->setModuleVersion (DEFS_URL, version);
m_updater->setNotifyOnFinish (DEFS_URL, notifyOnFinish); m_updater->setNotifyOnFinish (DEFS_URL, notifyOnFinish);
m_updater->setNotifyOnUpdate (DEFS_URL, notifyOnUpdate); m_updater->setNotifyOnUpdate (DEFS_URL, notifyOnUpdate);
m_updater->setUseCustomAppcast (DEFS_URL, customAppcast);
m_updater->setDownloaderEnabled (DEFS_URL, downloaderEnabled); m_updater->setDownloaderEnabled (DEFS_URL, downloaderEnabled);
/* Check for updates */ /* Check for updates */
@ -95,7 +101,26 @@ void Window::checkForUpdates() {
// Window::updateChangelog // Window::updateChangelog
//============================================================================== //==============================================================================
void Window::updateChangelog (QString url) { void Window::updateChangelog (const QString& url) {
if (url == DEFS_URL) if (url == DEFS_URL)
m_ui->changelogText->setText (m_updater->getChangelog (url)); m_ui->changelogText->setText (m_updater->getChangelog (url));
} }
//==============================================================================
// Window::displayAppcast
//==============================================================================
void Window::displayAppcast (const QString& url, const QByteArray& reply) {
if (url == DEFS_URL) {
QString text = "This is the downloaded appcast: <p><pre>" +
QString::fromUtf8 (reply) +
"</pre></p><p> If you need to store more information on the "
"appcast (or use another format), just use the "
"<b>QSimpleUpdater::setCustomAppcast()</b> function. "
"It allows your application to interpret the appcast "
"using your code and not QSU's code.</p>";
m_ui->changelogText->setText (text);
}
}

View File

@ -28,7 +28,8 @@ class Window : public QMainWindow {
public slots: public slots:
void resetFields(); void resetFields();
void checkForUpdates(); void checkForUpdates();
void updateChangelog (QString url); void updateChangelog (const QString& url);
void displayAppcast (const QString& url, const QByteArray& reply);
private: private:
Ui::Window* m_ui; Ui::Window* m_ui;

View File

@ -6,8 +6,8 @@
<rect> <rect>
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>496</width> <width>483</width>
<height>487</height> <height>494</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
@ -82,6 +82,23 @@
<string>Updater Options</string> <string>Updater Options</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="3" column="0">
<widget class="QCheckBox" name="showAllNotifcations">
<property name="text">
<string>Show all notifications</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="installedVersion">
<property name="text">
<string>0.1</string>
</property>
<property name="placeholderText">
<string>Write a version string...</string>
</property>
</widget>
</item>
<item row="5" column="0"> <item row="5" column="0">
<widget class="QCheckBox" name="enableDownloader"> <widget class="QCheckBox" name="enableDownloader">
<property name="text"> <property name="text">
@ -102,13 +119,6 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="3" column="0">
<widget class="QCheckBox" name="showAllNotifcations">
<property name="text">
<string>Show all notifications</string>
</property>
</widget>
</item>
<item row="2" column="0"> <item row="2" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
@ -116,13 +126,10 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="2" column="1"> <item row="6" column="0">
<widget class="QLineEdit" name="installedVersion"> <widget class="QCheckBox" name="customAppcast">
<property name="text"> <property name="text">
<string>0.1</string> <string>Do not use the QSU library to read the appcast</string>
</property>
<property name="placeholderText">
<string>Write a version string...</string>
</property> </property>
</widget> </widget>
</item> </item>
@ -144,27 +151,14 @@
<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; <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; &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; } p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'.Lucida Grande UI'; font-size:13pt; font-weight:400; font-style:normal;&quot;&gt; &lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'.SF NS Text'; font-size:13pt; 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;Click &amp;quot;Check for Updates&amp;quot; to update this field...&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string> &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;&lt;span style=&quot; font-family:'.Lucida Grande UI';&quot;&gt;Click &amp;quot;Check for Updates&amp;quot; to update this field...&lt;/span&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property> </property>
</widget> </widget>
</item> </item>
</layout> </layout>
</widget> </widget>
</item> </item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item> <item>
<widget class="QFrame" name="frame"> <widget class="QFrame" name="frame">
<property name="frameShape"> <property name="frameShape">