diff --git a/configure.in b/configure.in
index 45f0418..86a22e4 100644
--- a/configure.in
+++ b/configure.in
@@ -169,7 +169,7 @@ AC_ARG_ENABLE([qt],AS_HELP_STRING([--enable-qt],
[Qt interface support (default disabled)]))
qt_ok=0
AS_IF([test "${enable_qt}" != "no"],
- [PKG_CHECK_MODULES(QT, [QtCore QtGui >= 4.2.0],
+ [PKG_CHECK_MODULES(QT, [QtCore QtGui QtNetwork >= 4.2.0],
[qt_ok=1
AC_PATH_PROG(MOC, moc, moc,`$PKG_CONFIG --variable=exec_prefix QtCore`/bin)
AC_PATH_PROG(UIC, uic, uic,`$PKG_CONFIG --variable=exec_prefix QtCore`/bin)
diff --git a/qt/Makefile.am b/qt/Makefile.am
index d5614cb..881a3cf 100644
--- a/qt/Makefile.am
+++ b/qt/Makefile.am
@@ -90,6 +90,7 @@ eliot_SOURCES = \
training_widget.cpp training_widget.h \
player_widget.cpp player_widget.h \
prefs_dialog.cpp prefs_dialog.h \
+ update_checker.cpp update_checker.h \
aux_window.cpp aux_window.h \
main_window.cpp main_window.h \
main.cpp
@@ -137,6 +138,7 @@ nodist_eliot_SOURCES = \
topping_widget.moc.cpp \
training_widget.moc.cpp \
prefs_dialog.moc.cpp \
+ update_checker.moc.cpp \
aux_window.moc.cpp \
main_window.moc.cpp \
resources.cpp
diff --git a/qt/main_window.cpp b/qt/main_window.cpp
index 54eeb0f..f918ac9 100644
--- a/qt/main_window.cpp
+++ b/qt/main_window.cpp
@@ -70,6 +70,7 @@
#include "timer_widget.h"
#include "dic_wizard.h"
#include "aux_window.h"
+#include "update_checker.h"
#include "qtcommon.h"
@@ -217,6 +218,12 @@ MainWindow::MainWindow(QWidget *iParent)
}
}
emit dicChanged(m_dic);
+
+ // Check for updates
+ UpdateChecker *checker = new UpdateChecker(this);
+ QObject::connect(checker, SIGNAL(notifyInfo(QString)),
+ this, SLOT(displayInfoMsg(QString)));
+ checker->checkForUpdate();
}
@@ -1422,7 +1429,8 @@ void MainWindow::onHelpAbout()
"published by the Free Software Foundation; either version 2 of " \
"the License, or (at your option) any later version.");
msg += "
";
- // Translate the URL, because the web site is translated in French
+ // TRANSLATORS: If the website is translated in your language,
+ // feel free to adapt the URL.
QString url = _q("http://www.nongnu.org/eliot/en/");
msg += _q("Web site: %1").arg(QString("%2").arg(url).arg(url));
// QMessageBox::about() doesn't add the nice information icon, so we create
diff --git a/qt/prefs_dialog.cpp b/qt/prefs_dialog.cpp
index b2f4422..5914049 100644
--- a/qt/prefs_dialog.cpp
+++ b/qt/prefs_dialog.cpp
@@ -44,6 +44,7 @@ const QString PrefsDialog::kINTF_TIMER_TOTAL_DURATION = "Interface/TimerTotalDur
const QString PrefsDialog::kINTF_TIMER_ALERT_DURATION = "Interface/TimerAlertDuration";
const QString PrefsDialog::kINTF_TIMER_BEEPS = "Interface/TimerBeeps";
const QString PrefsDialog::kINTF_TIMER_AUTO_START = "Interface/TimerAutoStart";
+const QString PrefsDialog::kINTF_CHECK_FOR_UPDATES = "Interface/CheckForUpdates";
const QString PrefsDialog::kARBIT_AUTO_MASTER = "Arbitration/AutoAssignMaster";
const QString PrefsDialog::kARBIT_LINK_7P1 = "Arbitration/LinkRackWith7P1";
const QString PrefsDialog::kDEFAULT_DEF_SITE = "http://fr.wiktionary.org/wiki/%w";
@@ -87,6 +88,9 @@ PrefsDialog::PrefsDialog(QWidget *iParent)
"reaches the alert level, and when it reaches 0."));
checkBoxTimerAutoStart->setToolTip(_q("If checked, the timer will be reinitialized and restarted\n"
"automatically every time that the main rack changes."));
+ checkBoxIntfCheckUpdates->setToolTip(_q("If checked, Eliot will connect to the Internet from time to time\n"
+ "(about once a week) to check if new versions are available.\n"
+ "New versions are never installed automatically, you just get a notification."));
spinBoxTrainSearchLimit->setToolTip(_q("Maximum number of results returned by a search.\n"
"The returned results will always be the best ones.\n"
"Use 0 to disable the limit (warning: searches yielding many "
@@ -138,6 +142,8 @@ PrefsDialog::PrefsDialog(QWidget *iParent)
checkBoxTimerBeeps->setChecked(timerBeeps);
bool timerAutoStart = qs.value(kINTF_TIMER_AUTO_START, false).toBool();
checkBoxTimerAutoStart->setChecked(timerAutoStart);
+ bool checkForUpdates = qs.value(kINTF_CHECK_FOR_UPDATES, true).toBool();
+ checkBoxIntfCheckUpdates->setChecked(checkForUpdates);
// Duplicate settings
checkBoxDuplRefuseInvalid->setChecked(Settings::Instance().getBool("duplicate.reject-invalid"));
@@ -258,6 +264,7 @@ void PrefsDialog::updateSettings()
shouldEmitUpdate = true;
qs.setValue(kINTF_TIMER_AUTO_START, checkBoxTimerAutoStart->isChecked());
}
+ qs.setValue(kINTF_CHECK_FOR_UPDATES, checkBoxIntfCheckUpdates->isChecked());
// Duplicate settings
Settings::Instance().setBool("duplicate.reject-invalid",
diff --git a/qt/prefs_dialog.h b/qt/prefs_dialog.h
index 3cfb15c..5f694b9 100644
--- a/qt/prefs_dialog.h
+++ b/qt/prefs_dialog.h
@@ -47,6 +47,7 @@ public:
static const QString kINTF_TIMER_ALERT_DURATION;
static const QString kINTF_TIMER_BEEPS;
static const QString kINTF_TIMER_AUTO_START;
+ static const QString kINTF_CHECK_FOR_UPDATES;
static const QString kARBIT_AUTO_MASTER;
static const QString kARBIT_LINK_7P1;
diff --git a/qt/ui/prefs_dialog.ui b/qt/ui/prefs_dialog.ui
index c297a69..060b6d5 100644
--- a/qt/ui/prefs_dialog.ui
+++ b/qt/ui/prefs_dialog.ui
@@ -184,6 +184,13 @@
+ -
+
+
+ _("Check periodially for new versions")
+
+
+
@@ -691,11 +698,13 @@
spinBoxTimerTotal
spinBoxTimerAlert
checkBoxTimerBeeps
+ checkBoxTimerAutoStart
lineEditDefSite
lineEditIntfDicPath
pushButtonIntfDicBrowse
checkBoxIntfShowPoints
checkBoxIntfAlignHistory
+ checkBoxIntfCheckUpdates
checkBoxDuplRefuseInvalid
spinBoxDuplSoloPlayers
spinBoxDuplSoloValue
@@ -704,10 +713,18 @@
checkBoxArbitAutoMaster
checkBoxArbitFillRack
checkBoxArbitLink7P1
+ checkBoxArbitSoloAuto
+ spinBoxArbitSoloPlayers
+ spinBoxArbitSoloValue
spinBoxArbitPenaltyValue
+ spinBoxArbitWarnLimit
+ spinBoxArbitSearchLimit
+ checkBoxToppingElapsedPenalty
+ spinBoxToppingExpPenalty
checkBoxConfoStartGame
checkBoxConfoLoadGame
checkBoxConfoLoadDic
+ checkBoxConfoReplayTurn
checkBoxConfoQuitGame
checkBoxConfoArbitReplaceMaster
checkBoxConfoArbitLowMaster
diff --git a/qt/update_checker.cpp b/qt/update_checker.cpp
new file mode 100644
index 0000000..8b7565c
--- /dev/null
+++ b/qt/update_checker.cpp
@@ -0,0 +1,209 @@
+/*****************************************************************************
+ * Eliot
+ * Copyright (C) 2013 Olivier Teulière
+ * Authors: Olivier Teulière
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *****************************************************************************/
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+#include
+
+#include "config.h"
+
+#include "update_checker.h"
+#include "prefs_dialog.h"
+#include "qtcommon.h"
+
+
+using namespace std;
+
+INIT_LOGGER(qt, UpdateChecker);
+
+#define SETTING_KEY "Interface/NextUpdateCheck"
+#define URL "http://www.nongnu.org/eliot/latest-version"
+
+
+UpdateChecker::UpdateChecker(QObject *parent)
+ : QObject(parent)
+{
+}
+
+
+void UpdateChecker::checkForUpdate()
+{
+ QSettings qs;
+
+ // Do nothing if the updates are deactivated in the preferences
+ if (!qs.value(PrefsDialog::kINTF_CHECK_FOR_UPDATES, true).toBool())
+ return;
+
+ QString dateStr = qs.value(SETTING_KEY, "").toString();
+ QDate nextCheckDate = QDate::fromString(dateStr, Qt::ISODate);
+
+ // If the next date for the check is in the future, nothing to do
+ if (nextCheckDate.isValid() && nextCheckDate > QDate::currentDate())
+ return;
+
+ emit notifyInfo(_q("Checking for updates..."));
+
+ QNetworkAccessManager *networkManager = new QNetworkAccessManager(this);
+ QObject::connect(networkManager, SIGNAL(finished(QNetworkReply*)),
+ this, SLOT(updateCheckFinished(QNetworkReply*)));
+ networkManager->get(QNetworkRequest(QUrl(URL)));
+}
+
+
+void UpdateChecker::updateCheckFinished(QNetworkReply *iReply)
+{
+ // Check at most once a week
+ const int nbDaysToWait = 7;
+
+ // Save the new check date
+ QDate nextCheckDate = QDate::currentDate().addDays(nbDaysToWait);
+ QSettings qs;
+ qs.setValue(SETTING_KEY, nextCheckDate.toString(Qt::ISODate));
+
+ if (iReply->error() == QNetworkReply::NoError)
+ {
+ LOG_INFO("Update file retrieved successfully");
+ QByteArray data = iReply->readAll().trimmed();
+ bool newer = isNewer(data);
+
+ if (newer)
+ {
+ LOG_INFO("New version available: " << lfq(data));
+ showNewVersion(data);
+ }
+ else
+ emit notifyInfo(_q("Update check completed, no new version available"));
+ }
+ else
+ {
+ LOG_ERROR("Could not retrieve update file: " << lfq(iReply->errorString()));
+ emit notifyInfo(_q("Update check failed. Please check your internet connection"));
+ }
+
+ // Delete the reply
+ iReply->deleteLater();
+}
+
+
+UpdateChecker::VersionNumber UpdateChecker::parseVersionNumber(QString iVersion) const
+{
+ VersionNumber vn;
+
+ // A version number has the following form: 1.12a-git (where 'a' is an
+ // optional letter, and -git is optional as well)
+ // Regexp to the rescue!
+ QRegExp re("^(\\d+)\\.(\\d+)([a-z])?(-git.*)?$");
+ re.setPatternSyntax(QRegExp::RegExp2);
+ if (re.indexIn(iVersion) == -1)
+ {
+ LOG_ERROR("Error parsing version number: " << lfq(iVersion));
+ vn.major = -1;
+ return vn;
+ }
+ vn.major = re.cap(1).toInt();
+ vn.minor = re.cap(2).toInt();
+ vn.letter = 0;
+ if (re.pos(3) != -1)
+ vn.letter = re.cap(3)[0].toAscii();
+ vn.suffix = re.cap(4);
+
+ LOG_DEBUG("Parsed version number: " << vn.major << "." << vn.minor <<
+ (vn.letter ? string(1, vn.letter) : "") << lfq(vn.suffix) <<
+ " (from '" << lfq(iVersion) << "')");
+ return vn;
+}
+
+
+bool UpdateChecker::isNewer(QString iNewVersion) const
+{
+ // Before doing a lot of work, check the most common case first
+ if (iNewVersion == PACKAGE_VERSION)
+ return false;
+
+ // Parse the 2 version strings
+ const VersionNumber &currVN = parseVersionNumber(PACKAGE_VERSION);
+ const VersionNumber &newVN = parseVersionNumber(iNewVersion);
+ if (currVN.major == -1 || newVN.major == -1)
+ return false;
+
+ // We have the following order:
+ // - 1.* < 2.*
+ // - 1.12* < 1.12a* < 1.12b* < ... < 1.13*
+ // - 1.12 < 1.13-git < 1.13
+
+ // Compare the major numbers
+ if (newVN.major > currVN.major)
+ return true;
+ if (newVN.major < currVN.major)
+ return false;
+
+ // Compare the minor numbers
+ if (newVN.minor > currVN.minor)
+ return true;
+ if (newVN.minor < currVN.minor)
+ return false;
+
+ // Compare the letters
+ if (newVN.letter != 0 || currVN.letter != 0)
+ {
+ if (newVN.letter == 0 || currVN.letter == 0)
+ return currVN.letter == 0;
+ if (newVN.letter > currVN.letter)
+ return true;
+ if (newVN.letter < currVN.letter)
+ return false;
+ }
+
+ // Compare the suffixes
+ if (newVN.suffix != "" || currVN.suffix != "")
+ {
+ if (newVN.suffix == "" || currVN.suffix == "")
+ return newVN.suffix == "";
+ }
+
+ // If we reach this point, the 2 version numbers have (different) suffixes.
+ // This is not expected, so we are conservative...
+ LOG_WARN("Cannot compare version numbers: " <<
+ PACKAGE_VERSION << " and " << lfq(iNewVersion));
+ return false;
+}
+
+
+void UpdateChecker::showNewVersion(QString iVersion) const
+{
+ const QString url = _q("http://www.nongnu.org/eliot/en/");
+ // TRANSLATORS: Here %1 represents a version number.
+ QString msg = _q("Eliot %1 is available.").arg(iVersion);
+ msg += "
" + _q("You can download it from %1.")
+ .arg(QString("%2").arg(url).arg(url));
+ msg += "
" + _q("This message will be displayed at most once a week.");
+ QMessageBox infoBox(QMessageBox::Information, _q("New version available"),
+ msg, QMessageBox::Ok);
+ infoBox.setTextFormat(Qt::RichText);
+ infoBox.exec();
+}
+
+
diff --git a/qt/update_checker.h b/qt/update_checker.h
new file mode 100644
index 0000000..fd7220e
--- /dev/null
+++ b/qt/update_checker.h
@@ -0,0 +1,71 @@
+/*****************************************************************************
+ * Eliot
+ * Copyright (C) 2013 Olivier Teulière
+ * Authors: Olivier Teulière
+ *
+ * This program 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 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program 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 this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *****************************************************************************/
+
+#ifndef UPDATE_CHECKER_H_
+#define UPDATE_CHECKER_H_
+
+#include
+#include
+
+#include "logging.h"
+
+class QNetworkReply;
+
+
+/**
+ * Utility class to check if a new version of Eliot is available
+ */
+class UpdateChecker: public QObject
+{
+ Q_OBJECT;
+ DEFINE_LOGGER();
+
+ /**
+ * Structured representation of a version number
+ */
+ struct VersionNumber
+ {
+ int major;
+ int minor;
+ char letter; // ASCII value, or 0 if no letter is present
+ QString suffix; // May be an empty string
+ };
+
+public:
+ UpdateChecker(QObject *parent);
+
+ void checkForUpdate();
+
+signals:
+ void notifyInfo(QString msg);
+
+private slots:
+ void updateCheckFinished(QNetworkReply*);
+
+private:
+
+ VersionNumber parseVersionNumber(QString iVersion) const;
+ bool isNewer(QString iNewVersion) const;
+ void showNewVersion(QString iVersion) const;
+
+};
+
+#endif
+