From 990f4c5212ac6a5fbe8122b1b9e0263fdca5867c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Olivier=20Teuli=C3=A8re?= Date: Fri, 5 Sep 2008 21:31:30 +0000 Subject: [PATCH] Qt interface: - Initialize the random numbers generator, and print the seed value - Handle properly Qt builds without STL support - Save the position of the main window - Got rid of the useless toolbar - Better size of the preferences dialog - Hopefully fixed translation issues on Windows - The Settings class throws an exception when asked for a non-existing setting --- dic/compdic.cpp | 17 +++++++-- dic/listdic.cpp | 15 +++++++- dic/regexpmain.cpp | 15 +++++++- extras/contrib/Makefile | 16 ++++----- game/Makefile.am | 49 +++++++++++++------------- game/game_exception.cpp | 36 +++++++++++++++++++ game/game_exception.h | 45 ++++++++++++++++++++++++ game/settings.cpp | 7 ++-- qt/main.cpp | 24 +++++++++---- qt/main_window.cpp | 42 +++++++++++++++++++++- qt/main_window.h | 7 ++++ qt/prefs_dialog.cpp | 78 +++++++++++++++++++++++++---------------- qt/qtcommon.h | 13 +++---- qt/ui/main_window.ui | 23 ++---------- qt/ui/prefs_dialog.ui | 8 ++++- utils/ncurses.cpp | 15 +++++++- 16 files changed, 304 insertions(+), 106 deletions(-) create mode 100644 game/game_exception.cpp create mode 100644 game/game_exception.h diff --git a/dic/compdic.cpp b/dic/compdic.cpp index 0783edc..c488b3b 100644 --- a/dic/compdic.cpp +++ b/dic/compdic.cpp @@ -45,7 +45,7 @@ #include #include -// For ntohl & Co. +// For htonl & Co. #ifdef WIN32 # include #else @@ -63,6 +63,9 @@ #else # define _(String) String #endif +#ifdef WIN32 +# include +#endif #include "hashtable.h" #include "encoding.h" @@ -438,7 +441,17 @@ int main(int argc, char* argv[]) #if ENABLE_NLS // Set the message domain - bindtextdomain(PACKAGE, LOCALEDIR); +#ifdef WIN32 + // Get the absolute path, as returned by GetFullPathName() + char localeDir[MAX_PATH]; + GetFullPathName(argv[0], MAX_PATH, localeDir, NULL); + char *pos = strrchr(localeDir, L'\\'); + if (pos) + *pos = '\0'; +#else + static const char *localeDir = LOCALEDIR; +#endif + bindtextdomain(PACKAGE, localeDir); textdomain(PACKAGE); #endif diff --git a/dic/listdic.cpp b/dic/listdic.cpp index 826d02e..5d37e3b 100644 --- a/dic/listdic.cpp +++ b/dic/listdic.cpp @@ -41,6 +41,9 @@ #else # define _(String) String #endif +#ifdef WIN32 +# include +#endif #include "header.h" #include "encoding.h" @@ -125,7 +128,17 @@ int main(int argc, char *argv[]) #if ENABLE_NLS // Set the message domain - bindtextdomain(PACKAGE, LOCALEDIR); +#ifdef WIN32 + // Get the absolute path, as returned by GetFullPathName() + char localeDir[MAX_PATH]; + GetFullPathName(argv[0], MAX_PATH, localeDir, NULL); + char *pos = strrchr(localeDir, L'\\'); + if (pos) + *pos = '\0'; +#else + static const char *localeDir = LOCALEDIR; +#endif + bindtextdomain(PACKAGE, localeDir); textdomain(PACKAGE); #endif diff --git a/dic/regexpmain.cpp b/dic/regexpmain.cpp index 514f21f..210136d 100644 --- a/dic/regexpmain.cpp +++ b/dic/regexpmain.cpp @@ -38,6 +38,9 @@ #else # define _(String) String #endif +#ifdef WIN32 +# include +#endif #include "dic.h" #include "dic_exception.h" @@ -61,7 +64,17 @@ int main(int argc, char* argv[]) #if ENABLE_NLS // Set the message domain - bindtextdomain(PACKAGE, LOCALEDIR); +#ifdef WIN32 + // Get the absolute path, as returned by GetFullPathName() + char localeDir[MAX_PATH]; + GetFullPathName(argv[0], MAX_PATH, localeDir, NULL); + char *pos = strrchr(localeDir, L'\\'); + if (pos) + *pos = '\0'; +#else + static const char *localeDir = LOCALEDIR; +#endif + bindtextdomain(PACKAGE, localeDir); textdomain(PACKAGE); #endif diff --git a/extras/contrib/Makefile b/extras/contrib/Makefile index 27be5a7..d65ab90 100644 --- a/extras/contrib/Makefile +++ b/extras/contrib/Makefile @@ -1,7 +1,7 @@ ICONV_VERSION = 1.12 WX_VERSION = 2.6.4 BOOST_VERSION = 1_34_1 -QT_VERSION = 4.3.3 +QT_VERSION = 4.4.1 PREFIX = $(shell pwd)/inst WGET = wget -c @@ -72,14 +72,14 @@ $(BOOST_DIR): ### Qt ### # FIXME: No automated way at the moment :-( -QT_ARCHIVE = qt-win-opensource-$(QT_VERSION)-mingw.exe +QT_ARCHIVE = qt4-$(QT_VERSION)-win32-bin.tar.bz2 +QT_DIR = qt4-$(QT_VERSION)-win32-bin -$(QT_ARCHIVE): - $(WGET) ftp://ftp.trolltech.com/qt/source/qt-win-opensource-4.3.3-mingw.exe +$(QT_DIR): + $(WGET) http://download.videolan.org/pub/videolan/testing/contrib/$(QT_ARCHIVE) + tar xjf $(QT_ARCHIVE) -.qt: $(QT_ARCHIVE) - @echo "=============== Important note ===============" - @echo "You need to install $(QT_ARCHIVE) yourself using wine!" - @echo "==============================================" +.qt: $(QT_DIR) + (cd $<; mkdir -p $(PREFIX)/bin; mkdir -p $(PREFIX)/include; mkdir -p $(PREFIX)/lib/pkgconfig; rm -f $(PREFIX)/lib/pkgconfig/Qt*; sed 's,@@PREFIX@@,$(PREFIX),' lib/pkgconfig/QtCore.pc.in > $(PREFIX)/lib/pkgconfig/QtCore.pc; sed 's,@@PREFIX@@,$(PREFIX),' lib/pkgconfig/QtGui.pc.in > $(PREFIX)/lib/pkgconfig/QtGui.pc; cp -r include/* $(PREFIX)/include; cp lib/*a $(PREFIX)/lib; mkdir -p $(PREFIX)/share/qt4/translations; cp -r share/translations/* $(PREFIX)/share/qt4/translations) touch $@ diff --git a/game/Makefile.am b/game/Makefile.am index 53d5416..8a74f60 100644 --- a/game/Makefile.am +++ b/game/Makefile.am @@ -22,28 +22,29 @@ noinst_LIBRARIES = libgame.a AM_CPPFLAGS = -I$(top_srcdir)/dic -I../intl -I$(top_srcdir)/intl libgame_a_SOURCES= \ - ai_percent.cpp ai_percent.h \ - ai_player.h \ - bag.cpp bag.h \ - coord.cpp coord.h \ - cross.cpp cross.h \ - board.cpp board.h \ - board_cross.cpp \ - board_search.cpp \ - duplicate.cpp duplicate.h \ - freegame.cpp freegame.h \ - game.cpp game.h \ - game_factory.cpp game_factory.h \ - game_io.cpp \ - move.cpp move.h \ - player.cpp player.h \ - pldrack.cpp pldrack.h \ - rack.cpp rack.h \ - results.cpp results.h \ - round.cpp round.h \ - settings.cpp settings.h \ - training.cpp training.h \ - turn.cpp turn.h \ - history.cpp history.h \ - debug.h + ai_percent.cpp ai_percent.h \ + ai_player.h \ + bag.cpp bag.h \ + coord.cpp coord.h \ + cross.cpp cross.h \ + board.cpp board.h \ + board_cross.cpp \ + board_search.cpp \ + duplicate.cpp duplicate.h \ + freegame.cpp freegame.h \ + game.cpp game.h \ + game_exception.cpp game_exception.h \ + game_factory.cpp game_factory.h \ + game_io.cpp \ + move.cpp move.h \ + player.cpp player.h \ + pldrack.cpp pldrack.h \ + rack.cpp rack.h \ + results.cpp results.h \ + round.cpp round.h \ + settings.cpp settings.h \ + training.cpp training.h \ + turn.cpp turn.h \ + history.cpp history.h \ + debug.h diff --git a/game/game_exception.cpp b/game/game_exception.cpp new file mode 100644 index 0000000..bdf6628 --- /dev/null +++ b/game/game_exception.cpp @@ -0,0 +1,36 @@ +/***************************************************************************** + * Eliot + * Copyright (C) 2007 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 "game_exception.h" + +using namespace std; + + +GameException::GameException(const string &iMessage) + : m_message(iMessage) +{ +} + + +const char *GameException::what() const throw() +{ + return m_message.c_str(); +} + diff --git a/game/game_exception.h b/game/game_exception.h new file mode 100644 index 0000000..75ca107 --- /dev/null +++ b/game/game_exception.h @@ -0,0 +1,45 @@ +/***************************************************************************** + * Eliot + * Copyright (C) 2007 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 _GAME_EXCEPTION_H_ +#define _GAME_EXCEPTION_H_ + +#include +#include + + +/** + * Exception class for the Game library. + * It simply inherits from the standard exception and overrides + * its what() method. + */ +class GameException: public std::exception +{ + public: + GameException(const std::string &iMessage); + ~GameException() throw() {} + virtual const char *what() const throw(); + + private: + std::string m_message; +}; + + +#endif diff --git a/game/settings.cpp b/game/settings.cpp index d06bfce..1487d80 100644 --- a/game/settings.cpp +++ b/game/settings.cpp @@ -21,6 +21,7 @@ #include #include "settings.h" +#include "game_exception.h" Settings *Settings::m_instance = NULL; @@ -113,8 +114,7 @@ void Settings::OptionsHandler::setOption(const string &iName, const T &iValue typename map::iterator it = m_options.find(iName); if (it == m_options.end()) { - // FIXME: throw an exception object instead - throw 1; + throw GameException("No such option: " + iName); } it->second = iValue; } @@ -126,8 +126,7 @@ const T& Settings::OptionsHandler::getOption(const string &iName) const typename map::const_iterator it = m_options.find(iName); if (it == m_options.end()) { - // FIXME: throw an exception object instead - throw 1; + throw GameException("No such option: " + iName); } return it->second; } diff --git a/qt/main.cpp b/qt/main.cpp index 4528ea9..1a55223 100644 --- a/qt/main.cpp +++ b/qt/main.cpp @@ -24,6 +24,9 @@ #include #include #include "main_window.h" +#ifdef WIN32 +# include +#endif int main(int argc, char **argv) { @@ -37,24 +40,33 @@ int main(int argc, char **argv) #ifdef ENABLE_NLS // Set the message domain - bindtextdomain(PACKAGE, LOCALEDIR); +#ifdef WIN32 + // Get the absolute path, as returned by GetFullPathName() + char localeDir[MAX_PATH]; + GetFullPathName(argv[0], MAX_PATH, localeDir, NULL); + char *pos = strrchr(localeDir, L'\\'); + if (pos) + *pos = '\0'; +#else + static const char *localeDir = LOCALEDIR; +#endif + bindtextdomain(PACKAGE, localeDir); textdomain(PACKAGE); // Translations for Qt's own strings QTranslator translator; // Set the path for the translation file -#if !defined( WIN32 ) - QString path = QString(QT4LOCALEDIR); +#ifdef WIN32 + QString path = localeDir; #else - QString path = QString(LOCALEDIR) + "/qt4/"; + QString path = QString(QT4LOCALEDIR); #endif QString lang = QLocale::system().name(); - translator.load(path + "qt_" + lang); + translator.load(path + "/qt_" + lang); app.installTranslator(&translator); #endif MainWindow qmain; - qmain.move(200, 200); qmain.show(); return app.exec(); } diff --git a/qt/main_window.cpp b/qt/main_window.cpp index f06139d..58fd1d5 100644 --- a/qt/main_window.cpp +++ b/qt/main_window.cpp @@ -60,12 +60,29 @@ #include "coord.h" +const char *MainWindow::m_windowName = "MainWindow"; + MainWindow::MainWindow(QWidget *iParent) : QMainWindow(iParent), m_dic(NULL), m_game(NULL), m_newGameDialog(NULL), m_prefsDialog(NULL), m_bagWindow(NULL), m_boardWindow(NULL), m_historyWindow(NULL), m_dicToolsWindow(NULL), m_dicNameLabel(NULL) { m_ui.setupUi(this); + readSettings(); + + // Initialize the random numbers generator + // Note: This must be done _after_ creating the QMenuBar object, + // because on Gnome QMenuBar calls gconftool2, which for some reason + // calls srand() internally... + // This could be disabled using QApplication::setDesktopSettingsAware(), + // but we would lose the desktop integration... + unsigned int val = time(NULL); + srand(val); +#ifdef DEBUG + // Make it easier to reproduce bugs + cout << "Rand seed: " << val << endl; +#endif + QObject::connect(this, SIGNAL(gameChanged(const Game*)), this, SLOT(updateForGame(const Game*))); @@ -216,7 +233,7 @@ void MainWindow::updateStatusBar(const Dictionary *iDic) void MainWindow::displayErrorMsg(QString iMsg, QString iContext) { if (iContext == "") - iContext = PACKAGE_NAME; + iContext = _q("%1 error").arg(PACKAGE_NAME); QMessageBox::warning(this, iContext, iMsg); } @@ -239,10 +256,33 @@ void MainWindow::closeEvent(QCloseEvent *event) m_historyWindow->close(); if (m_dicToolsWindow) m_dicToolsWindow->close(); + writeSettings(); event->accept(); } +void MainWindow::writeSettings() const +{ + QSettings settings(ORGANIZATION, PACKAGE_NAME); + settings.beginGroup(m_windowName); + settings.setValue("size", size()); + settings.setValue("pos", pos()); + settings.endGroup(); +} + + +void MainWindow::readSettings() +{ + QSettings settings(ORGANIZATION, PACKAGE_NAME); + settings.beginGroup(m_windowName); + QSize size = settings.value("size").toSize(); + if (size.isValid()) + resize(size); + move(settings.value("pos", QPoint(200, 200)).toPoint()); + settings.endGroup(); +} + + void MainWindow::on_action_GameNew_triggered() { if (m_dic == NULL) diff --git a/qt/main_window.h b/qt/main_window.h index d1529e6..840654f 100644 --- a/qt/main_window.h +++ b/qt/main_window.h @@ -97,6 +97,8 @@ private: /// Dialog for the preferences PrefsDialog *m_prefsDialog; + static const char * m_windowName; + /// Auxiliary windows //@{ AuxWindow *m_bagWindow; @@ -108,6 +110,11 @@ private: /// Label indicationg the name of the current dictionary QLabel *m_dicNameLabel; + /// Save window state + void writeSettings() const; + /// Restore window state + void readSettings(); + /// Destroy the current game (if any) and the associated widgets void destroyCurrentGame(); diff --git a/qt/prefs_dialog.cpp b/qt/prefs_dialog.cpp index 9c4af63..5e5155e 100644 --- a/qt/prefs_dialog.cpp +++ b/qt/prefs_dialog.cpp @@ -22,8 +22,10 @@ #include #include +#include #include "prefs_dialog.h" +#include "game_exception.h" #include "settings.h" @@ -36,15 +38,23 @@ PrefsDialog::PrefsDialog(QWidget *iParent) { setupUi(this); - // Interface settings - QSettings qs(ORGANIZATION, PACKAGE_NAME); - checkBoxIntfAlignHistory->setChecked(qs.value(kINTF_ALIGN_HISTORY).toBool()); - lineEditIntfDicPath->setText(qs.value(kINTF_DIC_PATH, "").toString()); + try + { + // Interface settings + QSettings qs(ORGANIZATION, PACKAGE_NAME); + checkBoxIntfAlignHistory->setChecked(qs.value(kINTF_ALIGN_HISTORY).toBool()); + lineEditIntfDicPath->setText(qs.value(kINTF_DIC_PATH, "").toString()); - // Duplicate settings - checkBoxDuplRefuseInvalid->setChecked(Settings::Instance().getBool("duplicate-reject-invalid")); - spinBoxDuplSoloPlayers->setValue(Settings::Instance().getInt("duplicate-solo-players")); - spinBoxDuplSoloValue->setValue(Settings::Instance().getInt("duplicate-solo-value")); + // Duplicate settings + checkBoxDuplRefuseInvalid->setChecked(Settings::Instance().getBool("duplicate-reject-invalid")); + spinBoxDuplSoloPlayers->setValue(Settings::Instance().getInt("duplicate-solo-players")); + spinBoxDuplSoloValue->setValue(Settings::Instance().getInt("duplicate-solo-value")); + } + catch (GameException &e) + { + QMessageBox::warning(this, _q("%1 error").arg(PACKAGE_NAME), + _q("Cannot load preferences: %1").arg(e.what())); + } // Freegame settings checkBoxFreeRefuseInvalid->setChecked(Settings::Instance().getBool("freegame-reject-invalid")); @@ -69,30 +79,38 @@ void PrefsDialog::updateSettings() { bool shouldEmitUpdate = false; - // Interface settings - QSettings qs(ORGANIZATION, PACKAGE_NAME); - if (qs.value(kINTF_ALIGN_HISTORY).toBool() != checkBoxIntfAlignHistory->isChecked()) + try { - // We need to redraw the history widget - shouldEmitUpdate = true; - qs.setValue(kINTF_ALIGN_HISTORY, checkBoxIntfAlignHistory->isChecked()); + // Interface settings + QSettings qs(ORGANIZATION, PACKAGE_NAME); + if (qs.value(kINTF_ALIGN_HISTORY).toBool() != checkBoxIntfAlignHistory->isChecked()) + { + // We need to redraw the history widget + shouldEmitUpdate = true; + qs.setValue(kINTF_ALIGN_HISTORY, checkBoxIntfAlignHistory->isChecked()); + } + qs.setValue(kINTF_DIC_PATH, lineEditIntfDicPath->text()); + + // Duplicate settings + Settings::Instance().setBool("duplicate-reject-invalid", + checkBoxDuplRefuseInvalid->isChecked()); + Settings::Instance().setInt("duplicate-solo-players", + spinBoxDuplSoloPlayers->value()); + Settings::Instance().setInt("duplicate-solo-value", + spinBoxDuplSoloValue->value()); + + // Freegame settings + Settings::Instance().setBool("freegame-reject-invalid", + checkBoxFreeRefuseInvalid->isChecked()); + + // Training settings + + } + catch (GameException &e) + { + QMessageBox::warning(this, _q("%1 error").arg(PACKAGE_NAME), + _q("Cannot save preferences: %1").arg(e.what())); } - qs.setValue(kINTF_DIC_PATH, lineEditIntfDicPath->text()); - - // Duplicate settings - Settings::Instance().setBool("duplicate-reject-invalid", - checkBoxDuplRefuseInvalid->isChecked()); - Settings::Instance().setInt("duplicate-solo-players", - spinBoxDuplSoloPlayers->value()); - Settings::Instance().setInt("duplicate-solo-value", - spinBoxDuplSoloValue->value()); - - // Freegame settings - Settings::Instance().setBool("freegame-reject-invalid", - checkBoxFreeRefuseInvalid->isChecked()); - - // Training settings - if (shouldEmitUpdate) emit gameUpdated(); diff --git a/qt/qtcommon.h b/qt/qtcommon.h index c4f6932..6a98cb9 100644 --- a/qt/qtcommon.h +++ b/qt/qtcommon.h @@ -22,6 +22,7 @@ #define QT_COMMON_H_ #include "config.h" +#include #if ENABLE_NLS # include @@ -40,13 +41,13 @@ #define qfl(s) QString::fromLocal8Bit(s) #define qtl(s) (s).toLocal8Bit().data() // Convert to/from std::wstring -#ifdef WIN32 -#include "encoding.h" -#define qfw(s) qfl(convertToMb(s).c_str()) -#define qtw(s) convertToWc(qtl(s)) +#ifdef QT_NO_STL +# include "encoding.h" +# define qfw(s) qfl(convertToMb(s).c_str()) +# define qtw(s) convertToWc(qtl(s)) #else -#define qfw(s) QString::fromStdWString(s) -#define qtw(s) (s).toStdWString().data() +# define qfw(s) QString::fromStdWString(s) +# define qtw(s) (s).toStdWString().data() #endif // Translation macro to use gettext #define _q(s) qfu(_(s)) diff --git a/qt/ui/main_window.ui b/qt/ui/main_window.ui index a1333d0..209a1f8 100644 --- a/qt/ui/main_window.ui +++ b/qt/ui/main_window.ui @@ -20,9 +20,9 @@ 0 - 37 + 26 747 - 557 + 568 @@ -126,25 +126,6 @@ - - - - 0 - 26 - 747 - 11 - - - - toolBar - - - TopToolBarArea - - - false - - _("Choose dictionary...") diff --git a/qt/ui/prefs_dialog.ui b/qt/ui/prefs_dialog.ui index 4903c4a..2382ba0 100644 --- a/qt/ui/prefs_dialog.ui +++ b/qt/ui/prefs_dialog.ui @@ -5,7 +5,7 @@ 0 0 - 343 + 401 432 @@ -30,6 +30,12 @@ + + + 130 + 0 + + _("Enter the dictionary path (mandatory to start a game)") diff --git a/utils/ncurses.cpp b/utils/ncurses.cpp index 16f1568..c130149 100644 --- a/utils/ncurses.cpp +++ b/utils/ncurses.cpp @@ -24,6 +24,9 @@ #else # define _(String) String #endif +#ifdef WIN32 +# include +#endif #include #include // For strlen @@ -1118,7 +1121,17 @@ int main(int argc, char ** argv) #if ENABLE_NLS // Set the message domain - bindtextdomain(PACKAGE, LOCALEDIR); +#ifdef WIN32 + // Get the absolute path, as returned by GetFullPathName() + char localeDir[MAX_PATH]; + GetFullPathName(argv[0], MAX_PATH, localeDir, NULL); + char *pos = strrchr(localeDir, L'\\'); + if (pos) + *pos = '\0'; +#else + static const char *localeDir = LOCALEDIR; +#endif + bindtextdomain(PACKAGE, localeDir); textdomain(PACKAGE); #endif