summaryrefslogtreecommitdiffstats
path: root/libkdegames/highscore
diff options
context:
space:
mode:
Diffstat (limited to 'libkdegames/highscore')
-rw-r--r--libkdegames/highscore/INSTALL12
-rw-r--r--libkdegames/highscore/Makefile.am19
-rw-r--r--libkdegames/highscore/kconfigrawbackend.cpp62
-rw-r--r--libkdegames/highscore/kconfigrawbackend.h57
-rw-r--r--libkdegames/highscore/kexthighscore.cpp289
-rw-r--r--libkdegames/highscore/kexthighscore.h367
-rw-r--r--libkdegames/highscore/kexthighscore_gui.cpp552
-rw-r--r--libkdegames/highscore/kexthighscore_gui.h207
-rw-r--r--libkdegames/highscore/kexthighscore_internal.cpp868
-rw-r--r--libkdegames/highscore/kexthighscore_internal.h277
-rw-r--r--libkdegames/highscore/kexthighscore_item.cpp312
-rw-r--r--libkdegames/highscore/kexthighscore_item.h317
-rw-r--r--libkdegames/highscore/kexthighscore_tab.cpp281
-rw-r--r--libkdegames/highscore/kexthighscore_tab.h117
-rw-r--r--libkdegames/highscore/kfilelock.cpp88
-rw-r--r--libkdegames/highscore/kfilelock.h53
-rw-r--r--libkdegames/highscore/khighscore.cpp262
-rw-r--r--libkdegames/highscore/khighscore.h311
-rw-r--r--libkdegames/highscore/kscoredialog.cpp411
-rw-r--r--libkdegames/highscore/kscoredialog.h125
20 files changed, 4987 insertions, 0 deletions
diff --git a/libkdegames/highscore/INSTALL b/libkdegames/highscore/INSTALL
new file mode 100644
index 00000000..a16fa57d
--- /dev/null
+++ b/libkdegames/highscore/INSTALL
@@ -0,0 +1,12 @@
+Installation notes for the highscore files ; this is only relevant if you
+configured libkdegames with option --enable-highscore-dir=DIR (usually DIR is
+/var/games) for using system-wide highscore files.
+
+For each game using the highscore system :
+
+- the game executable "mygame" should be installed sgid "games"
+
+- an empty file "mygame.scores" should be created in the directory pointed by
+the configuration option. It should be owned by group "games" with read and
+write permissions and should -not- be world readable (since it can contains
+possibly sensitive information associating username with game usage).
diff --git a/libkdegames/highscore/Makefile.am b/libkdegames/highscore/Makefile.am
new file mode 100644
index 00000000..6fa18cc0
--- /dev/null
+++ b/libkdegames/highscore/Makefile.am
@@ -0,0 +1,19 @@
+noinst_LTLIBRARIES = libkhighscore.la
+
+INCLUDES = $(all_includes)
+
+libkhighscore_la_SOURCES = kconfigrawbackend.cpp \
+ kfilelock.cpp khighscore.cpp kscoredialog.cpp \
+ kexthighscore_item.cpp kexthighscore_internal.cpp \
+ kexthighscore_tab.cpp kexthighscore_gui.cpp \
+ kexthighscore.cpp
+
+include_HEADERS = khighscore.h kscoredialog.h \
+ kexthighscore_item.h kexthighscore.h
+
+noinst_HEADERS = kconfigrawbackend.h \
+ kfilelock.h kexthighscore_internal.h kexthighscore_tab.h \
+ kexthighscore_gui.h
+
+METASOURCES = kconfigrawbackend.moc khighscore.moc kscoredialog.moc \
+ kexthighscore_tab.moc kexthighscore_gui.moc
diff --git a/libkdegames/highscore/kconfigrawbackend.cpp b/libkdegames/highscore/kconfigrawbackend.cpp
new file mode 100644
index 00000000..a379ba23
--- /dev/null
+++ b/libkdegames/highscore/kconfigrawbackend.cpp
@@ -0,0 +1,62 @@
+/*
+ This file is part of the KDE games library
+ Copyright (C) 2003 Nicolas Hadacek <[email protected]>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kconfigrawbackend.h"
+#include "kconfigrawbackend.moc"
+
+#include <unistd.h>
+#include <qfile.h>
+
+
+KConfigRawBackEnd::KConfigRawBackEnd(KConfigBase *_config, int fd)
+ : KConfigINIBackEnd(_config, QString::null, "config", false),
+ _fd(fd), _stream(0)
+{
+ _file.open(IO_ReadOnly, _fd);
+}
+
+KConfigRawBackEnd::~KConfigRawBackEnd()
+{
+ if (_stream) fclose(_stream);
+}
+
+bool KConfigRawBackEnd::parseConfigFiles()
+{
+ _file.reset();
+ parseSingleConfigFile(_file);
+ return true;
+}
+
+void KConfigRawBackEnd::sync(bool bMerge)
+{
+ // write-sync is only necessary if there are dirty entries
+ if ( !pConfig->isDirty() || pConfig->isReadOnly() ) return;
+
+ _file.reset();
+ KEntryMap aTempMap;
+ getEntryMap(aTempMap, false, bMerge ? &_file : 0);
+
+ if ( _stream==0 ) {
+ _stream = fdopen(_fd, "w");
+ if ( _stream==0 ) return;
+ }
+ ftruncate(_fd, 0);
+ writeEntries(_stream, aTempMap);
+ fflush(_stream);
+}
diff --git a/libkdegames/highscore/kconfigrawbackend.h b/libkdegames/highscore/kconfigrawbackend.h
new file mode 100644
index 00000000..4b780320
--- /dev/null
+++ b/libkdegames/highscore/kconfigrawbackend.h
@@ -0,0 +1,57 @@
+/*
+ This file is part of the KDE games library
+ Copyright (C) 2003 Nicolas Hadacek <[email protected]>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef _KCONFIGRAWBACKEND_H
+#define _KCONFIGRAWBACKEND_H
+
+#include <qfile.h>
+
+#include <kconfigbackend.h>
+#include <ksimpleconfig.h>
+
+
+class KConfigRawBackEnd : public KConfigINIBackEnd
+{
+public:
+ KConfigRawBackEnd(KConfigBase *_config, int fd);
+ ~KConfigRawBackEnd();
+
+ bool parseConfigFiles();
+
+ void sync(bool bMerge = true);
+
+private:
+ int _fd;
+ FILE *_stream;
+ QFile _file;
+
+ class KConfigRawBackEndPrivate;
+ KConfigRawBackEndPrivate *d;
+};
+
+class KRawConfig : public KSimpleConfig
+{
+ Q_OBJECT
+public:
+ KRawConfig(int fd, bool readOnly)
+ : KSimpleConfig(new KConfigRawBackEnd(this, fd), readOnly) {}
+};
+
+
+#endif
diff --git a/libkdegames/highscore/kexthighscore.cpp b/libkdegames/highscore/kexthighscore.cpp
new file mode 100644
index 00000000..0ad9b3af
--- /dev/null
+++ b/libkdegames/highscore/kexthighscore.cpp
@@ -0,0 +1,289 @@
+/*
+ This file is part of the KDE games library
+ Copyright (C) 2001-2004 Nicolas Hadacek ([email protected])
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kexthighscore.h"
+
+#include <qlayout.h>
+
+#include <kdebug.h>
+
+#include "kexthighscore_internal.h"
+#include "kexthighscore_gui.h"
+
+
+namespace KExtHighscore
+{
+
+//-----------------------------------------------------------------------------
+ManagerPrivate *internal = 0;
+
+uint gameType()
+{
+ internal->checkFirst();
+ return internal->gameType();
+}
+
+void setGameType(uint type)
+{
+ internal->setGameType(type);
+}
+
+bool configure(QWidget *parent)
+{
+ internal->checkFirst();
+ ConfigDialog *cd = new ConfigDialog(parent);
+ cd->exec();
+ bool saved = cd->hasBeenSaved();
+ delete cd;
+ return saved;
+}
+
+void show(QWidget *parent, int rank)
+{
+ HighscoresDialog *hd = new HighscoresDialog(rank, parent);
+ hd->exec();
+ delete hd;
+}
+
+void submitScore(const Score &score, QWidget *widget)
+{
+ int rank = internal->submitScore(score, widget,
+ internal->showMode!=Manager::NeverShow);
+
+ switch (internal->showMode) {
+ case Manager::AlwaysShow:
+ show(widget, -1);
+ break;
+ case Manager::ShowForHigherScore:
+ if ( rank!=-1) show(widget, rank);
+ break;
+ case Manager::ShowForHighestScore:
+ if ( rank==0 ) show(widget, rank);
+ break;
+ case Manager::NeverShow:
+ break;
+ }
+}
+
+void show(QWidget *widget)
+{
+ internal->checkFirst();
+ show(widget, -1);
+}
+
+Score lastScore()
+{
+ internal->checkFirst();
+ internal->hsConfig().readCurrentConfig();
+ uint nb = internal->scoreInfos().maxNbEntries();
+ return internal->readScore(nb-1);
+}
+
+Score firstScore()
+{
+ internal->checkFirst();
+ internal->hsConfig().readCurrentConfig();
+ return internal->readScore(0);
+}
+
+
+//-----------------------------------------------------------------------------
+Manager::Manager(uint nbGameTypes, uint maxNbEntries)
+{
+ Q_ASSERT(nbGameTypes);
+ Q_ASSERT(maxNbEntries);
+ if (internal)
+ kdFatal(11002) << "A highscore object already exists" << endl;
+ internal = new ManagerPrivate(nbGameTypes, *this);
+ internal->init(maxNbEntries);
+}
+
+Manager::~Manager()
+{
+ delete internal;
+ internal = 0;
+}
+
+void Manager::setTrackLostGames(bool track)
+{
+ internal->trackLostGames = track;
+}
+
+void Manager::setTrackDrawGames(bool track)
+{
+ internal->trackDrawGames = track;
+}
+
+void Manager::setShowStatistics(bool show)
+{
+ internal->showStatistics = show;
+}
+
+void Manager::showStatistics(bool show)
+{
+ internal->showStatistics = show;
+}
+
+void Manager::setShowDrawGamesStatistic(bool show)
+{
+ internal->showDrawGames = show;
+}
+
+void Manager::setWWHighscores(const KURL &url, const QString &version)
+{
+ Q_ASSERT( url.isValid() );
+ internal->serverURL = url;
+ const char *HS_WW_URL = "ww hs url";
+ ConfigGroup cg;
+ if ( cg.config()->hasKey(HS_WW_URL) )
+ internal->serverURL = cg.config()->readEntry(HS_WW_URL);
+ else cg.config()->writeEntry(HS_WW_URL, url.url());
+ internal->version = version;
+}
+
+void Manager::setScoreHistogram(const QMemArray<uint> &scores,
+ ScoreTypeBound type)
+{
+ Q_ASSERT( scores.size()>=2 );
+ for (uint i=0; i<scores.size()-1; i++)
+ Q_ASSERT( scores[i]<scores[i+1] );
+ internal->playerInfos().createHistoItems(scores, type==ScoreBound);
+}
+
+void Manager::setShowMode(ShowMode mode)
+{
+ internal->showMode = mode;
+}
+
+void Manager::setScoreType(ScoreType type)
+{
+ switch (type) {
+ case Normal:
+ return;
+ case MinuteTime: {
+ Item *item = createItem(ScoreDefault);
+ item->setPrettyFormat(Item::MinuteTime);
+ setScoreItem(0, item);
+
+ item = createItem(MeanScoreDefault);
+ item->setPrettyFormat(Item::MinuteTime);
+ setPlayerItem(MeanScore, item);
+
+ item = createItem(BestScoreDefault);
+ item->setPrettyFormat(Item::MinuteTime);
+ setPlayerItem(BestScore, item);
+ return;
+ }
+ }
+}
+
+void Manager::submitLegacyScore(const Score &score) const
+{
+ internal->submitLocal(score);
+}
+
+bool Manager::isStrictlyLess(const Score &s1, const Score &s2) const
+{
+ return s1.score()<s2.score();
+}
+
+Item *Manager::createItem(ItemType type)
+{
+ Item *item = 0;
+ switch (type) {
+ case ScoreDefault:
+ item = new Item((uint)0, i18n("Score"), Qt::AlignRight);
+ break;
+ case MeanScoreDefault:
+ item = new Item((double)0, i18n("Mean Score"), Qt::AlignRight);
+ item->setPrettyFormat(Item::OneDecimal);
+ item->setPrettySpecial(Item::DefaultNotDefined);
+ break;
+ case BestScoreDefault:
+ item = new Item((uint)0, i18n("Best Score"), Qt::AlignRight);
+ item->setPrettySpecial(Item::DefaultNotDefined);
+ break;
+ case ElapsedTime:
+ item = new Item((uint)0, i18n("Elapsed Time"), Qt::AlignRight);
+ item->setPrettyFormat(Item::MinuteTime);
+ item->setPrettySpecial(Item::ZeroNotDefined);
+ break;
+ }
+ return item;
+}
+
+void Manager::setScoreItem(uint worstScore, Item *item)
+{
+ item->setDefaultValue(worstScore);
+ internal->scoreInfos().setItem("score", item);
+ internal->playerInfos().item("mean score")
+ ->item()->setDefaultValue(double(worstScore));
+ internal->playerInfos().item("best score")
+ ->item()->setDefaultValue(worstScore);
+}
+
+void Manager::addScoreItem(const QString &name, Item *item)
+{
+ internal->scoreInfos().addItem(name, item, true);
+}
+
+void Manager::setPlayerItem(PlayerItemType type, Item *item)
+{
+ const Item *scoreItem = internal->scoreInfos().item("score")->item();
+ uint def = scoreItem->defaultValue().toUInt();
+ QString name;
+ switch (type) {
+ case MeanScore:
+ name = "mean score";
+ item->setDefaultValue(double(def));
+ break;
+ case BestScore:
+ name = "best score";
+ item->setDefaultValue(def);
+ break;
+ }
+ internal->playerInfos().setItem(name, item);
+}
+
+QString Manager::gameTypeLabel(uint gameType, LabelType type) const
+{
+ if ( gameType!=0 )
+ kdFatal(11002) << "You need to reimplement KExtHighscore::Manager for "
+ << "multiple game types" << endl;
+ switch (type) {
+ case Icon:
+ case Standard:
+ case I18N: break;
+ case WW: return "normal";
+ }
+ return QString::null;
+}
+
+void Manager::addToQueryURL(KURL &url, const QString &item,
+ const QString &content)
+{
+ Q_ASSERT( !item.isEmpty() && url.queryItem(item).isNull() );
+
+ QString query = url.query();
+ if ( !query.isEmpty() ) query += '&';
+ query += item + '=' + KURL::encode_string(content);
+ url.setQuery(query);
+}
+
+} // namescape
diff --git a/libkdegames/highscore/kexthighscore.h b/libkdegames/highscore/kexthighscore.h
new file mode 100644
index 00000000..2484f97b
--- /dev/null
+++ b/libkdegames/highscore/kexthighscore.h
@@ -0,0 +1,367 @@
+/*
+ This file is part of the KDE games library
+ Copyright (C) 2001-2004 Nicolas Hadacek ([email protected])
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXTHIGHSCORE_H
+#define KEXTHIGHSCORE_H
+
+#include "kexthighscore_item.h"
+
+#include <kurl.h>
+#include <kdemacros.h>
+
+class QTabWidget;
+
+
+namespace KExtHighscore
+{
+
+class Score;
+class Item;
+
+class ManagerPrivate;
+extern ManagerPrivate *internal;
+
+/**
+ * Get the current game type.
+ */
+KDE_EXPORT uint gameType();
+
+/**
+ * Set the current game type.
+ */
+KDE_EXPORT void setGameType(uint gameType);
+
+/**
+ * Configure the highscores.
+ * @return true if the configuration has been modified and saved
+ */
+KDE_EXPORT bool configure(QWidget *parent);
+
+/**
+ * Show the highscores lists.
+ */
+KDE_EXPORT void show(QWidget *parent);
+
+/**
+ * Submit a score. See @ref Manager for usage example.
+ *
+ * @param widget a widget used as parent for error message box.
+ */
+KDE_EXPORT void submitScore(const Score &score, QWidget *widget);
+
+/**
+ * @return the last score in the local list of highscores. The worst possible
+ * score if there are less items than the maximum number.
+ */
+KDE_EXPORT Score lastScore();
+
+/**
+ * @return the first score in the local list of highscores (the worst possible
+ * score if there is no entry).
+ */
+KDE_EXPORT Score firstScore();
+
+/**
+ * This class manages highscores and players entries (several players can
+ * share the same highscores list if the libkdegame library is built to
+ * support a common highscores file; NOTE that to correctly implement such
+ * feature we probably need a locking mechanism in @ref KHighscore).
+ *
+ * You need one instance of this class during the application lifetime ; in
+ * main() just insert
+ * \code
+ * KExtHighscore::Manager highscoresManager;
+ * \endcode
+ * with the needed arguments. Use the derived class if you need to
+ * reimplement some of the default methods.
+ *
+ * This class has three functions :
+ * <ul>
+ * <li> Update the highscores list when new entries are submitted </li>
+ * <li> Display the highscores list and the players list </li>
+ * <li> Send query to an optionnal web server to support world-wide
+ * highscores </li>
+ * </ul>
+ *
+ * The highscores and the players lists contain several items described by
+ * the @ref Item class.
+ *
+ * The highscores list contains by default :
+ * <ul>
+ * <li> the player name (automatically set from the config value)</li>
+ * <li> the score value </li>
+ * <li> the time and date of the highscore (automatically set) </li>
+ * </ul>
+ * You can replace the score item (for e.g. displaying it differently) with
+ * setScoreItem or add an item with addScoreItem.
+ *
+ * The players list contains :
+ * <ul>
+ * <li> the player name (as defined by the user in the configuration
+ * dialog) </li>
+ * <li> the number of games played </li>
+ * <li> the mean score </li>
+ * <li> the best score </li>
+ * <li> the best score time and date </li>
+ * <li> the player comment (as defined by the user in the
+ * configuration dialog) </li>
+ * </ul>
+ * You can replace the best score and the mean score items
+ * by calling setPlayerItem.
+ *
+ * To submit a new score at game end, just construct a Score, set the
+ * score data and then call submitScore().
+ * \code
+ * KExtHighscore::Score score(KExtHighscore::Won);
+ * score.setScore(myScore);
+ * KExtHighscore::submitScore(score, widget);
+ * \endcode
+ * You only need to set the score value with Score::setScore()
+ * and the value of the items that you have optionnally added
+ * with Score::setData() ; player name and date are set automatically.
+ */
+class KDE_EXPORT Manager
+{
+ public:
+ /**
+ * Constructor
+ *
+ * @param nbGameTypes the number of different game types (usually one).
+ * For example KMines has easy, normal and expert levels.
+ * @param maxNbEntries the maximum numbers of highscores entries (by game
+ * types)
+ */
+ Manager(uint nbGameTypes = 1, uint maxNbEntries = 10);
+ virtual ~Manager();
+
+ /**
+ * Set the world-wide highscores.
+ * By default there is no world-wide highscores.
+ *
+ * Note: should be called at construction time.
+ *
+ * @param url the web server url
+ * @param version the game version which is sent to the web server (it can
+ * be useful for backward compatibility on the server side).
+ */
+ void setWWHighscores(const KURL &url, const QString &version);
+
+ /**
+ * Set if the number of lost games should be track for the world-wide
+ * highscores statistics. By default, there is no tracking.
+ * False by default.
+ *
+ * Note: should be called at construction time.
+ */
+ void setTrackLostGames(bool track);
+
+ /**
+ * @since 3.3
+ * Set if the number of "draw" games should be track for the world-wide
+ * highscores statistics. By default, there is no tracking.
+ * False by default.
+ *
+ * Note: should be called at construction time.
+ */
+ void setTrackDrawGames(bool track);
+
+ /**
+ * @since 3.3
+ * Set if the statistics tab should be shown in the highscores dialog.
+ * You only want to show this tab if it makes sense to lose or to win the
+ * game (for e.g. it makes no sense for a tetris game but it does for a
+ * minesweeper game).
+ * False by default.
+ *
+ * Note: should be called at construction time.
+ */
+ void setShowStatistics(bool show);
+
+ /** @obsolete */
+ // KDE4 remove this
+ void showStatistics(bool show) KDE_DEPRECATED;
+
+ /**
+ * @since 3.3
+ * Set if draw games statistics should be shown (enable this if
+ * draws are possible in your game).
+ * False by default.
+ */
+ void setShowDrawGamesStatistic(bool show);
+
+ enum ScoreTypeBound { ScoreNotBound, ScoreBound };
+ /**
+ * Set the ranges for the score histogram.
+ *
+ * Note: should be called at construction time.
+ */
+ void setScoreHistogram(const QMemArray<uint> &scores, ScoreTypeBound type);
+
+ /**
+ * Enumerate different conditions under which to show the
+ * high score dialog.
+ */
+ enum ShowMode { AlwaysShow, ///< Always show the dialog
+ NeverShow, ///< Never show the dialog
+ ShowForHigherScore, ///< Show if score has improved
+ ShowForHighestScore ///< Only for the top spot
+ };
+ /**
+ * Set how the highscores dialog is shown at game end.
+ * By default, the mode is ShowForHigherScore.
+ *
+ * Note: should be called at construction time.
+ */
+ void setShowMode(ShowMode mode);
+
+ /**
+ * Score type (@see setScoreType).
+ * @p Normal default score (unsigned integer without upper bound)
+ * @p MinuteTime score by time bound at 3599 seconds (for e.g. kmines)
+ */
+ enum ScoreType { Normal, MinuteTime };
+ /**
+ * Set score type. Helper method to quickly set the type of score.
+ * By default the type is Normal.
+ *
+ * Note: should be called at construction time.
+ */
+ void setScoreType(ScoreType type);
+
+ /**
+ * Some predefined item types.
+ * @p ScoreDefault default item for the score in the highscores list.
+ * @p MeanScoreDefault default item for the mean score (only show one decimal and
+ * 0 is shown as "--".
+ * @p BestScoreDefault default item for the best score (0 is shown as "--").
+ * @p ElapsedTime optionnal item for elapsed time (maximum value is 3599 seconds).
+ */
+ enum ItemType { ScoreDefault, MeanScoreDefault, BestScoreDefault,
+ ElapsedTime };
+ /**
+ * Create a predefined item.
+ */
+ static Item *createItem(ItemType type);
+
+ /**
+ * Replace the default score item in the highscores list by the given one.
+ * @p worstScore is the worst possible score. By default it is 0.
+ *
+ * Note : This method should be called at construction time.
+ */
+ void setScoreItem(uint worstScore, Item *item);
+
+ /**
+ * Add an item in the highscores list (it will add a column to this list).
+ *
+ * Note : This method should be called at construction time.
+ */
+ void addScoreItem(const QString &name, Item *item);
+
+ enum PlayerItemType { MeanScore, BestScore };
+ /**
+ * Replace an item in the players list.
+ *
+ * Note : This method should be called at construction time.
+ */
+ void setPlayerItem(PlayerItemType type, Item *item);
+
+ /**
+ * @return true if the first score is strictly worse than the second one.
+ * By default return <pre>s1.score()<s2.score()</pre>. You can reimplement
+ * this method if additional items added to @ref Score can further
+ * differentiate the scores (for e.g. the time spent).
+ *
+ * Note that you do not need to use directly this method, simply write
+ * <pre>s1<s2</pre> since the operator calls this method.
+ */
+ virtual bool isStrictlyLess(const Score &s1, const Score &s2) const;
+
+ /**
+ * Possible type of label (@see gameTypeLabel).
+ * @p Standard label used in config file.
+ * @p I18N label used to display the game type.
+ * @p WW label used when contacting the world-wide highscores server.
+ * @p Icon label used to load the icon corresponding to the game type.
+ */
+ enum LabelType { Standard, I18N, WW, Icon };
+
+ /**
+ * @return the label corresponding to the game type. The default
+ * implementation works only for one game type : you need to reimplement
+ * this method if the number of game types is more than one.
+ */
+ virtual QString gameTypeLabel(uint gameType, LabelType type) const;
+
+ protected:
+ /**
+ * This method is called once for each player (ie for each user). You
+ * can reimplement it to convert old style highscores to the new mechanism
+ * (@see submitLegacyScore). By default this method does nothing.
+ *
+ * @param gameType the game type
+ */
+ virtual void convertLegacy(uint gameType) { Q_UNUSED(gameType); }
+
+ /**
+ * This method should be called from @ref convertLegacy. It is used
+ * to submit an old highscore (it will not be send over the network).
+ * For each score do something like:
+ * \code
+ * Score score(Won);
+ * score.setScore(oldScore);
+ * score.setData("name", name);
+ * submitLegacyScore(score);
+ * \endcode
+ * Note that here you can set the player "name" and the highscore "date"
+ * if they are known.
+ */
+ void submitLegacyScore(const Score &score) const;
+
+ /**
+ * This method is called before submitting a score to the world-wide
+ * highscores server. You can reimplement this method to add an entry
+ * with @ref addToQueryURL. By default this method does nothing.
+ *
+ * @param url the URL to query
+ * @param score the score to be submitted.
+ */
+ virtual void additionalQueryItems(KURL &url, const Score &score) const
+ { Q_UNUSED(url); Q_UNUSED(score); }
+
+ /**
+ * Add an entry to the url to be submitted (@see additionalQueryItems).
+ *
+ * @param url the URL to query
+ * @param item the item name
+ * @param content the item content
+ */
+ static void addToQueryURL(KURL &url, const QString &item,
+ const QString &content);
+
+ friend class ManagerPrivate;
+
+ private:
+ Manager(const Manager &);
+ Manager &operator =(const Manager &);
+};
+
+} // namespace
+
+#endif
diff --git a/libkdegames/highscore/kexthighscore_gui.cpp b/libkdegames/highscore/kexthighscore_gui.cpp
new file mode 100644
index 00000000..547a885c
--- /dev/null
+++ b/libkdegames/highscore/kexthighscore_gui.cpp
@@ -0,0 +1,552 @@
+/*
+ This file is part of the KDE games library
+ Copyright (C) 2001-2003 Nicolas Hadacek ([email protected])
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kexthighscore_gui.h"
+#include "kexthighscore_gui.moc"
+
+#include <qlayout.h>
+#include <qtextstream.h>
+#include <qheader.h>
+#include <qgrid.h>
+#include <qvgroupbox.h>
+
+#include <kapplication.h>
+#include <kmessagebox.h>
+#include <kurllabel.h>
+#include <kopenwith.h>
+#include <krun.h>
+#include <kfiledialog.h>
+#include <ktempfile.h>
+#include <kio/netaccess.h>
+#include <kiconloader.h>
+
+#include "kexthighscore_internal.h"
+#include "kexthighscore.h"
+#include "kexthighscore_tab.h"
+
+
+namespace KExtHighscore
+{
+
+//-----------------------------------------------------------------------------
+ShowItem::ShowItem(QListView *list, bool highlight)
+ : KListViewItem(list), _highlight(highlight)
+{}
+
+void ShowItem::paintCell(QPainter *p, const QColorGroup &cg,
+ int column, int width, int align)
+{
+ QColorGroup cgrp(cg);
+ if (_highlight) cgrp.setColor(QColorGroup::Text, red);
+ KListViewItem::paintCell(p, cgrp, column, width, align);
+}
+
+//-----------------------------------------------------------------------------
+ScoresList::ScoresList(QWidget *parent)
+ : KListView(parent)
+{
+ setSelectionMode(QListView::NoSelection);
+ setItemMargin(3);
+ setAllColumnsShowFocus(true);
+ setSorting(-1);
+ header()->setClickEnabled(false);
+ header()->setMovingEnabled(false);
+}
+
+void ScoresList::addHeader(const ItemArray &items)
+{
+ addLineItem(items, 0, 0);
+}
+
+QListViewItem *ScoresList::addLine(const ItemArray &items,
+ uint index, bool highlight)
+{
+ QListViewItem *item = new ShowItem(this, highlight);
+ addLineItem(items, index, item);
+ return item;
+}
+
+void ScoresList::addLineItem(const ItemArray &items,
+ uint index, QListViewItem *line)
+{
+ uint k = 0;
+ for (uint i=0; i<items.size(); i++) {
+ const ItemContainer &container = *items[i];
+ if ( !container.item()->isVisible() ) continue;
+ if (line) line->setText(k, itemText(container, index));
+ else {
+ addColumn( container.item()->label() );
+ setColumnAlignment(k, container.item()->alignment());
+ }
+ k++;
+ }
+}
+
+//-----------------------------------------------------------------------------
+HighscoresList::HighscoresList(QWidget *parent)
+ : ScoresList(parent)
+{}
+
+QString HighscoresList::itemText(const ItemContainer &item, uint row) const
+{
+ return item.pretty(row);
+}
+
+void HighscoresList::load(const ItemArray &items, int highlight)
+{
+ clear();
+ QListViewItem *line = 0;
+ for (int j=items.nbEntries()-1; j>=0; j--) {
+ QListViewItem *item = addLine(items, j, j==highlight);
+ if ( j==highlight ) line = item;
+ }
+ if (line) ensureItemVisible(line);
+}
+
+//-----------------------------------------------------------------------------
+HighscoresWidget::HighscoresWidget(QWidget *parent)
+ : QWidget(parent, "show_highscores_widget"),
+ _scoresUrl(0), _playersUrl(0), _statsTab(0), _histoTab(0)
+{
+ const ScoreInfos &s = internal->scoreInfos();
+ const PlayerInfos &p = internal->playerInfos();
+
+ QVBoxLayout *vbox = new QVBoxLayout(this, KDialogBase::spacingHint());
+
+ _tw = new QTabWidget(this);
+ connect(_tw, SIGNAL(currentChanged(QWidget *)), SLOT(tabChanged()));
+ vbox->addWidget(_tw);
+
+ // scores tab
+ _scoresList = new HighscoresList(_tw);
+ _scoresList->addHeader(s);
+ _tw->addTab(_scoresList, i18n("Best &Scores"));
+
+ // players tab
+ _playersList = new HighscoresList(_tw);
+ _playersList->addHeader(p);
+ _tw->addTab(_playersList, i18n("&Players"));
+
+ // statistics tab
+ if ( internal->showStatistics ) {
+ _statsTab = new StatisticsTab(_tw);
+ _tw->addTab(_statsTab, i18n("Statistics"));
+ }
+
+ // histogram tab
+ if ( p.histogram().size()!=0 ) {
+ _histoTab = new HistogramTab(_tw);
+ _tw->addTab(_histoTab, i18n("Histogram"));
+ }
+
+ // url labels
+ if ( internal->isWWHSAvailable() ) {
+ KURL url = internal->queryURL(ManagerPrivate::Scores);
+ _scoresUrl = new KURLLabel(url.url(),
+ i18n("View world-wide highscores"), this);
+ connect(_scoresUrl, SIGNAL(leftClickedURL(const QString &)),
+ SLOT(showURL(const QString &)));
+ vbox->addWidget(_scoresUrl);
+
+ url = internal->queryURL(ManagerPrivate::Players);
+ _playersUrl = new KURLLabel(url.url(),
+ i18n("View world-wide players"), this);
+ connect(_playersUrl, SIGNAL(leftClickedURL(const QString &)),
+ SLOT(showURL(const QString &)));
+ vbox->addWidget(_playersUrl);
+ }
+}
+
+void HighscoresWidget::changeTab(int i)
+{
+ if ( i!=_tw->currentPageIndex() )
+ _tw->setCurrentPage(i);
+}
+
+void HighscoresWidget::showURL(const QString &url) const
+{
+ (void)new KRun(KURL(url));
+}
+
+void HighscoresWidget::load(int rank)
+{
+ _scoresList->load(internal->scoreInfos(), rank);
+ _playersList->load(internal->playerInfos(), internal->playerInfos().id());
+ if (_scoresUrl)
+ _scoresUrl->setURL(internal->queryURL(ManagerPrivate::Scores).url());
+ if (_playersUrl)
+ _playersUrl->setURL(internal->queryURL(ManagerPrivate::Players).url());
+ if (_statsTab) _statsTab->load();
+ if (_histoTab) _histoTab->load();
+}
+
+//-----------------------------------------------------------------------------
+HighscoresDialog::HighscoresDialog(int rank, QWidget *parent)
+ : KDialogBase(internal->nbGameTypes()>1 ? TreeList : Plain,
+ i18n("Highscores"), Close|User1|User2, Close,
+ parent, "show_highscores", true, true,
+ KGuiItem(i18n("Configure..."), "configure"),
+ KGuiItem(i18n("Export..."))), _rank(rank), _tab(0)
+{
+ _widgets.resize(internal->nbGameTypes(), 0);
+
+ if ( internal->nbGameTypes()>1 ) {
+ for (uint i=0; i<internal->nbGameTypes(); i++) {
+ QString title = internal->manager.gameTypeLabel(i, Manager::I18N);
+ QString icon = internal->manager.gameTypeLabel(i, Manager::Icon);
+ QWidget *w = addVBoxPage(title, QString::null,
+ BarIcon(icon, KIcon::SizeLarge));
+ if ( i==internal->gameType() ) createPage(w);
+ }
+
+ connect(this, SIGNAL(aboutToShowPage(QWidget *)),
+ SLOT(createPage(QWidget *)));
+ showPage(internal->gameType());
+ } else {
+ QVBoxLayout *vbox = new QVBoxLayout(plainPage());
+ createPage(plainPage());
+ vbox->addWidget(_widgets[0]);
+ setMainWidget(_widgets[0]);
+ }
+}
+
+void HighscoresDialog::createPage(QWidget *page)
+{
+ internal->hsConfig().readCurrentConfig();
+ _current = page;
+ bool several = ( internal->nbGameTypes()>1 );
+ int i = (several ? pageIndex(page) : 0);
+ if ( _widgets[i]==0 ) {
+ _widgets[i] = new HighscoresWidget(page);
+ connect(_widgets[i], SIGNAL(tabChanged(int)), SLOT(tabChanged(int)));
+ }
+ uint type = internal->gameType();
+ if (several) internal->setGameType(i);
+ _widgets[i]->load(uint(i)==type ? _rank : -1);
+ if (several) setGameType(type);
+ _widgets[i]->changeTab(_tab);
+}
+
+void HighscoresDialog::slotUser1()
+{
+ if ( KExtHighscore::configure(this) )
+ createPage(_current);
+}
+
+void HighscoresDialog::slotUser2()
+{
+ KURL url = KFileDialog::getSaveURL(QString::null, QString::null, this);
+ if ( url.isEmpty() ) return;
+ if ( KIO::NetAccess::exists(url, true, this) ) {
+ KGuiItem gi = KStdGuiItem::save();
+ gi.setText(i18n("Overwrite"));
+ int res = KMessageBox::warningContinueCancel(this,
+ i18n("The file already exists. Overwrite?"),
+ i18n("Export"), gi);
+ if ( res==KMessageBox::Cancel ) return;
+ }
+ KTempFile tmp;
+ internal->exportHighscores(*tmp.textStream());
+ tmp.close();
+ KIO::NetAccess::upload(tmp.name(), url, this);
+ tmp.unlink();
+}
+
+//-----------------------------------------------------------------------------
+LastMultipleScoresList::LastMultipleScoresList(
+ const QValueVector<Score> &scores, QWidget *parent)
+ : ScoresList(parent), _scores(scores)
+{
+ const ScoreInfos &s = internal->scoreInfos();
+ addHeader(s);
+ for (uint i=0; i<scores.size(); i++) addLine(s, i, false);
+}
+
+void LastMultipleScoresList::addLineItem(const ItemArray &si,
+ uint index, QListViewItem *line)
+{
+ uint k = 1; // skip "id"
+ for (uint i=0; i<si.size()-2; i++) {
+ if ( i==3 ) k = 5; // skip "date"
+ const ItemContainer *container = si[k];
+ k++;
+ if (line) line->setText(i, itemText(*container, index));
+ else {
+ addColumn( container->item()->label() );
+ setColumnAlignment(i, container->item()->alignment());
+ }
+ }
+}
+
+QString LastMultipleScoresList::itemText(const ItemContainer &item,
+ uint row) const
+{
+ QString name = item.name();
+ if ( name=="rank" )
+ return (_scores[row].type()==Won ? i18n("Winner") : QString::null);
+ QVariant v = _scores[row].data(name);
+ if ( name=="name" ) return v.toString();
+ return item.item()->pretty(row, v);
+}
+
+//-----------------------------------------------------------------------------
+TotalMultipleScoresList::TotalMultipleScoresList(
+ const QValueVector<Score> &scores, QWidget *parent)
+ : ScoresList(parent), _scores(scores)
+{
+ const ScoreInfos &s = internal->scoreInfos();
+ addHeader(s);
+ for (uint i=0; i<scores.size(); i++) addLine(s, i, false);
+}
+
+void TotalMultipleScoresList::addLineItem(const ItemArray &si,
+ uint index, QListViewItem *line)
+{
+ const PlayerInfos &pi = internal->playerInfos();
+ uint k = 1; // skip "id"
+ for (uint i=0; i<4; i++) { // skip additional fields
+ const ItemContainer *container;
+ if ( i==2 ) container = pi.item("nb games");
+ else if ( i==3 ) container = pi.item("mean score");
+ else {
+ container = si[k];
+ k++;
+ }
+ if (line) line->setText(i, itemText(*container, index));
+ else {
+ QString label =
+ (i==2 ? i18n("Won Games") : container->item()->label());
+ addColumn(label);
+ setColumnAlignment(i, container->item()->alignment());
+ }
+ }
+}
+
+QString TotalMultipleScoresList::itemText(const ItemContainer &item,
+ uint row) const
+{
+ QString name = item.name();
+ if ( name=="rank" ) return QString::number(_scores.size()-row);
+ if ( name=="nb games" )
+ return QString::number( _scores[row].data("nb won games").toUInt() );
+ QVariant v = _scores[row].data(name);
+ if ( name=="name" ) return v.toString();
+ return item.item()->pretty(row, v);
+}
+
+
+//-----------------------------------------------------------------------------
+ConfigDialog::ConfigDialog(QWidget *parent)
+ : KDialogBase(Swallow, i18n("Configure Highscores"),
+ Ok|Apply|Cancel, Cancel,
+ parent, "configure_highscores", true, true),
+ _saved(false), _WWHEnabled(0)
+{
+ QWidget *page = 0;
+ QTabWidget *tab = 0;
+ if ( internal->isWWHSAvailable() ) {
+ tab = new QTabWidget(this);
+ setMainWidget(tab);
+ page = new QWidget(tab);
+ tab->addTab(page, i18n("Main"));
+ } else {
+ page = new QWidget(this);
+ setMainWidget(page);
+ }
+
+ QGridLayout *pageTop =
+ new QGridLayout(page, 2, 2, spacingHint(), spacingHint());
+
+ QLabel *label = new QLabel(i18n("Nickname:"), page);
+ pageTop->addWidget(label, 0, 0);
+ _nickname = new QLineEdit(page);
+ connect(_nickname, SIGNAL(textChanged(const QString &)),
+ SLOT(modifiedSlot()));
+ connect(_nickname, SIGNAL(textChanged(const QString &)),
+ SLOT(nickNameChanged(const QString &)));
+
+ _nickname->setMaxLength(16);
+ pageTop->addWidget(_nickname, 0, 1);
+
+ label = new QLabel(i18n("Comment:"), page);
+ pageTop->addWidget(label, 1, 0);
+ _comment = new QLineEdit(page);
+ connect(_comment, SIGNAL(textChanged(const QString &)),
+ SLOT(modifiedSlot()));
+ _comment->setMaxLength(50);
+ pageTop->addWidget(_comment, 1, 1);
+
+ if (tab) {
+ _WWHEnabled
+ = new QCheckBox(i18n("World-wide highscores enabled"), page);
+ connect(_WWHEnabled, SIGNAL(toggled(bool)),
+ SLOT(modifiedSlot()));
+ pageTop->addMultiCellWidget(_WWHEnabled, 2, 2, 0, 1);
+
+ // advanced tab
+ QWidget *page = new QWidget(tab);
+ tab->addTab(page, i18n("Advanced"));
+ QVBoxLayout *pageTop =
+ new QVBoxLayout(page, spacingHint(), spacingHint());
+
+ QVGroupBox *group = new QVGroupBox(i18n("Registration Data"), page);
+ pageTop->addWidget(group);
+ QGrid *grid = new QGrid(2, group);
+ grid->setSpacing(spacingHint());
+
+ label = new QLabel(i18n("Nickname:"), grid);
+ _registeredName = new KLineEdit(grid);
+ _registeredName->setReadOnly(true);
+
+ label = new QLabel(i18n("Key:"), grid);
+ _key = new KLineEdit(grid);
+ _key->setReadOnly(true);
+
+ KGuiItem gi = KStdGuiItem::clear();
+ gi.setText(i18n("Remove"));
+ _removeButton = new KPushButton(gi, grid);
+ connect(_removeButton, SIGNAL(clicked()), SLOT(removeSlot()));
+ }
+
+ load();
+ enableButtonOK( !_nickname->text().isEmpty() );
+ enableButtonApply(false);
+}
+
+void ConfigDialog::nickNameChanged(const QString &text)
+{
+ enableButtonOK( !text.isEmpty() );
+}
+
+
+void ConfigDialog::modifiedSlot()
+{
+ enableButtonApply(true && !_nickname->text().isEmpty() );
+}
+
+void ConfigDialog::accept()
+{
+ if ( save() ) {
+ KDialogBase::accept();
+ kapp->config()->sync(); // safer
+ }
+}
+
+void ConfigDialog::removeSlot()
+{
+ KGuiItem gi = KStdGuiItem::clear();
+ gi.setText(i18n("Remove"));
+ int res = KMessageBox::warningContinueCancel(this,
+ i18n("This will permanently remove your "
+ "registration key. You will not be able to use "
+ "the currently registered nickname anymore."),
+ QString::null, gi);
+ if ( res==KMessageBox::Continue ) {
+ internal->playerInfos().removeKey();
+ _registeredName->clear();
+ _key->clear();
+ _removeButton->setEnabled(false);
+ _WWHEnabled->setChecked(false);
+ modifiedSlot();
+ }
+}
+
+void ConfigDialog::load()
+{
+ internal->hsConfig().readCurrentConfig();
+ const PlayerInfos &infos = internal->playerInfos();
+ _nickname->setText(infos.isAnonymous() ? QString::null : infos.name());
+ _comment->setText(infos.comment());
+ if (_WWHEnabled) {
+ _WWHEnabled->setChecked(infos.isWWEnabled());
+ if ( !infos.key().isEmpty() ) {
+ _registeredName->setText(infos.registeredName());
+ _registeredName->home(false);
+ _key->setText(infos.key());
+ _key->home(false);
+ }
+ _removeButton->setEnabled(!infos.key().isEmpty());
+ }
+}
+
+bool ConfigDialog::save()
+{
+ bool enabled = (_WWHEnabled ? _WWHEnabled->isChecked() : false);
+
+ // do not bother the user with "nickname empty" if he has not
+ // messed with nickname settings ...
+ QString newName = _nickname->text();
+ if ( newName.isEmpty() && !internal->playerInfos().isAnonymous()
+ && !enabled ) return true;
+
+ if ( newName.isEmpty() ) {
+ KMessageBox::sorry(this, i18n("Please choose a non empty nickname."));
+ return false;
+ }
+ if ( internal->playerInfos().isNameUsed(newName) ) {
+ KMessageBox::sorry(this, i18n("Nickname already in use. Please "
+ "choose another one"));
+ return false;
+ }
+
+ int res =
+ internal->modifySettings(newName, _comment->text(), enabled, this);
+ if (res) {
+ load(); // needed to update view when "apply" is clicked
+ enableButtonApply(false);
+ }
+ _saved = true;
+ return res;
+}
+
+//-----------------------------------------------------------------------------
+AskNameDialog::AskNameDialog(QWidget *parent)
+ : KDialogBase(Plain, i18n("Enter Your Nickname"), Ok | Cancel, Ok,
+ parent, "ask_name_dialog")
+{
+ internal->hsConfig().readCurrentConfig();
+
+ QVBoxLayout *top =
+ new QVBoxLayout(plainPage(), marginHint(), spacingHint());
+ QLabel *label =
+ new QLabel(i18n("Congratulations, you have won!"), plainPage());
+ top->addWidget(label);
+
+ QHBoxLayout *hbox = new QHBoxLayout(top);
+ label = new QLabel(i18n("Enter your nickname:"), plainPage());
+ hbox->addWidget(label);
+ _edit = new QLineEdit(plainPage());
+ _edit->setFocus();
+ connect(_edit, SIGNAL(textChanged(const QString &)), SLOT(nameChanged()));
+ hbox->addWidget(_edit);
+
+ top->addSpacing(spacingHint());
+ _checkbox = new QCheckBox(i18n("Do not ask again."), plainPage());
+ top->addWidget(_checkbox);
+
+ nameChanged();
+}
+
+void AskNameDialog::nameChanged()
+{
+ enableButtonOK( !name().isEmpty()
+ && !internal->playerInfos().isNameUsed(name()) );
+}
+
+} // namespace
diff --git a/libkdegames/highscore/kexthighscore_gui.h b/libkdegames/highscore/kexthighscore_gui.h
new file mode 100644
index 00000000..e721299a
--- /dev/null
+++ b/libkdegames/highscore/kexthighscore_gui.h
@@ -0,0 +1,207 @@
+/*
+ This file is part of the KDE games library
+ Copyright (C) 2001-02 Nicolas Hadacek ([email protected])
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXTHIGHSCORE_GUI_H
+#define KEXTHIGHSCORE_GUI_H
+
+#include <qcheckbox.h>
+#include <qlabel.h>
+#include <qvbox.h>
+#include <qtabwidget.h>
+
+#include <klistview.h>
+#include <klineedit.h>
+#include <kpushbutton.h>
+#include <kdialogbase.h>
+
+#include "kexthighscore.h"
+
+
+namespace KExtHighscore
+{
+
+class ItemContainer;
+class ItemArray;
+class Score;
+class AdditionalTab;
+
+//-----------------------------------------------------------------------------
+class ShowItem : public KListViewItem
+{
+ public:
+ ShowItem(QListView *, bool highlight);
+
+ protected:
+ virtual void paintCell(QPainter *, const QColorGroup &, int column,
+ int width, int align);
+
+ private:
+ bool _highlight;
+};
+
+class ScoresList : public KListView
+{
+ Q_OBJECT
+ public:
+ ScoresList(QWidget *parent);
+
+ void addHeader(const ItemArray &);
+
+ protected:
+ QListViewItem *addLine(const ItemArray &, uint index, bool highlight);
+ virtual QString itemText(const ItemContainer &, uint row) const = 0;
+
+ private:
+ virtual void addLineItem(const ItemArray &, uint index,
+ QListViewItem *item);
+};
+
+//-----------------------------------------------------------------------------
+class HighscoresList : public ScoresList
+{
+ Q_OBJECT
+ public:
+ HighscoresList(QWidget *parent);
+
+ void load(const ItemArray &, int highlight);
+
+ protected:
+ QString itemText(const ItemContainer &, uint row) const;
+};
+
+class HighscoresWidget : public QWidget
+{
+ Q_OBJECT
+ public:
+ HighscoresWidget(QWidget *parent);
+
+ void load(int rank);
+
+ signals:
+ void tabChanged(int i);
+
+ public slots:
+ void changeTab(int i);
+
+ private slots:
+ void showURL(const QString &) const;
+ void tabChanged() { emit tabChanged(_tw->currentPageIndex()); }
+
+ private:
+ QTabWidget *_tw;
+ HighscoresList *_scoresList, *_playersList;
+ KURLLabel *_scoresUrl, *_playersUrl;
+ AdditionalTab *_statsTab, *_histoTab;
+};
+
+class HighscoresDialog : public KDialogBase
+{
+ Q_OBJECT
+ public:
+ HighscoresDialog(int rank, QWidget *parent);
+
+ private slots:
+ void slotUser1();
+ void slotUser2();
+ void tabChanged(int i) { _tab = i; }
+ void createPage(QWidget *);
+
+ private:
+ int _rank, _tab;
+ QWidget *_current;
+ QValueVector<HighscoresWidget *> _widgets;
+};
+
+//-----------------------------------------------------------------------------
+class LastMultipleScoresList : public ScoresList
+{
+ Q_OBJECT
+public:
+ LastMultipleScoresList(const QValueVector<Score> &, QWidget *parent);
+
+private:
+ void addLineItem(const ItemArray &, uint index, QListViewItem *line);
+ QString itemText(const ItemContainer &, uint row) const;
+
+private:
+ const QValueVector<Score> &_scores;
+};
+
+class TotalMultipleScoresList : public ScoresList
+{
+ Q_OBJECT
+public:
+ TotalMultipleScoresList(const QValueVector<Score> &, QWidget *parent);
+
+private:
+ void addLineItem(const ItemArray &, uint index, QListViewItem *line);
+ QString itemText(const ItemContainer &, uint row) const;
+
+private:
+ const QValueVector<Score> &_scores;
+};
+
+//-----------------------------------------------------------------------------
+class ConfigDialog : public KDialogBase
+{
+ Q_OBJECT
+ public:
+ ConfigDialog(QWidget *parent);
+
+ bool hasBeenSaved() const { return _saved; }
+
+ private slots:
+ void modifiedSlot();
+ void removeSlot();
+ void accept();
+ void slotApply() { save(); }
+ void nickNameChanged(const QString &);
+
+ private:
+ bool _saved;
+ QCheckBox *_WWHEnabled;
+ QLineEdit *_nickname, *_comment;
+ KLineEdit *_key, *_registeredName;
+ KPushButton *_removeButton;
+
+ void load();
+ bool save();
+};
+
+//-----------------------------------------------------------------------------
+class AskNameDialog : public KDialogBase
+{
+ Q_OBJECT
+ public:
+ AskNameDialog(QWidget *parent);
+
+ QString name() const { return _edit->text(); }
+ bool dontAskAgain() const { return _checkbox->isChecked(); }
+
+ private slots:
+ void nameChanged();
+
+ private:
+ QLineEdit *_edit;
+ QCheckBox *_checkbox;
+};
+
+} // namespace
+
+#endif
diff --git a/libkdegames/highscore/kexthighscore_internal.cpp b/libkdegames/highscore/kexthighscore_internal.cpp
new file mode 100644
index 00000000..a8395753
--- /dev/null
+++ b/libkdegames/highscore/kexthighscore_internal.cpp
@@ -0,0 +1,868 @@
+/*
+ This file is part of the KDE games library
+ Copyright (C) 2001-2004 Nicolas Hadacek ([email protected])
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kexthighscore_internal.h"
+
+#include <pwd.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <qfile.h>
+#include <qlayout.h>
+#include <qdom.h>
+
+#include <kglobal.h>
+#include <kio/netaccess.h>
+#include <kio/job.h>
+#include <kmessagebox.h>
+#include <kmdcodec.h>
+#include <kdebug.h>
+
+#include "config.h"
+#include "kexthighscore.h"
+#include "kexthighscore_gui.h"
+#include "kemailsettings.h"
+
+
+namespace KExtHighscore
+{
+
+//-----------------------------------------------------------------------------
+const char ItemContainer::ANONYMOUS[] = "_";
+const char ItemContainer::ANONYMOUS_LABEL[] = I18N_NOOP("anonymous");
+
+ItemContainer::ItemContainer()
+ : _item(0)
+{}
+
+ItemContainer::~ItemContainer()
+{
+ delete _item;
+}
+
+void ItemContainer::setItem(Item *item)
+{
+ delete _item;
+ _item = item;
+}
+
+QString ItemContainer::entryName() const
+{
+ if ( _subGroup.isEmpty() ) return _name;
+ return _name + "_" + _subGroup;
+}
+
+QVariant ItemContainer::read(uint i) const
+{
+ Q_ASSERT(_item);
+
+ QVariant v = _item->defaultValue();
+ if ( isStored() ) {
+ internal->hsConfig().setHighscoreGroup(_group);
+ v = internal->hsConfig().readPropertyEntry(i+1, entryName(), v);
+ }
+ return _item->read(i, v);
+}
+
+QString ItemContainer::pretty(uint i) const
+{
+ Q_ASSERT(_item);
+ return _item->pretty(i, read(i));
+}
+
+void ItemContainer::write(uint i, const QVariant &value) const
+{
+ Q_ASSERT( isStored() );
+ Q_ASSERT( internal->hsConfig().isLocked() );
+ internal->hsConfig().setHighscoreGroup(_group);
+ internal->hsConfig().writeEntry(i+1, entryName(), value);
+}
+
+uint ItemContainer::increment(uint i) const
+{
+ uint v = read(i).toUInt() + 1;
+ write(i, v);
+ return v;
+}
+
+//-----------------------------------------------------------------------------
+ItemArray::ItemArray()
+ : _group(""), _subGroup("") // no null groups
+{}
+
+ItemArray::~ItemArray()
+{
+ for (uint i=0; i<size(); i++) delete at(i);
+}
+
+int ItemArray::findIndex(const QString &name) const
+{
+ for (uint i=0; i<size(); i++)
+ if ( at(i)->name()==name ) return i;
+ return -1;
+}
+
+const ItemContainer *ItemArray::item(const QString &name) const
+{
+ int i = findIndex(name);
+ if ( i==-1 ) kdError(11002) << k_funcinfo << "no item named \"" << name
+ << "\"" << endl;
+ return at(i);
+}
+
+ItemContainer *ItemArray::item(const QString &name)
+{
+ int i = findIndex(name);
+ if ( i==-1 ) kdError(11002) << k_funcinfo << "no item named \"" << name
+ << "\"" << endl;
+ return at(i);
+}
+
+void ItemArray::setItem(const QString &name, Item *item)
+{
+ int i = findIndex(name);
+ if ( i==-1 ) kdError(11002) << k_funcinfo << "no item named \"" << name
+ << "\"" << endl;
+ bool stored = at(i)->isStored();
+ bool canHaveSubGroup = at(i)->canHaveSubGroup();
+ _setItem(i, name, item, stored, canHaveSubGroup);
+}
+
+void ItemArray::addItem(const QString &name, Item *item,
+ bool stored, bool canHaveSubGroup)
+{
+ if ( findIndex(name)!=-1 )
+ kdError(11002) << "item already exists \"" << name << "\"" << endl;
+ uint i = size();
+ resize(i+1);
+ at(i) = new ItemContainer;
+ _setItem(i, name, item, stored, canHaveSubGroup);
+}
+
+void ItemArray::_setItem(uint i, const QString &name, Item *item,
+ bool stored, bool canHaveSubGroup)
+{
+ at(i)->setItem(item);
+ at(i)->setName(name);
+ at(i)->setGroup(stored ? _group : QString::null);
+ at(i)->setSubGroup(canHaveSubGroup ? _subGroup : QString::null);
+}
+
+void ItemArray::setGroup(const QString &group)
+{
+ Q_ASSERT( !group.isNull() );
+ _group = group;
+ for (uint i=0; i<size(); i++)
+ if ( at(i)->isStored() ) at(i)->setGroup(group);
+}
+
+void ItemArray::setSubGroup(const QString &subGroup)
+{
+ Q_ASSERT( !subGroup.isNull() );
+ _subGroup = subGroup;
+ for (uint i=0; i<size(); i++)
+ if ( at(i)->canHaveSubGroup() ) at(i)->setSubGroup(subGroup);
+}
+
+void ItemArray::read(uint k, Score &data) const
+{
+ for (uint i=0; i<size(); i++) {
+ if ( !at(i)->isStored() ) continue;
+ data.setData(at(i)->name(), at(i)->read(k));
+ }
+}
+
+void ItemArray::write(uint k, const Score &data, uint nb) const
+{
+ for (uint i=0; i<size(); i++) {
+ if ( !at(i)->isStored() ) continue;
+ for (uint j=nb-1; j>k; j--) at(i)->write(j, at(i)->read(j-1));
+ at(i)->write(k, data.data(at(i)->name()));
+ }
+}
+
+void ItemArray::exportToText(QTextStream &s) const
+{
+ for (uint k=0; k<nbEntries()+1; k++) {
+ for (uint i=0; i<size(); i++) {
+ const Item *item = at(i)->item();
+ if ( item->isVisible() ) {
+ if ( i!=0 ) s << '\t';
+ if ( k==0 ) s << item->label();
+ else s << at(i)->pretty(k-1);
+ }
+ }
+ s << endl;
+ }
+}
+
+//-----------------------------------------------------------------------------
+class ScoreNameItem : public NameItem
+{
+ public:
+ ScoreNameItem(const ScoreInfos &score, const PlayerInfos &infos)
+ : _score(score), _infos(infos) {}
+
+ QString pretty(uint i, const QVariant &v) const {
+ uint id = _score.item("id")->read(i).toUInt();
+ if ( id==0 ) return NameItem::pretty(i, v);
+ return _infos.prettyName(id-1);
+ }
+
+ private:
+ const ScoreInfos &_score;
+ const PlayerInfos &_infos;
+};
+
+//-----------------------------------------------------------------------------
+ScoreInfos::ScoreInfos(uint maxNbEntries, const PlayerInfos &infos)
+ : _maxNbEntries(maxNbEntries)
+{
+ addItem("id", new Item((uint)0));
+ addItem("rank", new RankItem, false);
+ addItem("name", new ScoreNameItem(*this, infos));
+ addItem("score", Manager::createItem(Manager::ScoreDefault));
+ addItem("date", new DateItem);
+}
+
+uint ScoreInfos::nbEntries() const
+{
+ uint i = 0;
+ for (; i<_maxNbEntries; i++)
+ if ( item("score")->read(i)==item("score")->item()->defaultValue() )
+ break;
+ return i;
+}
+
+//-----------------------------------------------------------------------------
+const char *HS_ID = "player id";
+const char *HS_REGISTERED_NAME = "registered name";
+const char *HS_KEY = "player key";
+const char *HS_WW_ENABLED = "ww hs enabled";
+
+PlayerInfos::PlayerInfos()
+{
+ setGroup("players");
+
+ // standard items
+ addItem("name", new NameItem);
+ Item *it = new Item((uint)0, i18n("Games Count"),Qt::AlignRight);
+ addItem("nb games", it, true, true);
+ it = Manager::createItem(Manager::MeanScoreDefault);
+ addItem("mean score", it, true, true);
+ it = Manager::createItem(Manager::BestScoreDefault);
+ addItem("best score", it, true, true);
+ addItem("date", new DateItem, true, true);
+ it = new Item(QString::null, i18n("Comment"), Qt::AlignLeft);
+ addItem("comment", it);
+
+ // statistics items
+ addItem("nb black marks", new Item((uint)0), true, true); // legacy
+ addItem("nb lost games", new Item((uint)0), true, true);
+ addItem("nb draw games", new Item((uint)0), true, true);
+ addItem("current trend", new Item((int)0), true, true);
+ addItem("max lost trend", new Item((uint)0), true, true);
+ addItem("max won trend", new Item((uint)0), true, true);
+
+ struct passwd *pwd = getpwuid(getuid());
+ QString username = pwd->pw_name;
+#ifdef HIGHSCORE_DIRECTORY
+ internal->hsConfig().setHighscoreGroup("players");
+ for (uint i=0; ;i++) {
+ if ( !internal->hsConfig().hasEntry(i+1, "username") ) {
+ _newPlayer = true;
+ _id = i;
+ break;
+ }
+ if ( internal->hsConfig().readEntry(i+1, "username")==username ) {
+ _newPlayer = false;
+ _id = i;
+ return;
+ }
+ }
+#endif
+ internal->hsConfig().lockForWriting();
+ KEMailSettings emailConfig;
+ emailConfig.setProfile(emailConfig.defaultProfileName());
+ QString name = emailConfig.getSetting(KEMailSettings::RealName);
+ if ( name.isEmpty() || isNameUsed(name) ) name = username;
+ if ( isNameUsed(name) ) name= QString(ItemContainer::ANONYMOUS);
+#ifdef HIGHSCORE_DIRECTORY
+ internal->hsConfig().writeEntry(_id+1, "username", username);
+ item("name")->write(_id, name);
+#endif
+
+ ConfigGroup cg;
+ _oldLocalPlayer = cg.config()->hasKey(HS_ID);
+ _oldLocalId = cg.config()->readUnsignedNumEntry(HS_ID);
+#ifdef HIGHSCORE_DIRECTORY
+ if (_oldLocalPlayer) { // player already exists in local config file
+ // copy player data
+ QString prefix = QString("%1_").arg(_oldLocalId+1);
+ QMap<QString, QString> entries =
+ cg.config()->entryMap("KHighscore_players");
+ QMap<QString, QString>::const_iterator it;
+ for (it=entries.begin(); it!=entries.end(); ++it) {
+ QString key = it.key();
+ if ( key.find(prefix)==0 ) {
+ QString name = key.right(key.length()-prefix.length());
+ if ( name!="name" || !isNameUsed(it.data()) )
+ internal->hsConfig().writeEntry(_id+1, name, it.data());
+ }
+ }
+ }
+#else
+ _newPlayer = !_oldLocalPlayer;
+ if (_oldLocalPlayer) _id = _oldLocalId;
+ else {
+ _id = nbEntries();
+ cg.config()->writeEntry(HS_ID, _id);
+ item("name")->write(_id, name);
+ }
+#endif
+ _bound = true;
+ internal->hsConfig().writeAndUnlock();
+}
+
+void PlayerInfos::createHistoItems(const QMemArray<uint> &scores, bool bound)
+{
+ Q_ASSERT( _histogram.size()==0 );
+ _bound = bound;
+ _histogram = scores;
+ for (uint i=1; i<histoSize(); i++)
+ addItem(histoName(i), new Item((uint)0), true, true);
+}
+
+bool PlayerInfos::isAnonymous() const
+{
+ return ( name()==ItemContainer::ANONYMOUS );
+}
+
+uint PlayerInfos::nbEntries() const
+{
+ internal->hsConfig().setHighscoreGroup("players");
+ QStringList list = internal->hsConfig().readList("name", -1);
+ return list.count();
+}
+
+QString PlayerInfos::key() const
+{
+ ConfigGroup cg;
+ return cg.config()->readEntry(HS_KEY, QString::null);
+}
+
+bool PlayerInfos::isWWEnabled() const
+{
+ ConfigGroup cg;
+ return cg.config()->readBoolEntry(HS_WW_ENABLED, false);
+}
+
+QString PlayerInfos::histoName(uint i) const
+{
+ const QMemArray<uint> &sh = _histogram;
+ Q_ASSERT( i<sh.size() || (_bound || i==sh.size()) );
+ if ( i==sh.size() )
+ return QString("nb scores greater than %1").arg(sh[sh.size()-1]);
+ return QString("nb scores less than %1").arg(sh[i]);
+}
+
+uint PlayerInfos::histoSize() const
+{
+ return _histogram.size() + (_bound ? 0 : 1);
+}
+
+void PlayerInfos::submitScore(const Score &score) const
+{
+ // update counts
+ uint nbGames = item("nb games")->increment(_id);
+ switch (score.type()) {
+ case Lost:
+ item("nb lost games")->increment(_id);
+ break;
+ case Won: break;
+ case Draw:
+ item("nb draw games")->increment(_id);
+ break;
+ };
+
+ // update mean
+ if ( score.type()==Won ) {
+ uint nbWonGames = nbGames - item("nb lost games")->read(_id).toUInt()
+ - item("nb draw games")->read(_id).toUInt()
+ - item("nb black marks")->read(_id).toUInt(); // legacy
+ double mean = (nbWonGames==1 ? 0.0
+ : item("mean score")->read(_id).toDouble());
+ mean += (double(score.score()) - mean) / nbWonGames;
+ item("mean score")->write(_id, mean);
+ }
+
+ // update best score
+ Score best = score; // copy optionnal fields (there are not taken into account here)
+ best.setScore( item("best score")->read(_id).toUInt() );
+ if ( best<score ) {
+ item("best score")->write(_id, score.score());
+ item("date")->write(_id, score.data("date").toDateTime());
+ }
+
+ // update trends
+ int current = item("current trend")->read(_id).toInt();
+ switch (score.type()) {
+ case Won: {
+ if ( current<0 ) current = 0;
+ current++;
+ uint won = item("max won trend")->read(_id).toUInt();
+ if ( (uint)current>won ) item("max won trend")->write(_id, current);
+ break;
+ }
+ case Lost: {
+ if ( current>0 ) current = 0;
+ current--;
+ uint lost = item("max lost trend")->read(_id).toUInt();
+ uint clost = -current;
+ if ( clost>lost ) item("max lost trend")->write(_id, clost);
+ break;
+ }
+ case Draw:
+ current = 0;
+ break;
+ }
+ item("current trend")->write(_id, current);
+
+ // update histogram
+ if ( score.type()==Won ) {
+ const QMemArray<uint> &sh = _histogram;
+ for (uint i=1; i<histoSize(); i++)
+ if ( i==sh.size() || score.score()<sh[i] ) {
+ item(histoName(i))->increment(_id);
+ break;
+ }
+ }
+}
+
+bool PlayerInfos::isNameUsed(const QString &newName) const
+{
+ if ( newName==name() ) return false; // own name...
+ for (uint i=0; i<nbEntries(); i++)
+ if ( newName.lower()==item("name")->read(i).toString().lower() ) return true;
+ if ( newName==i18n(ItemContainer::ANONYMOUS_LABEL) ) return true;
+ return false;
+}
+
+void PlayerInfos::modifyName(const QString &newName) const
+{
+ item("name")->write(_id, newName);
+}
+
+void PlayerInfos::modifySettings(const QString &newName,
+ const QString &comment, bool WWEnabled,
+ const QString &newKey) const
+{
+ modifyName(newName);
+ item("comment")->write(_id, comment);
+ ConfigGroup cg;
+ cg.config()->writeEntry(HS_WW_ENABLED, WWEnabled);
+ if ( !newKey.isEmpty() ) cg.config()->writeEntry(HS_KEY, newKey);
+ if (WWEnabled) cg.config()->writeEntry(HS_REGISTERED_NAME, newName);
+}
+
+QString PlayerInfos::registeredName() const
+{
+ ConfigGroup cg;
+ return cg.config()->readEntry(HS_REGISTERED_NAME, QString::null);
+}
+
+void PlayerInfos::removeKey()
+{
+ ConfigGroup cg;
+
+ // save old key/nickname
+ uint i = 0;
+ QString str = "%1 old #%2";
+ QString sk;
+ do {
+ i++;
+ sk = str.arg(HS_KEY).arg(i);
+ } while ( !cg.config()->readEntry(sk, QString::null).isEmpty() );
+ cg.config()->writeEntry(sk, key());
+ cg.config()->writeEntry(str.arg(HS_REGISTERED_NAME).arg(i),
+ registeredName());
+
+ // clear current key/nickname
+ cg.config()->deleteEntry(HS_KEY);
+ cg.config()->deleteEntry(HS_REGISTERED_NAME);
+ cg.config()->writeEntry(HS_WW_ENABLED, false);
+}
+
+//-----------------------------------------------------------------------------
+ManagerPrivate::ManagerPrivate(uint nbGameTypes, Manager &m)
+ : manager(m), showStatistics(false), showDrawGames(false),
+ trackLostGames(false), trackDrawGames(false),
+ showMode(Manager::ShowForHigherScore),
+ _first(true), _nbGameTypes(nbGameTypes), _gameType(0)
+{}
+
+void ManagerPrivate::init(uint maxNbEntries)
+{
+ _hsConfig = new KHighscore(false, 0);
+ _playerInfos = new PlayerInfos;
+ _scoreInfos = new ScoreInfos(maxNbEntries, *_playerInfos);
+}
+
+ManagerPrivate::~ManagerPrivate()
+{
+ delete _scoreInfos;
+ delete _playerInfos;
+ delete _hsConfig;
+}
+
+KURL ManagerPrivate::queryURL(QueryType type, const QString &newName) const
+{
+ KURL url = serverURL;
+ QString nameItem = "nickname";
+ QString name = _playerInfos->registeredName();
+ bool withVersion = true;
+ bool key = false;
+ bool level = false;
+
+ switch (type) {
+ case Submit:
+ url.addPath("submit.php");
+ level = true;
+ key = true;
+ break;
+ case Register:
+ url.addPath("register.php");
+ name = newName;
+ break;
+ case Change:
+ url.addPath("change.php");
+ key = true;
+ if ( newName!=name )
+ Manager::addToQueryURL(url, "new_nickname", newName);
+ break;
+ case Players:
+ url.addPath("players.php");
+ nameItem = "highlight";
+ withVersion = false;
+ break;
+ case Scores:
+ url.addPath("highscores.php");
+ withVersion = false;
+ if ( _nbGameTypes>1 ) level = true;
+ break;
+ }
+
+ if (withVersion) Manager::addToQueryURL(url, "version", version);
+ if ( !name.isEmpty() ) Manager::addToQueryURL(url, nameItem, name);
+ if (key) Manager::addToQueryURL(url, "key", _playerInfos->key());
+ if (level) {
+ QString label = manager.gameTypeLabel(_gameType, Manager::WW);
+ if ( !label.isEmpty() ) Manager::addToQueryURL(url, "level", label);
+ }
+
+ return url;
+}
+
+// strings that needs to be translated (coming from the highscores server)
+const char *DUMMY_STRINGS[] = {
+ I18N_NOOP("Undefined error."),
+ I18N_NOOP("Missing argument(s)."),
+ I18N_NOOP("Invalid argument(s)."),
+
+ I18N_NOOP("Unable to connect to MySQL server."),
+ I18N_NOOP("Unable to select database."),
+ I18N_NOOP("Error on database query."),
+ I18N_NOOP("Error on database insert."),
+
+ I18N_NOOP("Nickname already registered."),
+ I18N_NOOP("Nickname not registered."),
+ I18N_NOOP("Invalid key."),
+ I18N_NOOP("Invalid submit key."),
+
+ I18N_NOOP("Invalid level."),
+ I18N_NOOP("Invalid score.")
+};
+
+const char *UNABLE_TO_CONTACT =
+ I18N_NOOP("Unable to contact world-wide highscore server");
+
+bool ManagerPrivate::doQuery(const KURL &url, QWidget *parent,
+ QDomNamedNodeMap *map)
+{
+ KIO::http_update_cache(url, true, 0); // remove cache !
+
+ QString tmpFile;
+ if ( !KIO::NetAccess::download(url, tmpFile, parent) ) {
+ QString details = i18n("Server URL: %1").arg(url.host());
+ KMessageBox::detailedSorry(parent, i18n(UNABLE_TO_CONTACT), details);
+ return false;
+ }
+
+ QFile file(tmpFile);
+ if ( !file.open(IO_ReadOnly) ) {
+ KIO::NetAccess::removeTempFile(tmpFile);
+ QString details = i18n("Unable to open temporary file.");
+ KMessageBox::detailedSorry(parent, i18n(UNABLE_TO_CONTACT), details);
+ return false;
+ }
+
+ QTextStream t(&file);
+ QString content = t.read().stripWhiteSpace();
+ file.close();
+ KIO::NetAccess::removeTempFile(tmpFile);
+
+ QDomDocument doc;
+ if ( doc.setContent(content) ) {
+ QDomElement root = doc.documentElement();
+ QDomElement element = root.firstChild().toElement();
+ if ( element.tagName()=="success" ) {
+ if (map) *map = element.attributes();
+ return true;
+ }
+ if ( element.tagName()=="error" ) {
+ QDomAttr attr = element.attributes().namedItem("label").toAttr();
+ if ( !attr.isNull() ) {
+ QString msg = i18n(attr.value().latin1());
+ QString caption = i18n("Message from world-wide highscores "
+ "server");
+ KMessageBox::sorry(parent, msg, caption);
+ return false;
+ }
+ }
+ }
+ QString msg = i18n("Invalid answer from world-wide highscores server.");
+ QString details = i18n("Raw message: %1").arg(content);
+ KMessageBox::detailedSorry(parent, msg, details);
+ return false;
+}
+
+bool ManagerPrivate::getFromQuery(const QDomNamedNodeMap &map,
+ const QString &name, QString &value,
+ QWidget *parent)
+{
+ QDomAttr attr = map.namedItem(name).toAttr();
+ if ( attr.isNull() ) {
+ KMessageBox::sorry(parent,
+ i18n("Invalid answer from world-wide "
+ "highscores server (missing item: %1).").arg(name));
+ return false;
+ }
+ value = attr.value();
+ return true;
+}
+
+Score ManagerPrivate::readScore(uint i) const
+{
+ Score score(Won);
+ _scoreInfos->read(i, score);
+ return score;
+}
+
+int ManagerPrivate::rank(const Score &score) const
+{
+ uint nb = _scoreInfos->nbEntries();
+ uint i = 0;
+ for (; i<nb; i++)
+ if ( readScore(i)<score ) break;
+ return (i<_scoreInfos->maxNbEntries() ? (int)i : -1);
+}
+
+bool ManagerPrivate::modifySettings(const QString &newName,
+ const QString &comment, bool WWEnabled,
+ QWidget *widget)
+{
+ QString newKey;
+ bool newPlayer = false;
+
+ if (WWEnabled) {
+ newPlayer = _playerInfos->key().isEmpty()
+ || _playerInfos->registeredName().isEmpty();
+ KURL url = queryURL((newPlayer ? Register : Change), newName);
+ Manager::addToQueryURL(url, "comment", comment);
+
+ QDomNamedNodeMap map;
+ bool ok = doQuery(url, widget, &map);
+ if ( !ok || (newPlayer && !getFromQuery(map, "key", newKey, widget)) )
+ return false;
+ }
+
+ bool ok = _hsConfig->lockForWriting(widget); // no GUI when locking
+ if (ok) {
+ // check again name in case the config file has been changed...
+ // if it has, it is unfortunate because the WWW name is already
+ // committed but should be very rare and not really problematic
+ ok = ( !_playerInfos->isNameUsed(newName) );
+ if (ok)
+ _playerInfos->modifySettings(newName, comment, WWEnabled, newKey);
+ _hsConfig->writeAndUnlock();
+ }
+ return ok;
+}
+
+void ManagerPrivate::convertToGlobal()
+{
+ // read old highscores
+ KHighscore *tmp = _hsConfig;
+ _hsConfig = new KHighscore(true, 0);
+ QValueVector<Score> scores(_scoreInfos->nbEntries());
+ for (uint i=0; i<scores.count(); i++)
+ scores[i] = readScore(i);
+
+ // commit them
+ delete _hsConfig;
+ _hsConfig = tmp;
+ _hsConfig->lockForWriting();
+ for (uint i=0; i<scores.count(); i++)
+ if ( scores[i].data("id").toUInt()==_playerInfos->oldLocalId()+1 )
+ submitLocal(scores[i]);
+ _hsConfig->writeAndUnlock();
+}
+
+void ManagerPrivate::setGameType(uint type)
+{
+ if (_first) {
+ _first = false;
+ if ( _playerInfos->isNewPlayer() ) {
+ // convert legacy highscores
+ for (uint i=0; i<_nbGameTypes; i++) {
+ setGameType(i);
+ manager.convertLegacy(i);
+ }
+
+#ifdef HIGHSCORE_DIRECTORY
+ if ( _playerInfos->isOldLocalPlayer() ) {
+ // convert local to global highscores
+ for (uint i=0; i<_nbGameTypes; i++) {
+ setGameType(i);
+ convertToGlobal();
+ }
+ }
+#endif
+ }
+ }
+
+ Q_ASSERT( type<_nbGameTypes );
+ _gameType = kMin(type, _nbGameTypes-1);
+ QString str = "scores";
+ QString lab = manager.gameTypeLabel(_gameType, Manager::Standard);
+ if ( !lab.isEmpty() ) {
+ _playerInfos->setSubGroup(lab);
+ str += "_" + lab;
+ }
+ _scoreInfos->setGroup(str);
+}
+
+void ManagerPrivate::checkFirst()
+{
+ if (_first) setGameType(0);
+}
+
+int ManagerPrivate::submitScore(const Score &ascore,
+ QWidget *widget, bool askIfAnonymous)
+{
+ checkFirst();
+
+ Score score = ascore;
+ score.setData("id", _playerInfos->id() + 1);
+ score.setData("date", QDateTime::currentDateTime());
+
+ // ask new name if anonymous and winner
+ const char *dontAskAgainName = "highscore_ask_name_dialog";
+ QString newName;
+ KMessageBox::ButtonCode dummy;
+ if ( score.type()==Won && askIfAnonymous && _playerInfos->isAnonymous()
+ && KMessageBox::shouldBeShownYesNo(dontAskAgainName, dummy) ) {
+ AskNameDialog d(widget);
+ if ( d.exec()==QDialog::Accepted ) newName = d.name();
+ if ( d.dontAskAgain() )
+ KMessageBox::saveDontShowAgainYesNo(dontAskAgainName,
+ KMessageBox::No);
+ }
+
+ int rank = -1;
+ if ( _hsConfig->lockForWriting(widget) ) { // no GUI when locking
+ // check again new name in case the config file has been changed...
+ if ( !newName.isEmpty() && !_playerInfos->isNameUsed(newName) )
+ _playerInfos->modifyName(newName);
+
+ // commit locally
+ _playerInfos->submitScore(score);
+ if ( score.type()==Won ) rank = submitLocal(score);
+ _hsConfig->writeAndUnlock();
+ }
+
+ if ( _playerInfos->isWWEnabled() )
+ submitWorldWide(score, widget);
+
+ return rank;
+}
+
+int ManagerPrivate::submitLocal(const Score &score)
+{
+ int r = rank(score);
+ if ( r!=-1 ) {
+ uint nb = _scoreInfos->nbEntries();
+ if ( nb<_scoreInfos->maxNbEntries() ) nb++;
+ _scoreInfos->write(r, score, nb);
+ }
+ return r;
+}
+
+bool ManagerPrivate::submitWorldWide(const Score &score,
+ QWidget *widget) const
+{
+ if ( score.type()==Lost && !trackLostGames ) return true;
+ if ( score.type()==Draw && !trackDrawGames ) return true;
+
+ KURL url = queryURL(Submit);
+ manager.additionalQueryItems(url, score);
+ int s = (score.type()==Won ? score.score() : (int)score.type());
+ QString str = QString::number(s);
+ Manager::addToQueryURL(url, "score", str);
+ KMD5 context(QString(_playerInfos->registeredName() + str).latin1());
+ Manager::addToQueryURL(url, "check", context.hexDigest());
+
+ return doQuery(url, widget);
+}
+
+void ManagerPrivate::exportHighscores(QTextStream &s)
+{
+ uint tmp = _gameType;
+
+ for (uint i=0; i<_nbGameTypes; i++) {
+ setGameType(i);
+ if ( _nbGameTypes>1 ) {
+ if ( i!=0 ) s << endl;
+ s << "--------------------------------" << endl;
+ s << "Game type: "
+ << manager.gameTypeLabel(_gameType, Manager::I18N)
+ << endl;
+ s << endl;
+ }
+ s << "Players list:" << endl;
+ _playerInfos->exportToText(s);
+ s << endl;
+ s << "Highscores list:" << endl;
+ _scoreInfos->exportToText(s);
+ }
+
+ setGameType(tmp);
+}
+
+} // namespace
diff --git a/libkdegames/highscore/kexthighscore_internal.h b/libkdegames/highscore/kexthighscore_internal.h
new file mode 100644
index 00000000..3b206877
--- /dev/null
+++ b/libkdegames/highscore/kexthighscore_internal.h
@@ -0,0 +1,277 @@
+/*
+ This file is part of the KDE games library
+ Copyright (C) 2001-2004 Nicolas Hadacek ([email protected])
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXTHIGHSCORE_INTERNAL_H
+#define KEXTHIGHSCORE_INTERNAL_H
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <klocale.h>
+#include <kurl.h>
+
+#include "khighscore.h"
+#include "kexthighscore.h"
+
+class QTextStream;
+class QTabWidget;
+class QDomNamedNodeMap;
+
+
+namespace KExtHighscore
+{
+
+class PlayerInfos;
+class Score;
+class Manager;
+
+
+//-----------------------------------------------------------------------------
+class RankItem : public Item
+{
+ public:
+ RankItem()
+ : Item((uint)0, i18n("Rank"), Qt::AlignRight) {}
+
+ QVariant read(uint rank, const QVariant &) const { return rank; }
+ QString pretty(uint rank, const QVariant &) const
+ { return QString::number(rank+1); }
+};
+
+class NameItem : public Item
+{
+ public:
+ NameItem()
+ : Item(QString::null, i18n("Name"), Qt::AlignLeft) {
+ setPrettySpecial(Anonymous);
+ }
+};
+
+class DateItem : public Item
+{
+ public:
+ DateItem()
+ : Item(QDateTime(), i18n("Date"), Qt::AlignRight) {
+ setPrettyFormat(DateTime);
+ }
+};
+
+class SuccessPercentageItem : public Item
+{
+ public:
+ SuccessPercentageItem()
+ : Item((double)-1, i18n("Success"), Qt::AlignRight) {
+ setPrettyFormat(Percentage);
+ setPrettySpecial(NegativeNotDefined);
+ }
+};
+
+//-----------------------------------------------------------------------------
+class ItemContainer
+{
+ public:
+ ItemContainer();
+ ~ItemContainer();
+
+ void setItem(Item *item);
+ const Item *item() const { return _item; }
+ Item *item() { return _item; }
+
+ void setName(const QString &name) { _name = name; }
+ QString name() const { return _name; }
+
+ void setGroup(const QString &group) { _group = group; }
+ bool isStored() const { return !_group.isNull(); }
+
+ void setSubGroup(const QString &subGroup) { _subGroup = subGroup; }
+ bool canHaveSubGroup() const { return !_subGroup.isNull(); }
+
+ static const char ANONYMOUS[]; // name assigned to anonymous players
+ static const char ANONYMOUS_LABEL[];
+
+ QVariant read(uint i) const;
+ QString pretty(uint i) const;
+ void write(uint i, const QVariant &value) const;
+ // for UInt QVariant (return new value)
+ uint increment(uint i) const;
+
+ private:
+ Item *_item;
+ QString _name, _group, _subGroup;
+
+ QString entryName() const;
+
+ ItemContainer(const ItemContainer &);
+ ItemContainer &operator =(const ItemContainer &);
+};
+
+//-----------------------------------------------------------------------------
+/**
+ * Manage a bunch of @ref Item which are saved under the same group
+ * in KHighscores config file.
+ */
+class ItemArray : public QMemArray<ItemContainer *>
+{
+ public:
+ ItemArray();
+ virtual ~ItemArray();
+
+ virtual uint nbEntries() const = 0;
+
+ const ItemContainer *item(const QString &name) const;
+ ItemContainer *item(const QString &name);
+
+ void addItem(const QString &name, Item *, bool stored = true,
+ bool canHaveSubGroup = false);
+ void setItem(const QString &name, Item *);
+ int findIndex(const QString &name) const;
+
+ void setGroup(const QString &group);
+ void setSubGroup(const QString &subGroup);
+
+ void read(uint k, Score &data) const;
+ void write(uint k, const Score &data, uint maxNbLines) const;
+
+ void exportToText(QTextStream &) const;
+
+ private:
+ QString _group, _subGroup;
+
+ void _setItem(uint i, const QString &name, Item *, bool stored,
+ bool canHaveSubGroup);
+
+ ItemArray(const ItemArray &);
+ ItemArray &operator =(const ItemArray &);
+};
+
+//-----------------------------------------------------------------------------
+class ScoreInfos : public ItemArray
+{
+ public:
+ ScoreInfos(uint maxNbEntries, const PlayerInfos &infos);
+
+ uint nbEntries() const;
+ uint maxNbEntries() const { return _maxNbEntries; }
+
+ private:
+ uint _maxNbEntries;
+};
+
+//-----------------------------------------------------------------------------
+class ConfigGroup : public KConfigGroupSaver
+{
+ public:
+ ConfigGroup(const QString &group = QString::null)
+ : KConfigGroupSaver(kapp->config(), group) {}
+};
+
+//-----------------------------------------------------------------------------
+class PlayerInfos : public ItemArray
+{
+ public:
+ PlayerInfos();
+
+ bool isNewPlayer() const { return _newPlayer; }
+ bool isOldLocalPlayer() const { return _oldLocalPlayer; }
+ uint nbEntries() const;
+ QString name() const { return item("name")->read(_id).toString(); }
+ bool isAnonymous() const;
+ QString prettyName() const { return prettyName(_id); }
+ QString prettyName(uint id) const { return item("name")->pretty(id); }
+ QString registeredName() const;
+ QString comment() const { return item("comment")->pretty(_id); }
+ bool isWWEnabled() const;
+ QString key() const;
+ uint id() const { return _id; }
+ uint oldLocalId() const { return _oldLocalId; }
+
+ void createHistoItems(const QMemArray<uint> &scores, bool bound);
+ QString histoName(uint i) const;
+ uint histoSize() const;
+ const QMemArray<uint> &histogram() const { return _histogram; }
+
+ void submitScore(const Score &) const;
+ // return true if the nickname is already used locally
+ bool isNameUsed(const QString &name) const;
+ void modifyName(const QString &newName) const;
+ void modifySettings(const QString &newName, const QString &comment,
+ bool WWEnabled, const QString &newKey) const;
+ void removeKey();
+
+ private:
+ bool _newPlayer, _bound, _oldLocalPlayer;
+ uint _id, _oldLocalId;
+ QMemArray<uint> _histogram;
+};
+
+//-----------------------------------------------------------------------------
+class ManagerPrivate
+{
+ public:
+ ManagerPrivate(uint nbGameTypes, Manager &manager);
+ void init(uint maxNbentries);
+ ~ManagerPrivate();
+
+ bool modifySettings(const QString &newName, const QString &comment,
+ bool WWEnabled, QWidget *widget);
+
+ void setGameType(uint type);
+ void checkFirst();
+ int submitLocal(const Score &score);
+ int submitScore(const Score &score, QWidget *widget, bool askIfAnonymous);
+ Score readScore(uint i) const;
+
+ uint gameType() const { return _gameType; }
+ uint nbGameTypes() const { return _nbGameTypes; }
+ bool isWWHSAvailable() const { return !serverURL.isEmpty(); }
+ ScoreInfos &scoreInfos() { return *_scoreInfos; }
+ PlayerInfos &playerInfos() { return *_playerInfos; }
+ KHighscore &hsConfig() { return *_hsConfig; }
+ enum QueryType { Submit, Register, Change, Players, Scores };
+ KURL queryURL(QueryType type, const QString &newName=QString::null) const;
+
+ void exportHighscores(QTextStream &);
+
+ Manager &manager;
+ KURL serverURL;
+ QString version;
+ bool showStatistics, showDrawGames, trackLostGames, trackDrawGames;
+ Manager::ShowMode showMode;
+
+ private:
+ KHighscore *_hsConfig;
+ PlayerInfos *_playerInfos;
+ ScoreInfos *_scoreInfos;
+ bool _first;
+ const uint _nbGameTypes;
+ uint _gameType;
+
+ // return -1 if not a local best score
+ int rank(const Score &score) const;
+
+ bool submitWorldWide(const Score &score, QWidget *parent) const;
+ static bool doQuery(const KURL &url, QWidget *parent,
+ QDomNamedNodeMap *map = 0);
+ static bool getFromQuery(const QDomNamedNodeMap &map, const QString &name,
+ QString &value, QWidget *parent);
+ void convertToGlobal();
+};
+
+} // namespace
+
+#endif
diff --git a/libkdegames/highscore/kexthighscore_item.cpp b/libkdegames/highscore/kexthighscore_item.cpp
new file mode 100644
index 00000000..48556e02
--- /dev/null
+++ b/libkdegames/highscore/kexthighscore_item.cpp
@@ -0,0 +1,312 @@
+/*
+ This file is part of the KDE games library
+ Copyright (C) 2001-2003 Nicolas Hadacek ([email protected])
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kexthighscore_item.h"
+
+#include <qlayout.h>
+#include <kglobal.h>
+#include <kdialogbase.h>
+#include <kdebug.h>
+
+#include "khighscore.h"
+#include "kexthighscore_internal.h"
+#include "kexthighscore_gui.h"
+
+
+namespace KExtHighscore
+{
+
+//-----------------------------------------------------------------------------
+Item::Item(const QVariant &def, const QString &label, int alignment)
+ : _default(def), _label(label), _alignment(alignment),
+ _format(NoFormat), _special(NoSpecial)
+{}
+
+Item::~Item()
+{}
+
+QVariant Item::read(uint, const QVariant &value) const
+{
+ return value;
+}
+
+void Item::setPrettyFormat(Format format)
+{
+ bool buint = ( _default.type()==QVariant::UInt );
+ bool bdouble = ( _default.type()==QVariant::Double );
+ bool bnum = ( buint || bdouble || _default.type()==QVariant::Int );
+
+ switch (format) {
+ case OneDecimal:
+ case Percentage:
+ Q_ASSERT(bdouble);
+ break;
+ case MinuteTime:
+ Q_ASSERT(bnum);
+ break;
+ case DateTime:
+ Q_ASSERT( _default.type()==QVariant::DateTime );
+ break;
+ case NoFormat:
+ break;
+ }
+
+ _format = format;
+}
+
+void Item::setPrettySpecial(Special special)
+{
+ bool buint = ( _default.type()==QVariant::UInt );
+ bool bnum = ( buint || _default.type()==QVariant::Double
+ || _default.type()==QVariant::Int );
+
+ switch (special) {
+ case ZeroNotDefined:
+ Q_ASSERT(bnum);
+ break;
+ case NegativeNotDefined:
+ Q_ASSERT(bnum && !buint);
+ break;
+ case DefaultNotDefined:
+ break;
+ case Anonymous:
+ Q_ASSERT( _default.type()==QVariant::String );
+ break;
+ case NoSpecial:
+ break;
+ }
+
+ _special = special;
+}
+
+QString Item::timeFormat(uint n)
+{
+ Q_ASSERT( n<=3600 && n!=0 );
+ n = 3600 - n;
+ return QString::number(n / 60).rightJustify(2, '0') + ':'
+ + QString::number(n % 60).rightJustify(2, '0');
+}
+
+QString Item::pretty(uint, const QVariant &value) const
+{
+ switch (_special) {
+ case ZeroNotDefined:
+ if ( value.toUInt()==0 ) return "--";
+ break;
+ case NegativeNotDefined:
+ if ( value.toInt()<0 ) return "--";
+ break;
+ case DefaultNotDefined:
+ if ( value==_default ) return "--";
+ break;
+ case Anonymous:
+ if ( value.toString()==ItemContainer::ANONYMOUS )
+ return i18n(ItemContainer::ANONYMOUS_LABEL);
+ break;
+ case NoFormat:
+ break;
+ }
+
+ switch (_format) {
+ case OneDecimal:
+ return QString::number(value.toDouble(), 'f', 1);
+ case Percentage:
+ return QString::number(value.toDouble(), 'f', 1) + "%";
+ case MinuteTime:
+ return timeFormat(value.toUInt());
+ case DateTime:
+ if ( value.toDateTime().isNull() ) return "--";
+ return KGlobal::locale()->formatDateTime(value.toDateTime());
+ case NoSpecial:
+ break;
+ }
+
+ return value.toString();
+}
+
+//-----------------------------------------------------------------------------
+Score::Score(ScoreType type)
+ : _type(type)
+{
+ const ItemArray &items = internal->scoreInfos();
+ for (uint i=0; i<items.size(); i++)
+ _data[items[i]->name()] = items[i]->item()->defaultValue();
+}
+
+Score::~Score()
+{}
+
+const QVariant &Score::data(const QString &name) const
+{
+ Q_ASSERT( _data.contains(name) );
+ return _data[name];
+}
+
+void Score::setData(const QString &name, const QVariant &value)
+{
+ Q_ASSERT( _data.contains(name) );
+ Q_ASSERT( _data[name].type()==value.type() );
+ _data[name] = value;
+}
+
+bool Score::isTheWorst() const
+{
+ Score s;
+ return score()==s.score();
+}
+
+bool Score::operator <(const Score &score)
+{
+ return internal->manager.isStrictlyLess(*this, score);
+}
+
+QDataStream &operator <<(QDataStream &s, const Score &score)
+{
+ s << (Q_UINT8)score.type();
+ s << score._data;
+ return s;
+}
+
+QDataStream &operator >>(QDataStream &s, Score &score)
+{
+ Q_UINT8 type;
+ s >> type;
+ score._type = (ScoreType)type;
+ s >> score._data;
+ return s;
+}
+
+//-----------------------------------------------------------------------------
+MultiplayerScores::MultiplayerScores()
+{}
+
+MultiplayerScores::~MultiplayerScores()
+{}
+
+void MultiplayerScores::clear()
+{
+ Score score;
+ for (uint i=0; i<_scores.size(); i++) {
+ _nbGames[i] = 0;
+ QVariant name = _scores[i].data("name");
+ _scores[i] = score;
+ _scores[i].setData("name", name);
+ _scores[i]._data["mean score"] = double(0);
+ _scores[i]._data["nb won games"] = uint(0);
+ }
+}
+
+void MultiplayerScores::setPlayerCount(uint nb)
+{
+ _nbGames.resize(nb);
+ _scores.resize(nb);
+ clear();
+}
+
+void MultiplayerScores::setName(uint i, const QString &name)
+{
+ _scores[i].setData("name", name);
+}
+
+void MultiplayerScores::addScore(uint i, const Score &score)
+{
+ QVariant name = _scores[i].data("name");
+ double mean = _scores[i].data("mean score").toDouble();
+ uint won = _scores[i].data("nb won games").toUInt();
+ _scores[i] = score;
+ _scores[i].setData("name", name);
+ _nbGames[i]++;
+ mean += (double(score.score()) - mean) / _nbGames[i];
+ _scores[i]._data["mean score"] = mean;
+ if ( score.type()==Won ) won++;
+ _scores[i]._data["nb won games"] = won;
+}
+
+void MultiplayerScores::show(QWidget *parent)
+{
+ // check consistency
+ if ( _nbGames.size()<2 ) kdWarning(11002) << "less than 2 players" << endl;
+ else {
+ bool ok = true;
+ uint nb = _nbGames[0];
+ for (uint i=1; i<_nbGames.size(); i++)
+ if ( _nbGames[i]!=nb ) ok = false;
+ if (!ok)
+ kdWarning(11002) << "players have not same number of games" << endl;
+ }
+
+ // order the players according to the number of won games
+ QValueVector<Score> ordered;
+ for (uint i=0; i<_scores.size(); i++) {
+ uint won = _scores[i].data("nb won games").toUInt();
+ double mean = _scores[i].data("mean score").toDouble();
+ QValueVector<Score>::iterator it;
+ for(it = ordered.begin(); it!=ordered.end(); ++it) {
+ uint cwon = (*it).data("nb won games").toUInt();
+ double cmean = (*it).data("mean score").toDouble();
+ if ( won<cwon || (won==cwon && mean<cmean) ) {
+ ordered.insert(it, _scores[i]);
+ break;
+ }
+ }
+ if ( it==ordered.end() ) ordered.push_back(_scores[i]);
+ }
+
+ // show the scores
+ KDialogBase dialog(KDialogBase::Plain, i18n("Multiplayers Scores"),
+ KDialogBase::Close, KDialogBase::Close,
+ parent, "show_multiplayers_score", true, true);
+ QHBoxLayout *hbox = new QHBoxLayout(dialog.plainPage(),
+ KDialog::marginHint(), KDialog::spacingHint());
+
+ QVBox *vbox = new QVBox(dialog.plainPage());
+ hbox->addWidget(vbox);
+ if ( _nbGames[0]==0 ) (void)new QLabel(i18n("No game played."), vbox);
+ else {
+ (void)new QLabel(i18n("Scores for last game:"), vbox);
+ (void)new LastMultipleScoresList(ordered, vbox);
+ }
+
+ if ( _nbGames[0]>1 ) {
+ vbox = new QVBox(dialog.plainPage());
+ hbox->addWidget(vbox);
+ (void)new QLabel(i18n("Scores for the last %1 games:")
+ .arg(_nbGames[0]), vbox);
+ (void)new TotalMultipleScoresList(ordered, vbox);
+ }
+
+ dialog.enableButtonSeparator(false);
+ dialog.exec();
+}
+
+QDataStream &operator <<(QDataStream &s, const MultiplayerScores &score)
+{
+ s << score._scores;
+ s << score._nbGames;
+ return s;
+}
+
+QDataStream &operator >>(QDataStream &s, MultiplayerScores &score)
+{
+ s >> score._scores;
+ s >> score._nbGames;
+ return s;
+}
+
+} // namespace
diff --git a/libkdegames/highscore/kexthighscore_item.h b/libkdegames/highscore/kexthighscore_item.h
new file mode 100644
index 00000000..0200fabd
--- /dev/null
+++ b/libkdegames/highscore/kexthighscore_item.h
@@ -0,0 +1,317 @@
+/*
+ This file is part of the KDE games library
+ Copyright (C) 2001-2003 Nicolas Hadacek ([email protected])
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXTHIGHSCORE_ITEM_H
+#define KEXTHIGHSCORE_ITEM_H
+
+#include <qvariant.h>
+#include <qnamespace.h>
+#include <qmap.h>
+#include <qvaluevector.h>
+#include <kdemacros.h>
+class QWidget;
+
+
+namespace KExtHighscore
+{
+
+//-----------------------------------------------------------------------------
+/**
+ * This class defines how to convert and how to display
+ * a highscore element (such as the score, the date, ...) or a player
+ * info (such as the player name, the best score, ...).
+ */
+class KDE_EXPORT Item
+{
+ public:
+ /**
+ * Possible display format.
+ * <ul>
+ * <li> @p NoFormat : no formatting (default) </li>
+ * <li> @p OneDecimal : with one decimal (only for Double) </li>
+ * <li> @p Percentage : with one decimal + % (only for Double) </li>
+ * <li> @p MinuteTime : MM:SS ie 3600 is 00:00, 1 is 59:59 and 0 is
+ * undefined (only for UInt, Int and Double) </li>
+ * <li> @p DateTime : date and time according to locale (only for
+ * DateTime) </li>
+ * </ul>
+ */
+ enum Format { NoFormat, OneDecimal, Percentage, MinuteTime,
+ DateTime };
+
+ /**
+ * Possible special value for display format.
+ * <ul>
+ * <li> @p NoSpecial : no special value ; a null DateTime is replaced by
+ * "--" (default) </li>
+ * <li> ZeroNotDefined : 0 is replaced by "--" (only for UInt, Int and
+ * Double) </li>
+ * <li> @p NegativeNotDefined : negative values are replaced by "--" (only
+ * for Int and Double) </li>
+ * <li> @p DefaultNotDefined : default value is replaced by "--" </li>
+ * <li> @p Anonymous : replace the special value ItemBase::ANONYMOUS
+ * by i18n("anonymous") (only for String) </li>
+ * </ul>
+ */
+ enum Special { NoSpecial, ZeroNotDefined, NegativeNotDefined,
+ DefaultNotDefined, Anonymous };
+
+ /**
+ * Constructor.
+ *
+ * @param def default value ; the QVariant also gives the type of data.
+ * Be sure to cast the value to the required type (for e.g. with uint).
+ * @param label the label corresponding to the item. If empty, the item
+ * is not shown.
+ * @param alignment the alignment of the item.
+ */
+ Item(const QVariant &def = QVariant::Invalid,
+ const QString &label = QString::null, int alignment = Qt::AlignRight);
+
+ virtual ~Item();
+
+ /**
+ * Set the display format.
+ * @see Format
+ */
+ void setPrettyFormat(Format format);
+
+ /**
+ * Set the special value for display.
+ * @see Special
+ */
+ void setPrettySpecial(Special special);
+
+ /**
+ * @return if the item is shown.
+ */
+ bool isVisible() const { return !_label.isEmpty(); }
+
+ /**
+ * Set the label.
+ */
+ void setLabel(const QString &label) { _label = label; }
+
+ /**
+ * @return the label.
+ */
+ QString label() const { return _label; }
+
+ /**
+ * @return the alignment.
+ */
+ int alignment() const { return _alignment; }
+
+ /**
+ * Set default value.
+ */
+ void setDefaultValue(const QVariant &value) { _default = value; }
+
+ /**
+ * @return the default value.
+ */
+ const QVariant &defaultValue() const { return _default; }
+
+ /**
+ * @return the converted value (by default the value is left
+ * unchanged). Most of the time you don't need to reimplement this method.
+ *
+ * @param i the element index ("rank" for score / "id" for player)
+ * @param value the value to convert
+ */
+ virtual QVariant read(uint i, const QVariant &value) const;
+
+ /**
+ * @return the string to be displayed. You may need to reimplement this
+ * method for special formatting (different from the standard ones).
+ *
+ * @param i the element index ("rank" for score / "id" for player)
+ * @param value the value to convert
+ */
+ virtual QString pretty(uint i, const QVariant &value) const;
+
+ private:
+ QVariant _default;
+ QString _label;
+ int _alignment;
+ Format _format;
+ Special _special;
+
+ class ItemPrivate;
+ ItemPrivate *d;
+
+ static QString timeFormat(uint);
+};
+
+//-----------------------------------------------------------------------------
+/**
+ * Possible score type.
+ * @p Won the game has been won.
+ * @p Lost the game has been lost or has been aborted.
+ * @p Draw the game is a draw.
+ */
+enum ScoreType { Won = 0, Lost = -1, Draw = -2 };
+
+/**
+ * This class contains data for a score. You should not inherit from
+ * this class but reimplement the methods in Highscores.
+ */
+class KDE_EXPORT Score
+{
+ public:
+ Score(ScoreType type = Won);
+
+ ~Score();
+
+ /**
+ * @return the game type.
+ */
+ ScoreType type() const { return _type; }
+
+ /**
+ * Set the game type.
+ */
+ void setType(ScoreType type) { _type = type; }
+
+ /**
+ * @return the data associated with the named Item.
+ */
+ const QVariant &data(const QString &name) const;
+
+ /**
+ * Set the data associated with the named Item. Note that the
+ * value should have the type of the default value of the
+ * Item.
+ */
+ void setData(const QString &name, const QVariant &value);
+
+ /**
+ * @return the score value.
+ *
+ * Equivalent to <pre>data("score").toUInt()</pre>.
+ */
+ uint score() const { return data("score").toUInt(); }
+
+ /**
+ * Set the score value.
+ *
+ * Equivalent to <pre>setData("score", score)</pre>.
+ */
+ void setScore(uint score) { setData("score", score); }
+
+ /**
+ * @return true if this is the worst possible score (ie the default
+ * argument of ScoreItem).
+ */
+ bool isTheWorst() const;
+
+ /**
+ * Comparison operator.
+ *
+ * @see Manager::isStrictlyLess
+ */
+ bool operator <(const Score &score);
+
+ private:
+ ScoreType _type;
+ QMap<QString, QVariant> _data;
+
+ class ScorePrivate;
+ ScorePrivate *d;
+
+ friend class MultiplayerScores;
+
+ friend QDataStream &operator <<(QDataStream &stream, const Score &score);
+ friend QDataStream &operator >>(QDataStream &stream, Score &score);
+};
+
+KDE_EXPORT QDataStream &operator <<(QDataStream &stream, const Score &score);
+KDE_EXPORT QDataStream &operator >>(QDataStream &stream, Score &score);
+
+/**
+ * This class is used to store and show scores for multiplayer games.
+ *
+ * Example of use:
+ * Initialize the class:
+ * <pre>
+ * KExtHighscore::MultiScore ms(2);
+ * ms.setPlayerName(0, "player 1");
+ * ms.setPlayerName(1, "player 2");
+ * </pre>
+ * At the end of each game, add the score of each players:
+ * <pre>
+ * KExtHighscore::Score score(KExtHighscore::Won);
+ * score.setScore(100);
+ * ms.addScore(0, score);
+ * score.setType(KExtHighscore::Lost);
+ * score.setScore(20);
+ * ms.addScore(1, score);
+ * </pre>
+ */
+class KDE_EXPORT MultiplayerScores
+{
+ public:
+ MultiplayerScores();
+
+ ~MultiplayerScores();
+
+ /**
+ * Set the number of players and clear the scores.
+ */
+ void setPlayerCount(uint nb);
+
+ /**
+ * Set the name of player.
+ */
+ void setName(uint player, const QString &name);
+
+ /**
+ * Add the score of player.
+ */
+ void addScore(uint player, const Score &score);
+
+ /**
+ * Clear all scores.
+ */
+ void clear();
+
+ /**
+ * Show scores.
+ */
+ void show(QWidget *parent);
+
+ private:
+ QValueVector<uint> _nbGames;
+ QValueVector<Score> _scores;
+
+ class MultiplayerScoresPrivate;
+ MultiplayerScoresPrivate *d;
+
+ friend QDataStream &operator <<(QDataStream &stream,
+ const MultiplayerScores &score);
+ friend QDataStream &operator >>(QDataStream &stream,
+ MultiplayerScores &score);
+};
+
+KDE_EXPORT QDataStream &operator <<(QDataStream &stream, const MultiplayerScores &score);
+KDE_EXPORT QDataStream &operator >>(QDataStream &stream, MultiplayerScores &score);
+
+} // namespace
+
+#endif
diff --git a/libkdegames/highscore/kexthighscore_tab.cpp b/libkdegames/highscore/kexthighscore_tab.cpp
new file mode 100644
index 00000000..3e9cbe8a
--- /dev/null
+++ b/libkdegames/highscore/kexthighscore_tab.cpp
@@ -0,0 +1,281 @@
+/*
+ This file is part of the KDE games library
+ Copyright (C) 2002 Nicolas Hadacek ([email protected])
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kexthighscore_tab.h"
+#include "kexthighscore_tab.moc"
+
+#include <qlayout.h>
+#include <qlabel.h>
+#include <qvgroupbox.h>
+#include <qgrid.h>
+#include <qheader.h>
+
+#include <kdialogbase.h>
+#include <klistview.h>
+#include <kdebug.h>
+#include <kglobal.h>
+
+#include "kexthighscore.h"
+#include "kexthighscore_internal.h"
+
+
+namespace KExtHighscore
+{
+
+//-----------------------------------------------------------------------------
+PlayersCombo::PlayersCombo(QWidget *parent, const char *name)
+ : QComboBox(parent, name)
+{
+ const PlayerInfos &p = internal->playerInfos();
+ for (uint i = 0; i<p.nbEntries(); i++)
+ insertItem(p.prettyName(i));
+ insertItem(QString("<") + i18n("all") + '>');
+ connect(this, SIGNAL(activated(int)), SLOT(activatedSlot(int)));
+}
+
+void PlayersCombo::activatedSlot(int i)
+{
+ const PlayerInfos &p = internal->playerInfos();
+ if ( i==(int)p.nbEntries() ) emit allSelected();
+ else if ( i==(int)p.nbEntries()+1 ) emit noneSelected();
+ else emit playerSelected(i);
+}
+
+void PlayersCombo::load()
+{
+ const PlayerInfos &p = internal->playerInfos();
+ for (uint i = 0; i<p.nbEntries(); i++)
+ changeItem(p.prettyName(i), i);
+}
+
+//-----------------------------------------------------------------------------
+AdditionalTab::AdditionalTab(QWidget *parent, const char *name)
+ : QWidget(parent, name)
+{
+ QVBoxLayout *top = new QVBoxLayout(this, KDialogBase::marginHint(),
+ KDialogBase::spacingHint());
+
+ QHBoxLayout *hbox = new QHBoxLayout(top);
+ QLabel *label = new QLabel(i18n("Select player:"), this);
+ hbox->addWidget(label);
+ _combo = new PlayersCombo(this);
+ connect(_combo, SIGNAL(playerSelected(uint)),
+ SLOT(playerSelected(uint)));
+ connect(_combo, SIGNAL(allSelected()), SLOT(allSelected()));
+ hbox->addWidget(_combo);
+ hbox->addStretch(1);
+}
+
+void AdditionalTab::init()
+{
+ uint id = internal->playerInfos().id();
+ _combo->setCurrentItem(id);
+ playerSelected(id);
+}
+
+void AdditionalTab::allSelected()
+{
+ display(internal->playerInfos().nbEntries());
+}
+
+QString AdditionalTab::percent(uint n, uint total, bool withBraces)
+{
+ if ( n==0 || total==0 ) return QString::null;
+ QString s = QString("%1%").arg(100.0 * n / total, 0, 'f', 1);
+ return (withBraces ? QString("(") + s + ")" : s);
+}
+
+void AdditionalTab::load()
+{
+ _combo->load();
+}
+
+
+//-----------------------------------------------------------------------------
+const char *StatisticsTab::COUNT_LABELS[Nb_Counts] = {
+ I18N_NOOP("Total:"), I18N_NOOP("Won:"), I18N_NOOP("Lost:"),
+ I18N_NOOP("Draw:")
+};
+const char *StatisticsTab::TREND_LABELS[Nb_Trends] = {
+ I18N_NOOP("Current:"), I18N_NOOP("Max won:"), I18N_NOOP("Max lost:")
+};
+
+StatisticsTab::StatisticsTab(QWidget *parent)
+ : AdditionalTab(parent, "statistics_tab")
+{
+ // construct GUI
+ QVBoxLayout *top = static_cast<QVBoxLayout *>(layout());
+
+ QHBoxLayout *hbox = new QHBoxLayout(top);
+ QVBoxLayout *vbox = new QVBoxLayout(hbox);
+ QVGroupBox *group = new QVGroupBox(i18n("Game Counts"), this);
+ vbox->addWidget(group);
+ QGrid *grid = new QGrid(3, group);
+ grid->setSpacing(KDialogBase::spacingHint());
+ for (uint k=0; k<Nb_Counts; k++) {
+ if ( Count(k)==Draw && !internal->showDrawGames ) continue;
+ (void)new QLabel(i18n(COUNT_LABELS[k]), grid);
+ _nbs[k] = new QLabel(grid);
+ _percents[k] = new QLabel(grid);
+ }
+
+ group = new QVGroupBox(i18n("Trends"), this);
+ vbox->addWidget(group);
+ grid = new QGrid(2, group);
+ grid->setSpacing(KDialogBase::spacingHint());
+ for (uint k=0; k<Nb_Trends; k++) {
+ (void)new QLabel(i18n(TREND_LABELS[k]), grid);
+ _trends[k] = new QLabel(grid);
+ }
+
+ hbox->addStretch(1);
+ top->addStretch(1);
+}
+
+void StatisticsTab::load()
+{
+ AdditionalTab::load();
+ const PlayerInfos &pi = internal->playerInfos();
+ uint nb = pi.nbEntries();
+ _data.resize(nb+1);
+ for (uint i=0; i<_data.size()-1; i++) {
+ _data[i].count[Total] = pi.item("nb games")->read(i).toUInt();
+ _data[i].count[Lost] = pi.item("nb lost games")->read(i).toUInt()
+ + pi.item("nb black marks")->read(i).toUInt(); // legacy
+ _data[i].count[Draw] = pi.item("nb draw games")->read(i).toUInt();
+ _data[i].count[Won] = _data[i].count[Total] - _data[i].count[Lost]
+ - _data[i].count[Draw];
+ _data[i].trend[CurrentTrend] =
+ pi.item("current trend")->read(i).toInt();
+ _data[i].trend[WonTrend] = pi.item("max won trend")->read(i).toUInt();
+ _data[i].trend[LostTrend] =
+ -(int)pi.item("max lost trend")->read(i).toUInt();
+ }
+
+ for (uint k=0; k<Nb_Counts; k++) _data[nb].count[k] = 0;
+ for (uint k=0; k<Nb_Trends; k++) _data[nb].trend[k] = 0;
+ for (uint i=0; i<_data.size()-1; i++) {
+ for (uint k=0; k<Nb_Counts; k++)
+ _data[nb].count[k] += _data[i].count[k];
+ for (uint k=0; k<Nb_Trends; k++)
+ _data[nb].trend[k] += _data[i].trend[k];
+ }
+ for (uint k=0; k<Nb_Trends; k++)
+ _data[nb].trend[k] /= (_data.size()-1);
+
+ init();
+}
+
+QString StatisticsTab::percent(const Data &d, Count count) const
+{
+ if ( count==Total ) return QString::null;
+ return AdditionalTab::percent(d.count[count], d.count[Total], true);
+}
+
+void StatisticsTab::display(uint i)
+{
+ const Data &d = _data[i];
+ for (uint k=0; k<Nb_Counts; k++) {
+ if ( Count(k) && !internal->showDrawGames ) continue;
+ _nbs[k]->setText(QString::number(d.count[k]));
+ _percents[k]->setText(percent(d, Count(k)));
+ }
+ for (uint k=0; k<Nb_Trends; k++) {
+ QString s;
+ if ( d.trend[k]>0 ) s = '+';
+ int prec = (i==internal->playerInfos().nbEntries() ? 1 : 0);
+ _trends[k]->setText(s + QString::number(d.trend[k], 'f', prec));
+ }
+}
+
+//-----------------------------------------------------------------------------
+HistogramTab::HistogramTab(QWidget *parent)
+ : AdditionalTab(parent, "histogram_tab")
+{
+ // construct GUI
+ QVBoxLayout *top = static_cast<QVBoxLayout *>(layout());
+
+ _list = new KListView(this);
+ _list->setSelectionMode(QListView::NoSelection);
+ _list->setItemMargin(3);
+ _list->setAllColumnsShowFocus(true);
+ _list->setSorting(-1);
+ _list->header()->setClickEnabled(false);
+ _list->header()->setMovingEnabled(false);
+ top->addWidget(_list);
+
+ _list->addColumn(i18n("From"));
+ _list->addColumn(i18n("To"));
+ _list->addColumn(i18n("Count"));
+ _list->addColumn(i18n("Percent"));
+ for (uint i=0; i<4; i++) _list->setColumnAlignment(i, AlignRight);
+ _list->addColumn(QString::null);
+
+ const Item *sitem = internal->scoreInfos().item("score")->item();
+ const PlayerInfos &pi = internal->playerInfos();
+ const QMemArray<uint> &sh = pi.histogram();
+ for (uint k=1; k<pi.histoSize(); k++) {
+ QString s1 = sitem->pretty(0, sh[k-1]);
+ QString s2;
+ if ( k==sh.size() ) s2 = "...";
+ else if ( sh[k]!=sh[k-1]+1 ) s2 = sitem->pretty(0, sh[k]);
+ (void)new KListViewItem(_list, s1, s2);
+ }
+}
+
+void HistogramTab::load()
+{
+ AdditionalTab::load();
+ const PlayerInfos &pi = internal->playerInfos();
+ uint n = pi.nbEntries();
+ uint s = pi.histoSize() - 1;
+ _counts.resize((n+1) * s);
+ _data.fill(0, n+1);
+ for (uint k=0; k<s; k++) {
+ _counts[n*s + k] = 0;
+ for (uint i=0; i<n; i++) {
+ uint nb = pi.item(pi.histoName(k+1))->read(i).toUInt();
+ _counts[i*s + k] = nb;
+ _counts[n*s + k] += nb;
+ _data[i] += nb;
+ _data[n] += nb;
+ }
+ }
+
+ init();
+}
+
+void HistogramTab::display(uint i)
+{
+ const PlayerInfos &pi = internal->playerInfos();
+ QListViewItem *item = _list->firstChild();
+ uint s = pi.histoSize() - 1;
+ for (int k=s-1; k>=0; k--) {
+ uint nb = _counts[i*s + k];
+ item->setText(2, QString::number(nb));
+ item->setText(3, percent(nb, _data[i]));
+ uint width = (_data[i]==0 ? 0 : qRound(150.0 * nb / _data[i]));
+ QPixmap pixmap(width, 10);
+ pixmap.fill(blue);
+ item->setPixmap(4, pixmap);
+ item = item->nextSibling();
+ }
+}
+
+} // namespace
diff --git a/libkdegames/highscore/kexthighscore_tab.h b/libkdegames/highscore/kexthighscore_tab.h
new file mode 100644
index 00000000..ce47a75f
--- /dev/null
+++ b/libkdegames/highscore/kexthighscore_tab.h
@@ -0,0 +1,117 @@
+/*
+ This file is part of the KDE games library
+ Copyright (C) 2002 Nicolas Hadacek ([email protected])
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#ifndef KEXTHIGHSCORE_TAB_H
+#define KEXTHIGHSCORE_TAB_H
+
+#include <qcombobox.h>
+#include <qmemarray.h>
+
+class QLabel;
+class KListView;
+
+
+namespace KExtHighscore
+{
+
+//-----------------------------------------------------------------------------
+class PlayersCombo : public QComboBox
+{
+ Q_OBJECT
+ public:
+ PlayersCombo(QWidget *parent = 0, const char *name = 0);
+
+ void load();
+
+ signals:
+ void playerSelected(uint i);
+ void allSelected();
+ void noneSelected();
+
+ private slots:
+ void activatedSlot(int i);
+};
+
+//-----------------------------------------------------------------------------
+class AdditionalTab : public QWidget
+{
+ Q_OBJECT
+ public:
+ AdditionalTab(QWidget *parent, const char *name);
+
+ virtual void load();
+
+ private slots:
+ void playerSelected(uint i) { display(i) ; }
+ void allSelected();
+
+ protected:
+ void init();
+ static QString percent(uint n, uint total, bool withBraces = false);
+ virtual void display(uint i) = 0;
+
+ private:
+ PlayersCombo *_combo;
+};
+
+//-----------------------------------------------------------------------------
+class StatisticsTab : public AdditionalTab
+{
+ Q_OBJECT
+ public:
+ StatisticsTab(QWidget *parent);
+
+ void load();
+
+ private:
+ enum Count { Total = 0, Won, Lost, Draw, Nb_Counts };
+ static const char *COUNT_LABELS[Nb_Counts];
+ enum Trend { CurrentTrend = 0, WonTrend, LostTrend, Nb_Trends };
+ static const char *TREND_LABELS[Nb_Trends];
+ struct Data {
+ uint count[Nb_Counts];
+ double trend[Nb_Trends];
+ };
+ QMemArray<Data> _data;
+ QLabel *_nbs[Nb_Counts], *_percents[Nb_Counts], *_trends[Nb_Trends];
+
+ QString percent(const Data &, Count) const;
+ void display(uint i);
+};
+
+//-----------------------------------------------------------------------------
+class HistogramTab : public AdditionalTab
+{
+ Q_OBJECT
+ public:
+ HistogramTab(QWidget *parent);
+
+ void load();
+
+ private:
+ QMemArray<uint> _counts;
+ QMemArray<uint> _data;
+ KListView *_list;
+
+ void display(uint i);
+};
+
+} // namespace
+
+#endif
diff --git a/libkdegames/highscore/kfilelock.cpp b/libkdegames/highscore/kfilelock.cpp
new file mode 100644
index 00000000..7dc83b96
--- /dev/null
+++ b/libkdegames/highscore/kfilelock.cpp
@@ -0,0 +1,88 @@
+/*
+ This file is part of the KDE games library
+ Copyright (C) 2003 Nicolas Hadacek <[email protected]>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+
+#include "kfilelock.h"
+
+#include <unistd.h>
+#include <sys/file.h>
+#include <errno.h>
+
+#include <kdebug.h>
+
+
+KFileLock::KFileLock(int fd)
+ : _fd(fd), _locked(false)
+{}
+
+int KFileLock::lock()
+{
+ kdDebug(11002) << "lock fd=" << _fd << endl;
+#ifdef F_SETLK
+# ifndef SEEK_SET
+# define SEEK_SET 0
+# endif
+ struct flock lock_data;
+ lock_data.l_type = F_WRLCK;
+ lock_data.l_whence = SEEK_SET;
+ lock_data.l_start = lock_data.l_len = 0;
+ if ( fcntl(_fd, F_SETLK, &lock_data)==-1 ) {
+ if ( errno==EAGAIN ) return -2;
+ return -1;
+ }
+#else
+# ifdef LOCK_EX
+ if ( flock (_fd, LOCK_EX|LOCK_NB)==-1 ) {
+ if ( errno==EWOULDBLOCK ) return -2;
+ return -1;
+ }
+# else
+ if ( lockf(_fd, F_TLOCK, 0)==-1 ) {
+ if ( errno==EACCES ) return -2;
+ return -1;
+ }
+# endif
+#endif
+ _locked = true;
+ return 0;
+}
+
+KFileLock::~KFileLock()
+{
+ unlock();
+}
+
+void KFileLock::unlock()
+{
+ if ( !_locked ) return;
+ kdDebug(11002) << "unlock" << endl;
+# ifdef F_SETLK
+ struct flock lock_data;
+ lock_data.l_type = F_UNLCK;
+ lock_data.l_whence = SEEK_SET;
+ lock_data.l_start = lock_data.l_len = 0;
+ (void)fcntl(_fd, F_SETLK, &lock_data);
+# else
+# ifdef F_ULOCK
+ lockf(_fd, F_ULOCK, 0);
+# else
+ flock(_fd, LOCK_UN);
+# endif
+# endif
+ _locked = false;
+}
diff --git a/libkdegames/highscore/kfilelock.h b/libkdegames/highscore/kfilelock.h
new file mode 100644
index 00000000..2e1841ba
--- /dev/null
+++ b/libkdegames/highscore/kfilelock.h
@@ -0,0 +1,53 @@
+/*
+ This file is part of the KDE games library
+ Copyright (C) 2003 Nicolas Hadacek <[email protected]>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+#ifndef KFILELOCK_H
+#define KFILELOCK_H
+
+
+class KFileLock
+{
+public:
+ KFileLock(int fd);
+
+ /** Call unlock(). */
+ ~KFileLock();
+
+ /** @return the file descriptor. */
+ int fd() const { return _fd; }
+
+ /*
+ * Lock the file.
+ * @return 0 on success, -1 on failure (no permission) and -2 if another
+ * process is currently locking the file.
+ */
+ int lock();
+
+ /** Unlock the file. */
+ void unlock();
+
+ /** @return true if we currently lock the file. */
+ bool isLocked() const { return _locked; }
+
+private:
+ int _fd;
+ bool _locked;
+};
+
+
+#endif
diff --git a/libkdegames/highscore/khighscore.cpp b/libkdegames/highscore/khighscore.cpp
new file mode 100644
index 00000000..4e1f68e5
--- /dev/null
+++ b/libkdegames/highscore/khighscore.cpp
@@ -0,0 +1,262 @@
+/*
+ This file is part of the KDE games library
+ Copyright (C) 2001 Andreas Beckermann ([email protected])
+ Copyright (C) 2003 Nicolas Hadacek <[email protected]>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/*
+ $Id$
+*/
+
+#include <config.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <sys/file.h>
+
+#include <kapplication.h>
+#include <ksimpleconfig.h>
+#include <kglobal.h>
+#include <kstdguiitem.h>
+#include <klocale.h>
+#include <kmessagebox.h>
+#include <kdebug.h>
+#include <kstaticdeleter.h>
+
+#include "khighscore.h"
+#include "kconfigrawbackend.h"
+#include "kfilelock.h"
+
+#define GROUP "KHighscore"
+
+class KHighscorePrivate
+{
+public:
+ KHighscorePrivate() {}
+
+ QString group;
+ bool global;
+};
+
+KFileLock *KHighscore::_lock = 0;
+KRawConfig *KHighscore::_config = 0;
+static KStaticDeleter<KFileLock> lockSD;
+static KStaticDeleter<KRawConfig> configSD;
+
+
+KHighscore::KHighscore(QObject* parent)
+ : QObject(parent)
+{
+ init(true);
+}
+
+KHighscore::KHighscore(bool forceLocal, QObject* parent)
+ : QObject(parent)
+{
+ init(forceLocal);
+}
+
+void KHighscore::init(bool forceLocal)
+{
+ d = new KHighscorePrivate;
+#ifdef HIGHSCORE_DIRECTORY
+ d->global = !forceLocal;
+ if ( d->global && _lock==0 )
+ kdFatal(11002) << "KHighscore::init should be called before!!" << endl;
+#else
+ d->global = false;
+ Q_UNUSED(forceLocal);
+#endif
+ readCurrentConfig();
+}
+
+bool KHighscore::isLocked() const
+{
+ return (d->global ? _lock->isLocked() : true);
+}
+
+void KHighscore::readCurrentConfig()
+{
+ if ( d->global ) _config->reparseConfiguration();
+}
+
+void KHighscore::init(const char *appname)
+{
+#ifdef HIGHSCORE_DIRECTORY
+ const QString filename = QString::fromLocal8Bit("%1/%2.scores")
+ .arg(HIGHSCORE_DIRECTORY).arg(appname);
+ int fd = open(filename.local8Bit(), O_RDWR);
+ if ( fd<0 ) kdFatal(11002) << "cannot open global highscore file \""
+ << filename << "\"" << endl;
+ lockSD.setObject(_lock, new KFileLock(fd));
+ configSD.setObject(_config, new KRawConfig(fd, true)); // read-only
+
+ // drop the effective gid
+ int gid = getgid();
+ setregid(gid, gid);
+#else
+ Q_UNUSED(appname);
+#endif
+}
+
+bool KHighscore::lockForWriting(QWidget *widget)
+{
+ if ( isLocked() ) return true;
+
+ bool first = true;
+ for (;;) {
+ kdDebug(11002) << "try locking" << endl;
+ // lock the highscore file (it should exist)
+ int result = _lock->lock();
+ bool ok = ( result==0 );
+ kdDebug(11002) << "locking system-wide highscore file res="
+ << result << " (ok=" << ok << ")" << endl;
+ if (ok) {
+ readCurrentConfig();
+ _config->setReadOnly(false);
+ return true;
+ }
+
+ if ( !first ) {
+ KGuiItem item = KStdGuiItem::cont();
+ item.setText(i18n("Retry"));
+ int res = KMessageBox::warningContinueCancel(widget, i18n("Cannot access the highscore file. Another user is probably currently writing to it."), QString::null, item, "ask_lock_global_highscore_file");
+ if ( res==KMessageBox::Cancel ) break;
+ } else sleep(1);
+ first = false;
+ }
+ return false;
+}
+
+void KHighscore::writeAndUnlock()
+{
+ if ( !d->global ) {
+ kapp->config()->sync();
+ return;
+ }
+ if ( !isLocked() ) return;
+
+ kdDebug(11002) << "unlocking" << endl;
+ _config->sync(); // write config
+ _lock->unlock();
+ _config->setReadOnly(true);
+}
+
+KHighscore::~KHighscore()
+{
+ writeAndUnlock();
+ delete d;
+}
+
+KConfig* KHighscore::config() const
+{
+ return (d->global ? _config : kapp->config());
+}
+
+void KHighscore::writeEntry(int entry, const QString& key, const QVariant& value)
+{
+ Q_ASSERT( isLocked() );
+ KConfigGroupSaver cg(config(), group());
+ QString confKey = QString("%1_%2").arg(entry).arg(key);
+ cg.config()->writeEntry(confKey, value);
+}
+
+void KHighscore::writeEntry(int entry, const QString& key, int value)
+{
+ Q_ASSERT( isLocked() );
+ KConfigGroupSaver cg(config(), group());
+ QString confKey = QString("%1_%2").arg(entry).arg(key);
+ cg.config()->writeEntry(confKey, value);
+}
+
+void KHighscore::writeEntry(int entry, const QString& key, const QString &value)
+{
+ Q_ASSERT (isLocked() );
+ KConfigGroupSaver cg(config(), group());
+ QString confKey = QString("%1_%2").arg(entry).arg(key);
+ cg.config()->writeEntry(confKey, value);
+}
+
+QVariant KHighscore::readPropertyEntry(int entry, const QString& key, const QVariant& pDefault) const
+{
+ KConfigGroupSaver cg(config(), group());
+ QString confKey = QString("%1_%2").arg(entry).arg(key);
+ return cg.config()->readPropertyEntry(confKey, pDefault);
+}
+
+QString KHighscore::readEntry(int entry, const QString& key, const QString& pDefault) const
+{
+ KConfigGroupSaver cg(config(), group());
+ QString confKey = QString("%1_%2").arg(entry).arg(key);
+ return cg.config()->readEntry(confKey, pDefault);
+}
+
+int KHighscore::readNumEntry(int entry, const QString& key, int pDefault) const
+{
+ KConfigGroupSaver cg(config(), group());
+ QString confKey = QString("%1_%2").arg(entry).arg(key);
+ return cg.config()->readNumEntry(confKey, pDefault);
+}
+
+bool KHighscore::hasEntry(int entry, const QString& key) const
+{
+ KConfigGroupSaver cg(config(), group());
+ QString confKey = QString("%1_%2").arg(entry).arg(key);
+ return cg.config()->hasKey(confKey);
+}
+
+QStringList KHighscore::readList(const QString& key, int lastEntry) const
+{
+ QStringList list;
+ for (int i = 1; hasEntry(i, key) && ((lastEntry > 0) ? (i <= lastEntry) : true); i++) {
+ list.append(readEntry(i, key));
+ }
+ return list;
+}
+
+void KHighscore::writeList(const QString& key, const QStringList& list)
+{
+ for (int unsigned i = 1; i <= list.count(); i++) {
+ writeEntry(i, key, list[i - 1]);
+ }
+}
+
+void KHighscore::setHighscoreGroup(const QString& group)
+{
+ d->group = group;
+}
+
+const QString& KHighscore::highscoreGroup() const
+{
+ return d->group;
+}
+
+QString KHighscore::group() const
+{
+ if ( highscoreGroup().isNull() )
+ return (d->global ? QString::null : GROUP);
+ return (d->global ? highscoreGroup()
+ : QString("%1_%2").arg(GROUP).arg(highscoreGroup()));
+}
+
+bool KHighscore::hasTable() const
+{ return config()->hasGroup(group()); }
+
+void KHighscore::sync()
+{
+ writeAndUnlock();
+}
+
+#include "khighscore.moc"
diff --git a/libkdegames/highscore/khighscore.h b/libkdegames/highscore/khighscore.h
new file mode 100644
index 00000000..b1e3d25f
--- /dev/null
+++ b/libkdegames/highscore/khighscore.h
@@ -0,0 +1,311 @@
+/*
+ This file is part of the KDE games library
+ Copyright (C) 2001 Andreas Beckermann ([email protected])
+ Copyright (C) 2003 Nicolas Hadacek <[email protected]>
+
+ This library is free software; you can redistribute it and/or
+ modify it under the terms of the GNU Library General Public
+ License version 2 as published by the Free Software Foundation.
+
+ This library 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
+ Library General Public License for more details.
+
+ You should have received a copy of the GNU Library General Public License
+ along with this library; see the file COPYING.LIB. If not, write to
+ the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ Boston, MA 02110-1301, USA.
+*/
+/*
+ $Id$
+*/
+#ifndef __KHIGHSCORE_H__
+#define __KHIGHSCORE_H__
+
+#include <qstring.h>
+#include <qobject.h>
+#include <kdemacros.h>
+class KConfig;
+class KFileLock;
+class KRawConfig;
+class KHighscorePrivate;
+
+/**
+ * @short Class for managing highscore tables
+ *
+ * This is the KDE class for saving and reading highscore tables. It offers the
+ * possibility for system-wide highscore tables (configure with e.g.
+ * --enable-highscore-dir=/var/games) and a theoretically unlimited number of
+ * entries.
+ *
+ * You can specify different "keys" for an entry - just like the KConfig
+ * keys. But it will be prefixed with the number of the entry. For example you
+ * will probably use something like this to save the name of the player on the
+ * top of the list (ie the winner):
+ * \code
+ * highscore->writeEntry(1, "name", myPlayer->name());
+ * \endcode
+ * Note that it doesn't really matter if you use "0" or "1" as the first entry
+ * of the list as long as your program always uses the same for the first
+ * entry. I recommend to use "1", as several convenience methods use this.
+ *
+ * You can also specify different groups using setHighscoreGroup. Just
+ * like the keys mentioned above the groups behave like groups in KConfig
+ * but are prefixed with "KHighscore_". The default group is just "KHighscore".
+ * You might use this e.g. to create different highscore tables like
+ * \code
+ * table->setHighscoreGroup("Easy");
+ * // write the highscores for level "easy" to the table
+ * writeEasyHighscores(table);
+ *
+ * table->setHighscore("Player_1");
+ * // write player specific highscores to the table
+ * writePlayerHighscores(table);
+ * \endcode
+ * As you can see above you can also use this to write the highscores of a
+ * single player, so the "best times" of a player. To write highscores for a
+ * specific player in a specific level you will have to use a more complex way:
+ * \code
+ * QString group = QString("%1_%2").arg(player).arg(level);
+ * table->setGroup(group);
+ * writeHighscore(table, player, level);
+ * \endcode
+ *
+ * Also note that you MUST NOT mark the key or the group for translation! I.e.
+ * don't use i18n() for the keys or groups! Here is the code to read the above
+ * written entry:
+ * \code
+ * QString firstName = highscore->readEntry(0, "name");
+ * \endcode
+ * Easy, what?
+ * @author Andreas Beckermann <[email protected]>
+ **/
+class KDE_EXPORT KHighscore : public QObject
+{
+ Q_OBJECT
+public:
+ /** @obsolete
+ * Constructor. The highscore file is forced to be local to support
+ * games using the old behaviour.
+ */
+ KHighscore(QObject* parent = 0);
+
+ /**
+ * Constructor.
+ *
+ * @param forceLocal if true, the local highscore file is used even
+ * when the configuration has been set to use a system-wide file. This
+ * is convenient for converting highscores from legacy applications.
+ * @param parent parent widget for this widget
+ * @since 3.2
+ */
+ KHighscore(bool forceLocal, QObject *parent);
+
+ /**
+ * Read the current state of the highscore file. Remember that when
+ * it's not locked for writing, this file can change at any time.
+ * (This method is only useful for a system-wide highscore file).
+ * @since 3.2
+ */
+ void readCurrentConfig();
+
+ /** @since 3.2
+ * This method open the system-wide highscore file using the effective
+ * group id of the game executable (which should be "games"). The
+ * effective group id is completely dropped afterwards.
+ *
+ * Note: this method should be called in main() before creating a
+ * KApplication and doing anything else (KApplication checks that the
+ * program is not suid/sgid and will exit the program for security
+ * reason if it is the case).
+ */
+ static void init(const char *appname);
+
+ /** @since 3.2
+ * Lock the system-wide highscore file for writing (does nothing and
+ * return true if the local file is used).
+ * You should perform writing without GUI interaction to avoid
+ * blocking and don't forget to unlock the file as soon as possible
+ * with writeAndUnlock().
+ *
+ * If the config file cannot be locked,
+ * the method waits for 1 second and, if it failed again, displays
+ * a message box asking for retry or cancel.
+ * @param widget used as the parent of the message box.
+ *
+ * @return false on error or if the config file is locked by another
+ * process. In such case, the config stays read-only.
+ */
+ bool lockForWriting(QWidget *widget = 0);
+
+ /**
+ * Effectively write and unlock the system-wide highscore file
+ * (@see lockForWriting).
+ * If using a local highscore file, it will sync the config.
+ * @since 3.2
+ */
+ void writeAndUnlock();
+
+ /**
+ * @return true if the highscore file is locked or if a local
+ * file is used.
+ * @since 3.2
+ */
+ bool isLocked() const;
+
+ /**
+ * Destructor.
+ * If necessary, write and unlock the highscore file.
+ */
+ ~KHighscore();
+
+ /**
+ * @param entry The number of the entry / the placing of the player
+ * @param key A key for this entry. E.g. "name" for the name of the
+ * player. Nearly the same as the usual keys in KConfig - but they
+ * are prefixed with the entry number
+ * @param value The value of this entry
+ **/
+ void writeEntry(int entry, const QString& key, const QString& value);
+
+ /**
+ * This is an overloaded member function, provided for convenience.
+ * It differs from the above function only in what argument(s) it accepts.
+ **/
+ void writeEntry(int entry, const QString& key, int value);
+
+ /**
+ * This is an overloaded member function, provided for convenience.
+ * It differs from the above function only in what argument(s) it accepts.
+ * See KConfigBase documentation for allowed QVariant::Type.
+ **/
+ void writeEntry(int entry, const QString& key, const QVariant &value);
+
+ /**
+ * Reads an entry from the highscore table.
+ * @param entry The number of the entry / the placing to be read
+ * @param key The key of the entry. E.g. "name" for the name of the
+ * player. Nearly the same as the usual keys in KConfig - but they
+ * are prefixed with the entry number
+ * @param pDefault This will be used as default value if the key+pair
+ * entry can't be found.
+ * @return The value of this entry+key pair or pDefault if the entry+key
+ * pair doesn't exist
+ **/
+ QString readEntry(int entry, const QString& key, const QString& pDefault = QString::null) const;
+
+ /**
+ * Read a numeric value.
+ * @param entry The number of the entry / the placing to be read
+ * @param key The key of the entry. E.g. "name" for the name of the
+ * player. Nearly the same as the usual keys in KConfig - but they
+ * are prefixed with the entry number
+ * @param pDefault This will be used as default value if the key+pair
+ * entry can't be found.
+ * @return The value of this entry+key pair or pDefault if the entry+key
+ * pair doesn't exist
+ **/
+ int readNumEntry(int entry, const QString& key, int pDefault = -1) const;
+
+ /**
+ * Read a QVariant entry.
+ * See KConfigBase documentation for allowed QVariant::Type.
+ *
+ * @return the value of this entry+key pair or pDefault if the entry+key
+ * pair doesn't exist or
+ */
+ QVariant readPropertyEntry(int entry, const QString &key, const QVariant &pDefault) const;
+
+ /**
+ * @return True if the highscore table conatins the entry/key pair,
+ * otherwise false
+ **/
+ bool hasEntry(int entry, const QString& key) const;
+
+ /**
+ * Reads a list of entries from the highscore table starting at 1 until
+ * lastEntry. If an entry between those numbers doesn't exist the
+ * function aborts reading even if after the missing entry is an
+ * existing one. The first entry of the list is the first placing, the
+ * last on is the last placing.
+ * @return A list of the entries of this key. You could also call
+ * readEntry(i, key) where i is from 1 to 20. Note that this function
+ * depends on "1" as the first entry!
+ * @param key The key of the entry. E.g. "name" for the name of the
+ * player. Nearly the same as the usual keys in KConfig - but they
+ * are prefixed with the entry number
+ * @param lastEntry the last entry which will be includes into the list.
+ * 1 will include a list with maximal 1 entry - 20 a list with maximal
+ * 20 entries. If lastEntry is <= 0 then rading is only stopped when when an
+ * entry does not exist.
+ **/
+ QStringList readList(const QString& key, int lastEntry = 20) const;
+
+ /**
+ * Writes a list of entries to the highscore table.
+ *
+ * The first entry is prefixed with "1". Using this method is a short
+ * way of calling writeEntry(i, key, list[i]) from i = 1 to
+ * list.count()
+ * @param key A key for the entry. E.g. "name" for the name of the
+ * player. Nearly the same as the usual keys in KConfig - but they
+ * are prefixed with the entry number
+ * @param list The list of values
+ **/
+ void writeList(const QString& key, const QStringList& list);
+
+ /**
+ * @return Whether a highscore table exists. You can use this
+ * function to indicate whether KHighscore created a highscore table
+ * before and - if not - read your old (non-KHighscore) table instead.
+ * This way you can safely read an old table and save it using
+ * KHighscore without losing any data
+ **/
+ bool hasTable() const;
+
+ /** @obsolete
+ * This does the same as writeAndUnlock().
+ */
+ void sync();
+
+ /**
+ * Set the new highscore group. The group is being prefixed with
+ * "KHighscore_" in the table.
+ * @param groupname The new groupname. E.g. use "easy" for the easy
+ * level of your game. If you use QString::null (the default) the
+ * default group is used.
+ **/
+ void setHighscoreGroup(const QString& groupname = QString::null);
+
+ /**
+ * @return The currently used group. This doesn't contain the prefix
+ * ("KHighscore_") but the same as setHighscoreGroup uses. The
+ * default is QString::null
+ **/
+ const QString& highscoreGroup() const;
+
+protected:
+ /**
+ * @return A groupname to be used in KConfig. Used internally to
+ * prefix the value from highscoreGroup() with "KHighscore_"
+ **/
+ QString group() const;
+
+ /**
+ * @return A pointer to the KConfig object to be used. This is
+ * either kapp->config() (default) or a KSimpleConfig object for
+ * a system-wide highscore file.
+ **/
+ KConfig* config() const;
+
+ void init(bool forceLocal);
+
+private:
+ KHighscorePrivate* d;
+
+ static KFileLock *_lock; // lock on system-wide highscore file
+ static KRawConfig *_config; // config for system-wide highscore file
+};
+
+#endif
diff --git a/libkdegames/highscore/kscoredialog.cpp b/libkdegames/highscore/kscoredialog.cpp
new file mode 100644
index 00000000..37155650
--- /dev/null
+++ b/libkdegames/highscore/kscoredialog.cpp
@@ -0,0 +1,411 @@
+/****************************************************************
+Copyright (c) 1998 Sandro Sigala <[email protected]>.
+Copyright (c) 2001 Waldo Bastian <[email protected]>
+All rights reserved.
+
+Permission to use, copy, modify, and distribute this software
+and its documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appear in all
+copies and that both that the copyright notice and this
+permission notice and warranty disclaimer appear in supporting
+documentation, and that the name of the author not be used in
+advertising or publicity pertaining to distribution of the
+software without specific, written prior permission.
+
+The author disclaim all warranties with regard to this
+software, including all implied warranties of merchantability
+and fitness. In no event shall the author be liable for any
+special, indirect or consequential damages or any damages
+whatsoever resulting from loss of use, data or profits, whether
+in an action of contract, negligence or other tortious action,
+arising out of or in connection with the use or performance of
+this software.
+****************************************************************/
+
+#include "config.h"
+
+#include <qlabel.h>
+#include <qlayout.h>
+#include <qlineedit.h>
+#include <qwidgetstack.h>
+#include <qtimer.h>
+#include <qevent.h>
+#include <qptrvector.h>
+
+#include <kapplication.h>
+#include <kconfig.h>
+#include <klocale.h>
+#include <kseparator.h>
+
+#include "kscoredialog.h"
+
+class KScoreDialog::KScoreDialogPrivate
+{
+public:
+ QPtrList<FieldInfo> scores;
+ QWidget *page;
+ QGridLayout *layout;
+ QLineEdit *edit;
+ QPtrVector<QWidgetStack> stack;
+ QPtrVector<QLabel> labels;
+ QLabel *commentLabel;
+ QString comment;
+ int fields;
+ int newName;
+ int latest;
+ int nrCols;
+ bool loaded;
+ QString configGroup;
+
+ QMap<int, int> col;
+ QMap<int, QString> header;
+ QMap<int, QString> key;
+ QString player;
+};
+
+
+KScoreDialog::KScoreDialog(int fields, QWidget *parent, const char *oname)
+ : KDialogBase(parent, oname, true, i18n("High Scores"), Ok, Ok, true)
+{
+ d = new KScoreDialogPrivate();
+ d->edit = 0;
+ d->fields = fields;
+ d->newName = -1;
+ d->latest = -1;
+ d->loaded = false;
+ d->nrCols = 0;
+ d->configGroup = "High Score";
+
+ d->scores.setAutoDelete(true);
+ d->header[Name] = i18n("Name");
+ d->key[Name] = "Name";
+
+ d->header[Date] = i18n("Date");
+ d->key[Date] = "Date";
+
+ d->header[Level] = i18n("Level");
+ d->key[Level] = "Level";
+
+ d->header[Score] = i18n("Score");
+ d->key[Score] = "Score";
+ d->page = makeMainWidget();
+
+ connect(this, SIGNAL(okClicked()), SLOT(slotGotName()));
+}
+
+KScoreDialog::~KScoreDialog()
+{
+ delete d;
+}
+
+void KScoreDialog::setConfigGroup(const QString &group)
+{
+ d->configGroup = group;
+ d->loaded = false;
+}
+
+void KScoreDialog::setComment(const QString &comment)
+{
+ d->comment = comment;
+}
+
+void KScoreDialog::addField(int field, const QString &header, const QString &key)
+{
+ d->fields |= field;
+ d->header[field] = header;
+ d->key[field] = key;
+}
+
+void KScoreDialog::setupDialog()
+{
+ d->nrCols = 1;
+
+ for(int field = 1; field < d->fields; field = field * 2)
+ {
+ if (d->fields & field)
+ d->col[field] = d->nrCols++;
+ }
+
+ d->layout = new QGridLayout(d->page, 15, d->nrCols, marginHint() + 20, spacingHint());
+ d->layout->addRowSpacing(4, 15);
+
+ d->commentLabel = new QLabel(d->page);
+ d->commentLabel->setAlignment(AlignVCenter | AlignHCenter);
+ d->layout->addMultiCellWidget(d->commentLabel, 1, 1, 0, d->nrCols-1);
+
+ QFont bold = font();
+ bold.setBold(true);
+
+ QLabel *label;
+ d->layout->addColSpacing(0, 50);
+ label = new QLabel(i18n("Rank"), d->page);
+ d->layout->addWidget(label, 3, 0);
+ label->setFont(bold);
+
+ for(int field = 1; field < d->fields; field = field * 2)
+ {
+ if (d->fields & field)
+ {
+ d->layout->addColSpacing(d->col[field], 50);
+
+ label = new QLabel(d->header[field], d->page);
+ d->layout->addWidget(label, 3, d->col[field], field <= Name ? AlignLeft : AlignRight);
+ label->setFont(bold);
+ }
+ }
+
+ KSeparator *sep = new KSeparator(Horizontal, d->page);
+ d->layout->addMultiCellWidget(sep, 4, 4, 0, d->nrCols-1);
+
+ d->labels.resize(d->nrCols * 10);
+ d->stack.resize(10);
+
+ QString num;
+ for (int i = 1; i <= 10; ++i) {
+ QLabel *label;
+ num.setNum(i);
+ label = new QLabel(i18n("#%1").arg(num), d->page);
+ d->labels.insert((i-1)*d->nrCols + 0, label);
+ d->layout->addWidget(label, i+4, 0);
+ if (d->fields & Name)
+ {
+ QWidgetStack *stack = new QWidgetStack(d->page);
+ d->stack.insert(i-1, stack);
+ d->layout->addWidget(stack, i+4, d->col[Name]);
+ label = new QLabel(d->page);
+ d->labels.insert((i-1)*d->nrCols + d->col[Name], label);
+ stack->addWidget(label);
+ stack->raiseWidget(label);
+ }
+ for(int field = Name * 2; field < d->fields; field = field * 2)
+ {
+ if (d->fields & field)
+ {
+ label = new QLabel(d->page);
+ d->labels.insert((i-1)*d->nrCols + d->col[field], label);
+ d->layout->addWidget(label, i+4, d->col[field], AlignRight);
+ }
+ }
+ }
+}
+
+void KScoreDialog::aboutToShow()
+{
+ if (!d->loaded)
+ loadScores();
+
+ if (!d->nrCols)
+ setupDialog();
+
+ d->commentLabel->setText(d->comment);
+ if (d->comment.isEmpty())
+ {
+ d->commentLabel->setMinimumSize(QSize(1,1));
+ d->commentLabel->hide();
+ d->layout->addRowSpacing(0, -15);
+ d->layout->addRowSpacing(2, -15);
+ }
+ else
+ {
+ d->commentLabel->setMinimumSize(d->commentLabel->sizeHint());
+ d->commentLabel->show();
+ d->layout->addRowSpacing(0, -10);
+ d->layout->addRowSpacing(2, 10);
+ }
+ d->comment = QString::null;
+
+ QFont normal = font();
+ QFont bold = normal;
+ bold.setBold(true);
+
+ QString num;
+ for (int i = 1; i <= 10; ++i) {
+ QLabel *label;
+ num.setNum(i);
+ FieldInfo *score = d->scores.at(i-1);
+ label = d->labels[(i-1)*d->nrCols + 0];
+ if (i == d->latest)
+ label->setFont(bold);
+ else
+ label->setFont(normal);
+
+ if (d->fields & Name)
+ {
+ if (d->newName == i)
+ {
+ QWidgetStack *stack = d->stack[i-1];
+ d->edit = new QLineEdit(d->player, stack);
+ d->edit->setMinimumWidth(40);
+ stack->addWidget(d->edit);
+ stack->raiseWidget(d->edit);
+ d->edit->setFocus();
+ connect(d->edit, SIGNAL(returnPressed()),
+ this, SLOT(slotGotReturn()));
+ }
+ else
+ {
+ label = d->labels[(i-1)*d->nrCols + d->col[Name]];
+ if (i == d->latest)
+ label->setFont(bold);
+ else
+ label->setFont(normal);
+ label->setText((*score)[Name]);
+ }
+
+ }
+ for(int field = Name * 2; field < d->fields; field = field * 2)
+ {
+ if (d->fields & field)
+ {
+ label = d->labels[(i-1)*d->nrCols + d->col[field]];
+ if (i == d->latest)
+ label->setFont(bold);
+ else
+ label->setFont(normal);
+ label->setText((*score)[field]);
+ }
+ }
+ }
+ d->latest = -1;
+ setFixedSize(minimumSizeHint());
+}
+
+void KScoreDialog::loadScores()
+{
+ QString key, value;
+ d->loaded = true;
+ d->scores.clear();
+ KConfigGroup config(kapp->config(), d->configGroup.utf8());
+
+ d->player = config.readEntry("LastPlayer");
+
+ QString num;
+ for (int i = 1; i <= 10; ++i) {
+ num.setNum(i);
+ FieldInfo *score = new FieldInfo();
+ for(int field = 1; field < d->fields; field = field * 2)
+ {
+ if (d->fields & field)
+ {
+ key = "Pos" + num + d->key[field];
+ (*score)[field] = config.readEntry(key, "-");
+ }
+ }
+ d->scores.append(score);
+ }
+}
+
+void KScoreDialog::saveScores()
+{
+ QString key, value;
+ KConfigGroup config(kapp->config(), d->configGroup.utf8());
+
+ config.writeEntry("LastPlayer", d->player);
+
+ QString num;
+ for (int i = 1; i <= 10; ++i) {
+ num.setNum(i);
+ FieldInfo *score = d->scores.at(i-1);
+ for(int field = 1; field < d->fields; field = field * 2)
+ {
+ if (d->fields & field)
+ {
+ key = "Pos" + num + d->key[field];
+ config.writeEntry(key, (*score)[field]);
+ }
+ }
+ }
+ kapp->config()->sync();
+}
+
+int KScoreDialog::addScore(int newScore, const FieldInfo &newInfo, bool askName)
+{
+ return addScore(newScore, newInfo, askName, false);
+}
+
+int KScoreDialog::addScore(int newScore, const FieldInfo &newInfo, bool askName, bool lessIsMore)
+{
+ if (!d->loaded)
+ loadScores();
+ FieldInfo *score = d->scores.first();
+ int i = 1;
+ for(; score; score = d->scores.next(), i++)
+ {
+ bool ok;
+ int num_score = (*score)[Score].toLong(&ok);
+ if (lessIsMore && !ok)
+ num_score = 1 << 30;
+ if (((newScore > num_score) && !lessIsMore) ||
+ ((newScore < num_score) && lessIsMore))
+ {
+ score = new FieldInfo(newInfo);
+ (*score)[Score].setNum(newScore);
+ d->scores.insert(i-1, score);
+ d->scores.remove(10);
+ d->latest = i;
+ if (askName)
+ d->newName = i;
+ else
+ saveScores();
+ if (i == 1)
+ d->comment = i18n("Excellent!\nYou have a new high score!");
+ else
+ d->comment = i18n("Well done!\nYou made it to the high score list!");
+ return i;
+ }
+ }
+ return 0;
+}
+
+void KScoreDialog::show()
+{
+ aboutToShow();
+ KDialogBase::show();
+}
+
+void KScoreDialog::slotGotReturn()
+{
+ QTimer::singleShot(0, this, SLOT(slotGotName()));
+}
+
+void KScoreDialog::slotGotName()
+{
+ if (d->newName == -1) return;
+
+ d->player = d->edit->text();
+
+ (*d->scores.at(d->newName-1))[Name] = d->player;
+ saveScores();
+
+ QFont bold = font();
+ bold.setBold(true);
+
+ QLabel *label = d->labels[(d->newName-1)*d->nrCols + d->col[Name]];
+ label->setFont(bold);
+ label->setText(d->player);
+ d->stack[(d->newName-1)]->raiseWidget(label);
+ delete d->edit;
+ d->edit = 0;
+ d->newName = -1;
+}
+
+int KScoreDialog::highScore()
+{
+ if (!d->loaded)
+ loadScores();
+
+ return (*d->scores.first())[Score].toInt();
+}
+
+void KScoreDialog::keyPressEvent( QKeyEvent *ev)
+{
+ if ((d->newName != -1) && (ev->key() == Key_Return))
+ {
+ ev->ignore();
+ return;
+ }
+ KDialogBase::keyPressEvent(ev);
+}
+
+
+#include "kscoredialog.moc"
diff --git a/libkdegames/highscore/kscoredialog.h b/libkdegames/highscore/kscoredialog.h
new file mode 100644
index 00000000..4d4a76db
--- /dev/null
+++ b/libkdegames/highscore/kscoredialog.h
@@ -0,0 +1,125 @@
+/****************************************************************
+Copyright (c) 1998 Sandro Sigala <[email protected]>.
+Copyright (c) 2001 Waldo Bastian <[email protected]>
+All rights reserved.
+
+Permission to use, copy, modify, and distribute this software
+and its documentation for any purpose and without fee is hereby
+granted, provided that the above copyright notice appear in all
+copies and that both that the copyright notice and this
+permission notice and warranty disclaimer appear in supporting
+documentation, and that the name of the author not be used in
+advertising or publicity pertaining to distribution of the
+software without specific, written prior permission.
+
+The author disclaim all warranties with regard to this
+software, including all implied warranties of merchantability
+and fitness. In no event shall the author be liable for any
+special, indirect or consequential damages or any damages
+whatsoever resulting from loss of use, data or profits, whether
+in an action of contract, negligence or other tortious action,
+arising out of or in connection with the use or performance of
+this software.
+****************************************************************/
+
+#ifndef KSCOREDIALOG_H
+#define KSCOREDIALOG_H
+
+#include <qmap.h>
+#include <qptrlist.h>
+
+#include <kdialogbase.h>
+#include <kdemacros.h>
+class QGridLayout;
+class QLineEdit;
+class QWidgetStack;
+
+/**
+ * A simple high score dialog.
+ */
+class KDE_EXPORT KScoreDialog : public KDialogBase {
+ Q_OBJECT
+
+public:
+ enum Fields { Name = 1 << 0,
+ Level = 1 << 1,
+
+ Custom1 = 1 << 10,
+ Custom2 = 1 << 11,
+ Custom3 = 1 << 12,
+
+ Date = 1 << 27,
+ Time = 1 << 28,
+ Score = 1 << 29 };
+
+ typedef QMap<int, QString> FieldInfo;
+
+ /**
+ * @param fields Which fields should be listed.
+ * @param parent passed to parent QWidget constructor
+ * @param name passed to parent QWidget constructor
+ */
+ KScoreDialog(int fields, QWidget *parent=0, const char *name=0);
+
+ ~KScoreDialog();
+
+ /**
+ * @param group to use for reading/writing highscores from/to. By default
+ * the class will use "High Score"
+ */
+ void setConfigGroup(const QString &group);
+
+ /**
+ * @param comment to add when showing high-scores.
+ * The comment is only used once.
+ */
+ void setComment(const QString &comment);
+
+ /**
+ * Define an extra FieldInfo entry.
+ * @param field Id of this field
+ * @param header Header shown in the dialog for this field
+ * @param key used to store this field with.
+ */
+ void addField(int field, const QString &header, const QString &key);
+
+ /**
+ * Adds a new score to the list.
+ *
+ * @param newScore the score of this game.
+ * @param newInfo additional info about the score.
+ * @param askName Whether to prompt for the players name.
+ * @param lessIsMore If true, the lowest score is the best score.
+ *
+ * @returns The highscore position if the score was good enough to
+ * make it into the list (1 being topscore) or 0 otherwise.
+ */
+ int addScore(int newScore, const FieldInfo &newInfo, bool askName, bool lessIsMore);
+ int addScore(int newScore, const FieldInfo &newInfo, bool askName=true);
+
+ /**
+ * Returns the current best score.
+ */
+ int highScore();
+
+ virtual void show();
+
+private slots:
+ void slotGotReturn();
+ void slotGotName();
+
+private:
+ /* read scores */
+ void loadScores();
+ void saveScores();
+
+ void aboutToShow();
+ void setupDialog();
+ void keyPressEvent( QKeyEvent *ev);
+
+private:
+ class KScoreDialogPrivate;
+ KScoreDialogPrivate *d;
+};
+
+#endif // !KSCOREDIALOG_H