• Skip to content
  • Skip to link menu
KDE 4.2 API Reference
  • KDE API Reference
  • API Reference
  • Sitemap
  • Contact Us
 

libkdegames/highscore

kexthighscore_internal.cpp

Go to the documentation of this file.
00001 /*
00002     This file is part of the KDE games library
00003     Copyright (C) 2001-2004 Nicolas Hadacek (hadacek@kde.org)
00004 
00005     This library is free software; you can redistribute it and/or
00006     modify it under the terms of the GNU Library General Public
00007     License version 2 as published by the Free Software Foundation.
00008 
00009     This library is distributed in the hope that it will be useful,
00010     but WITHOUT ANY WARRANTY; without even the implied warranty of
00011     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00012     Library General Public License for more details.
00013 
00014     You should have received a copy of the GNU Library General Public License
00015     along with this library; see the file COPYING.LIB.  If not, write to
00016     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00017     Boston, MA 02110-1301, USA.
00018 */
00019 
00020 #include "kexthighscore_internal.h"
00021 
00022 #include <pwd.h>
00023 #include <sys/types.h>
00024 #include <unistd.h>
00025 
00026 #include <QFile>
00027 #include <QLayout>
00028 #include <qdom.h>
00029 //Added by qt3to4:
00030 #include <QTextStream>
00031 #include <QVector>
00032 #include <kuser.h>
00033 
00034 #include <kglobal.h>
00035 #include <kio/netaccess.h>
00036 #include <kio/job.h>
00037 #include <kmessagebox.h>
00038 #include <kcodecs.h>
00039 #include <kdebug.h>
00040 
00041 #include "kexthighscore.h"
00042 #include "kexthighscore_gui.h"
00043 #include "kemailsettings.h"
00044 
00045 #include <config-highscore.h>
00046 
00047 namespace KExtHighscore
00048 {
00049 
00050 //-----------------------------------------------------------------------------
00051 const char ItemContainer::ANONYMOUS[] = "_";
00052 const char ItemContainer::ANONYMOUS_LABEL[] = I18N_NOOP("anonymous");
00053 
00054 ItemContainer::ItemContainer()
00055     : _item(0)
00056 {}
00057 
00058 ItemContainer::~ItemContainer()
00059 {
00060     delete _item;
00061 }
00062 
00063 void ItemContainer::setItem(Item *item)
00064 {
00065     delete _item;
00066     _item = item;
00067 }
00068 
00069 QString ItemContainer::entryName() const
00070 {
00071     if ( _subGroup.isEmpty() ) return _name;
00072     return _name + '_' + _subGroup;
00073 }
00074 
00075 QVariant ItemContainer::read(uint i) const
00076 {
00077     Q_ASSERT(_item);
00078 
00079     QVariant v = _item->defaultValue();
00080     if ( isStored() ) {
00081         internal->hsConfig().setHighscoreGroup(_group);
00082         v = internal->hsConfig().readPropertyEntry(i+1, entryName(), v);
00083     }
00084     return _item->read(i, v);
00085 }
00086 
00087 QString ItemContainer::pretty(uint i) const
00088 {
00089     Q_ASSERT(_item);
00090     return _item->pretty(i, read(i));
00091 }
00092 
00093 void ItemContainer::write(uint i, const QVariant &value) const
00094 {
00095     Q_ASSERT( isStored() );
00096     Q_ASSERT( internal->hsConfig().isLocked() );
00097     internal->hsConfig().setHighscoreGroup(_group);
00098     internal->hsConfig().writeEntry(i+1, entryName(), value);
00099 }
00100 
00101 uint ItemContainer::increment(uint i) const
00102 {
00103     uint v = read(i).toUInt() + 1;
00104     write(i, v);
00105     return v;
00106 }
00107 
00108 //-----------------------------------------------------------------------------
00109 ItemArray::ItemArray()
00110     : _group(""), _subGroup("") // no null groups
00111 {}
00112 
00113 ItemArray::~ItemArray()
00114 {
00115     for (int i=0; i<size(); i++) delete at(i);
00116 }
00117 
00118 int ItemArray::findIndex(const QString &name) const
00119 {
00120     for (int i=0; i<size(); i++)
00121         if ( at(i)->name()==name ) return i;
00122     return -1;
00123 }
00124 
00125 const ItemContainer *ItemArray::item(const QString &name) const
00126 {
00127     int i = findIndex(name);
00128     if ( i==-1 ) kError(11002) << "no item named \"" << name
00129                                 << "\"";
00130     return at(i);
00131 }
00132 
00133 ItemContainer *ItemArray::item(const QString &name)
00134 {
00135     int i = findIndex(name);
00136     if ( i==-1 ) kError(11002) << "no item named \"" << name
00137                                 << "\"";
00138     return at(i);
00139 }
00140 
00141 void ItemArray::setItem(const QString &name, Item *item)
00142 {
00143     int i = findIndex(name);
00144     if ( i==-1 ) kError(11002) << "no item named \"" << name
00145                                 << "\"";
00146     bool stored = at(i)->isStored();
00147     bool canHaveSubGroup = at(i)->canHaveSubGroup();
00148     _setItem(i, name, item, stored, canHaveSubGroup);
00149 }
00150 
00151 void ItemArray::addItem(const QString &name, Item *item,
00152                         bool stored, bool canHaveSubGroup)
00153 {
00154     if ( findIndex(name)!=-1 )
00155         kError(11002) << "item already exists \"" << name << "\"";
00156 
00157     append(new ItemContainer);
00158     //at(i) = new ItemContainer;
00159     _setItem(size()-1, name, item, stored, canHaveSubGroup);
00160 }
00161 
00162 void ItemArray::_setItem(uint i, const QString &name, Item *item,
00163                          bool stored, bool canHaveSubGroup)
00164 {
00165     at(i)->setItem(item);
00166     at(i)->setName(name);
00167     at(i)->setGroup(stored ? _group : QString());
00168     at(i)->setSubGroup(canHaveSubGroup ? _subGroup : QString());
00169 }
00170 
00171 void ItemArray::setGroup(const QString &group)
00172 {
00173     Q_ASSERT( !group.isNull() );
00174     _group = group;
00175     for (int i=0; i<size(); i++)
00176         if ( at(i)->isStored() ) at(i)->setGroup(group);
00177 }
00178 
00179 void ItemArray::setSubGroup(const QString &subGroup)
00180 {
00181     Q_ASSERT( !subGroup.isNull() );
00182     _subGroup = subGroup;
00183     for (int i=0; i<size(); i++)
00184         if ( at(i)->canHaveSubGroup() ) at(i)->setSubGroup(subGroup);
00185 }
00186 
00187 void ItemArray::read(uint k, Score &data) const
00188 {
00189     for (int i=0; i<size(); i++) {
00190         if ( !at(i)->isStored() ) continue;
00191         data.setData(at(i)->name(), at(i)->read(k));
00192     }
00193 }
00194 
00195 void ItemArray::write(uint k, const Score &data, uint nb) const
00196 {
00197     for (int i=0; i<size(); i++) {
00198         if ( !at(i)->isStored() ) continue;
00199         for (uint j=nb-1; j>k; j--)  at(i)->write(j, at(i)->read(j-1));
00200         at(i)->write(k, data.data(at(i)->name()));
00201     }
00202 }
00203 
00204 void ItemArray::exportToText(QTextStream &s) const
00205 {
00206     for (uint k=0; k<nbEntries()+1; k++) {
00207         for (int i=0; i<size(); i++) {
00208             const Item *item = at(i)->item();
00209             if ( item->isVisible() ) {
00210                 if ( i!=0 ) s << '\t';
00211                 if ( k==0 ) s << item->label();
00212                 else s << at(i)->pretty(k-1);
00213             }
00214         }
00215         s << endl;
00216     }
00217 }
00218 
00219 //-----------------------------------------------------------------------------
00220 class ScoreNameItem : public NameItem
00221 {
00222  public:
00223     ScoreNameItem(const ScoreInfos &score, const PlayerInfos &infos)
00224         : _score(score), _infos(infos) {}
00225 
00226     QString pretty(uint i, const QVariant &v) const {
00227         uint id = _score.item("id")->read(i).toUInt();
00228         if ( id==0 ) return NameItem::pretty(i, v);
00229         return _infos.prettyName(id-1);
00230     }
00231 
00232  private:
00233     const ScoreInfos  &_score;
00234     const PlayerInfos &_infos;
00235 };
00236 
00237 //-----------------------------------------------------------------------------
00238 ScoreInfos::ScoreInfos(uint maxNbEntries, const PlayerInfos &infos)
00239     : _maxNbEntries(maxNbEntries)
00240 {
00241     addItem("id", new Item((uint)0));
00242     addItem("rank", new RankItem, false);
00243     addItem("name", new ScoreNameItem(*this, infos));
00244     addItem("score", Manager::createItem(Manager::ScoreDefault));
00245     addItem("date", new DateItem);
00246 }
00247 
00248 uint ScoreInfos::nbEntries() const
00249 {
00250     uint i = 0;
00251     for (; i<_maxNbEntries; i++)
00252         if ( item("score")->read(i)==item("score")->item()->defaultValue() )
00253             break;
00254     return i;
00255 }
00256 
00257 //-----------------------------------------------------------------------------
00258 const char *HS_ID              = "player id";
00259 const char *HS_REGISTERED_NAME = "registered name";
00260 const char *HS_KEY             = "player key";
00261 const char *HS_WW_ENABLED      = "ww hs enabled";
00262 
00263 PlayerInfos::PlayerInfos()
00264 {
00265     setGroup("players");
00266 
00267     // standard items
00268     addItem("name", new NameItem);
00269     Item *it = new Item((uint)0, i18n("Games Count"),Qt::AlignRight);
00270     addItem("nb games", it, true, true);
00271     it = Manager::createItem(Manager::MeanScoreDefault);
00272     addItem("mean score", it, true, true);
00273     it = Manager::createItem(Manager::BestScoreDefault);
00274     addItem("best score", it, true, true);
00275     addItem("date", new DateItem, true, true);
00276     it = new Item(QString(), i18n("Comment"), Qt::AlignLeft);
00277     addItem("comment", it);
00278 
00279     // statistics items
00280     addItem("nb black marks", new Item((uint)0), true, true); // legacy
00281     addItem("nb lost games", new Item((uint)0), true, true);
00282     addItem("nb draw games", new Item((uint)0), true, true);
00283     addItem("current trend", new Item((int)0), true, true);
00284     addItem("max lost trend", new Item((uint)0), true, true);
00285     addItem("max won trend", new Item((uint)0), true, true);
00286 
00287     QString username = KUser().loginName();
00288  
00289 #ifdef HIGHSCORE_DIRECTORY
00290     internal->hsConfig().setHighscoreGroup("players");
00291     for (uint i=0; ;i++) {
00292         if ( !internal->hsConfig().hasEntry(i+1, "username") ) {
00293             _newPlayer = true;
00294             _id = i;
00295             break;
00296         }
00297         if ( internal->hsConfig().readEntry(i+1, "username")==username ) {
00298             _newPlayer = false;
00299             _id = i;
00300             return;
00301         }
00302     }
00303 #endif
00304     internal->hsConfig().lockForWriting();
00305     KEMailSettings emailConfig;
00306     emailConfig.setProfile(emailConfig.defaultProfileName());
00307     QString name = emailConfig.getSetting(KEMailSettings::RealName);
00308     if ( name.isEmpty() || isNameUsed(name) ) name = username;
00309     if ( isNameUsed(name) ) name= QString(ItemContainer::ANONYMOUS);
00310 #ifdef HIGHSCORE_DIRECTORY
00311     internal->hsConfig().writeEntry(_id+1, "username", username);
00312     item("name")->write(_id, name);
00313 #endif
00314 
00315     ConfigGroup cg;
00316     _oldLocalPlayer = cg.hasKey(HS_ID);
00317     _oldLocalId = cg.readEntry(HS_ID).toUInt();
00318 #ifdef HIGHSCORE_DIRECTORY
00319     if (_oldLocalPlayer) { // player already exists in local config file
00320         // copy player data
00321         QString prefix = QString("%1_").arg(_oldLocalId+1);
00322 #ifdef __GNUC__
00323 #warning "kde4 port g.config()->entryMap";
00324 #endif
00325 #if 0
00326         QMap<QString, QString> entries =
00327             cg.config()->entryMap("KHighscore_players");
00328         QMap<QString, QString>::const_iterator it;
00329         for (it=entries.begin(); it!=entries.end(); ++it) {
00330             QString key = it.key();
00331             if ( key.find(prefix)==0 ) {
00332                 QString name = key.right(key.length()-prefix.length());
00333                 if ( name!="name" || !isNameUsed(it.data()) )
00334                     internal->hsConfig().writeEntry(_id+1, name, it.data());
00335             }
00336         }
00337 #endif
00338     }    
00339 #else
00340     _newPlayer = !_oldLocalPlayer;
00341     if (_oldLocalPlayer) _id = _oldLocalId;
00342     else {
00343         _id = nbEntries();
00344         cg.writeEntry(HS_ID, _id);
00345         item("name")->write(_id, name);
00346     }
00347 #endif
00348     _bound = true;
00349     internal->hsConfig().writeAndUnlock();
00350 }
00351 
00352 void PlayerInfos::createHistoItems(const QVector<uint> &scores, bool bound)
00353 {
00354     Q_ASSERT( _histogram.size()==0 );
00355     _bound = bound;
00356     _histogram = scores;
00357     for (int i=1; i<histoSize(); i++)
00358         addItem(histoName(i), new Item((uint)0), true, true);
00359 }
00360 
00361 bool PlayerInfos::isAnonymous() const
00362 {
00363     return ( name()==ItemContainer::ANONYMOUS );
00364 }
00365 
00366 uint PlayerInfos::nbEntries() const
00367 {
00368     internal->hsConfig().setHighscoreGroup("players");
00369     QStringList list = internal->hsConfig().readList("name", -1);
00370     return list.count();
00371 }
00372 
00373 QString PlayerInfos::key() const
00374 {
00375     ConfigGroup cg;
00376     return cg.readEntry(HS_KEY, QString());
00377 }
00378 
00379 bool PlayerInfos::isWWEnabled() const
00380 {
00381     ConfigGroup cg;
00382     return cg.readEntry(HS_WW_ENABLED, false);
00383 }
00384 
00385 QString PlayerInfos::histoName(int i) const
00386 {
00387     const QVector<uint> &sh = _histogram;
00388     Q_ASSERT( i<sh.size() || (_bound || i==sh.size()) );
00389     if ( i==sh.size() )
00390         return QString("nb scores greater than %1").arg(sh[sh.size()-1]);
00391     return QString("nb scores less than %1").arg(sh[i]);
00392 }
00393 
00394 int PlayerInfos::histoSize() const
00395 {
00396      return _histogram.size() + (_bound ? 0 : 1);
00397 }
00398 
00399 void PlayerInfos::submitScore(const Score &score) const
00400 {
00401     // update counts
00402     uint nbGames = item("nb games")->increment(_id);
00403     switch (score.type()) {
00404     case Lost:
00405         item("nb lost games")->increment(_id);
00406         break;
00407     case Won: break;
00408     case Draw:
00409         item("nb draw games")->increment(_id);
00410         break;
00411     };
00412 
00413     // update mean
00414     if ( score.type()==Won ) {
00415         uint nbWonGames = nbGames - item("nb lost games")->read(_id).toUInt()
00416                         - item("nb draw games")->read(_id).toUInt()
00417                         - item("nb black marks")->read(_id).toUInt(); // legacy
00418         double mean = (nbWonGames==1 ? 0.0
00419                        : item("mean score")->read(_id).toDouble());
00420         mean += (double(score.score()) - mean) / nbWonGames;
00421         item("mean score")->write(_id, mean);
00422     }
00423 
00424     // update best score
00425     Score best = score; // copy optional fields (there are not taken into account here)
00426     best.setScore( item("best score")->read(_id).toUInt() );
00427     if ( best<score ) {
00428         item("best score")->write(_id, score.score());
00429         item("date")->write(_id, score.data("date").toDateTime());
00430     }
00431 
00432     // update trends
00433     int current = item("current trend")->read(_id).toInt();
00434     switch (score.type()) {
00435     case Won: {
00436         if ( current<0 ) current = 0;
00437         current++;
00438         uint won = item("max won trend")->read(_id).toUInt();
00439         if ( (uint)current>won ) item("max won trend")->write(_id, current);
00440         break;
00441     }
00442     case Lost: {
00443         if ( current>0 ) current = 0;
00444         current--;
00445         uint lost = item("max lost trend")->read(_id).toUInt();
00446         uint clost = -current;
00447         if ( clost>lost ) item("max lost trend")->write(_id, clost);
00448         break;
00449     }
00450     case Draw:
00451         current = 0;
00452         break;
00453     }
00454     item("current trend")->write(_id, current);
00455 
00456     // update histogram
00457     if ( score.type()==Won ) {
00458         const QVector<uint> &sh = _histogram;
00459         for (int i=1; i<histoSize(); i++)
00460             if ( i==sh.size() || score.score()<sh[i] ) {
00461                 item(histoName(i))->increment(_id);
00462                 break;
00463             }
00464     }
00465 }
00466 
00467 bool PlayerInfos::isNameUsed(const QString &newName) const
00468 {
00469     if ( newName==name() ) return false; // own name...
00470     for (uint i=0; i<nbEntries(); i++)
00471         if ( newName.toLower()==item("name")->read(i).toString().toLower() ) return true;
00472     if ( newName==i18n(ItemContainer::ANONYMOUS_LABEL) ) return true;
00473     return false;
00474 }
00475 
00476 void PlayerInfos::modifyName(const QString &newName) const
00477 {
00478     item("name")->write(_id, newName);
00479 }
00480 
00481 void PlayerInfos::modifySettings(const QString &newName,
00482                                  const QString &comment, bool WWEnabled,
00483                                  const QString &newKey) const
00484 {
00485     modifyName(newName);
00486     item("comment")->write(_id, comment);
00487     ConfigGroup cg;
00488     cg.writeEntry(HS_WW_ENABLED, WWEnabled);
00489     if ( !newKey.isEmpty() ) cg.writeEntry(HS_KEY, newKey);
00490     if (WWEnabled) cg.writeEntry(HS_REGISTERED_NAME, newName);
00491 }
00492 
00493 QString PlayerInfos::registeredName() const
00494 {
00495     ConfigGroup cg;
00496     return cg.readEntry(HS_REGISTERED_NAME, QString());
00497 }
00498 
00499 void PlayerInfos::removeKey()
00500 {
00501     ConfigGroup cg;
00502 
00503     // save old key/nickname
00504     uint i = 0;
00505     QString str = "%1 old #%2";
00506     QString sk;
00507     do {
00508         i++;
00509         sk = str.arg(HS_KEY).arg(i);
00510     } while ( !cg.readEntry(sk, QString()).isEmpty() );
00511     cg.writeEntry(sk, key());
00512     cg.writeEntry(str.arg(HS_REGISTERED_NAME).arg(i),
00513                             registeredName());
00514 
00515     // clear current key/nickname
00516     cg.deleteEntry(HS_KEY);
00517     cg.deleteEntry(HS_REGISTERED_NAME);
00518     cg.writeEntry(HS_WW_ENABLED, false);
00519 }
00520 
00521 //-----------------------------------------------------------------------------
00522 ManagerPrivate::ManagerPrivate(uint nbGameTypes, Manager &m)
00523     : manager(m), showStatistics(false), showDrawGames(false),
00524       trackLostGames(false), trackDrawGames(false), 
00525       showMode(Manager::ShowForHigherScore),
00526       _first(true), _nbGameTypes(nbGameTypes), _gameType(0)
00527 {}
00528 
00529 void ManagerPrivate::init(uint maxNbEntries)
00530 {
00531     _hsConfig = new KHighscore(false, 0);
00532     _playerInfos = new PlayerInfos;
00533     _scoreInfos = new ScoreInfos(maxNbEntries, *_playerInfos);
00534 }
00535 
00536 ManagerPrivate::~ManagerPrivate()
00537 {
00538     delete _scoreInfos;
00539     delete _playerInfos;
00540     delete _hsConfig;
00541 }
00542 
00543 KUrl ManagerPrivate::queryUrl(QueryType type, const QString &newName) const
00544 {
00545     KUrl url = serverURL;
00546     QString nameItem = "nickname";
00547     QString name = _playerInfos->registeredName();
00548     bool withVersion = true;
00549     bool key = false;
00550     bool level = false;
00551 
00552     switch (type) {
00553         case Submit:
00554             url.addPath("submit.php");
00555             level = true;
00556             key = true;
00557             break;
00558         case Register:
00559             url.addPath("register.php");
00560             name = newName;
00561             break;
00562         case Change:
00563             url.addPath("change.php");
00564             key = true;
00565             if ( newName!=name )
00566                 Manager::addToQueryURL(url, "new_nickname", newName);
00567             break;
00568         case Players:
00569             url.addPath("players.php");
00570             nameItem = "highlight";
00571             withVersion = false;
00572             break;
00573         case Scores:
00574             url.addPath("highscores.php");
00575             withVersion = false;
00576             if ( _nbGameTypes>1 ) level = true;
00577             break;
00578     }
00579 
00580     if (withVersion) Manager::addToQueryURL(url, "version", version);
00581     if ( !name.isEmpty() ) Manager::addToQueryURL(url, nameItem, name);
00582     if (key) Manager::addToQueryURL(url, "key", _playerInfos->key());
00583     if (level) {
00584         QString label = manager.gameTypeLabel(_gameType, Manager::WW);
00585         if ( !label.isEmpty() ) Manager::addToQueryURL(url, "level", label);
00586     }
00587 
00588     return url;
00589 }
00590 
00591 // strings that needs to be translated (coming from the highscores server)
00592 const char *DUMMY_STRINGS[] = {
00593     I18N_NOOP("Undefined error."),
00594     I18N_NOOP("Missing argument(s)."),
00595     I18N_NOOP("Invalid argument(s)."),
00596 
00597     I18N_NOOP("Unable to connect to MySQL server."),
00598     I18N_NOOP("Unable to select database."),
00599     I18N_NOOP("Error on database query."),
00600     I18N_NOOP("Error on database insert."),
00601 
00602     I18N_NOOP("Nickname already registered."),
00603     I18N_NOOP("Nickname not registered."),
00604     I18N_NOOP("Invalid key."),
00605     I18N_NOOP("Invalid submit key."),
00606 
00607     I18N_NOOP("Invalid level."),
00608     I18N_NOOP("Invalid score.")
00609 };
00610 
00611 const char *UNABLE_TO_CONTACT =
00612     I18N_NOOP("Unable to contact world-wide highscore server");
00613 
00614 bool ManagerPrivate::doQuery(const KUrl &url, QWidget *parent,
00615                                 QDomNamedNodeMap *map)
00616 {
00617     KIO::http_update_cache(url, true, 0); // remove cache !
00618 
00619     QString tmpFile;
00620     if ( !KIO::NetAccess::download(url, tmpFile, parent) ) {
00621         QString details = i18n("Server URL: %1", url.host());
00622         KMessageBox::detailedSorry(parent, i18n(UNABLE_TO_CONTACT), details);
00623         return false;
00624     }
00625 
00626     QFile file(tmpFile);
00627     if ( !file.open(QIODevice::ReadOnly) ) {
00628         KIO::NetAccess::removeTempFile(tmpFile);
00629         QString details = i18n("Unable to open temporary file.");
00630         KMessageBox::detailedSorry(parent, i18n(UNABLE_TO_CONTACT), details);
00631         return false;
00632     }
00633 
00634     QTextStream t(&file);
00635     QString content = t.readAll().trimmed();
00636     file.close();
00637     KIO::NetAccess::removeTempFile(tmpFile);
00638 
00639     QDomDocument doc;
00640     if ( doc.setContent(content) ) {
00641         QDomElement root = doc.documentElement();
00642         QDomElement element = root.firstChild().toElement();
00643         if ( element.tagName()=="success" ) {
00644             if (map) *map = element.attributes();
00645             return true;
00646         }
00647         if ( element.tagName()=="error" ) {
00648             QDomAttr attr = element.attributes().namedItem("label").toAttr();
00649             if ( !attr.isNull() ) {
00650                 QString msg = i18n(attr.value().toLatin1());
00651                 QString caption = i18n("Message from world-wide highscores "
00652                                        "server");
00653                 KMessageBox::sorry(parent, msg, caption);
00654                 return false;
00655             }
00656         }
00657     }
00658     QString msg = i18n("Invalid answer from world-wide highscores server.");
00659     QString details = i18n("Raw message: %1", content);
00660     KMessageBox::detailedSorry(parent, msg, details);
00661     return false;
00662 }
00663 
00664 bool ManagerPrivate::getFromQuery(const QDomNamedNodeMap &map,
00665                                   const QString &name, QString &value,
00666                                   QWidget *parent)
00667 {
00668     QDomAttr attr = map.namedItem(name).toAttr();
00669     if ( attr.isNull() ) {
00670         KMessageBox::sorry(parent,
00671                i18n("Invalid answer from world-wide "
00672                     "highscores server (missing item: %1).", name));
00673         return false;
00674     }
00675     value = attr.value();
00676     return true;
00677 }
00678 
00679 Score ManagerPrivate::readScore(uint i) const
00680 {
00681     Score score(Won);
00682     _scoreInfos->read(i, score);
00683     return score;
00684 }
00685 
00686 int ManagerPrivate::rank(const Score &score) const
00687 {
00688     uint nb = _scoreInfos->nbEntries();
00689     uint i = 0;
00690     for (; i<nb; i++)
00691         if ( readScore(i)<score ) break;
00692     return (i<_scoreInfos->maxNbEntries() ? (int)i : -1);
00693 }
00694 
00695 bool ManagerPrivate::modifySettings(const QString &newName,
00696                                     const QString &comment, bool WWEnabled,
00697                                     QWidget *widget)
00698 {
00699     QString newKey;
00700     bool newPlayer = false;
00701 
00702     if (WWEnabled) {
00703         newPlayer = _playerInfos->key().isEmpty()
00704                     || _playerInfos->registeredName().isEmpty();
00705         KUrl url = queryUrl((newPlayer ? Register : Change), newName);
00706         Manager::addToQueryURL(url, "comment", comment);
00707 
00708         QDomNamedNodeMap map;
00709         bool ok = doQuery(url, widget, &map);
00710         if ( !ok || (newPlayer && !getFromQuery(map, "key", newKey, widget)) )
00711             return false;
00712     }
00713 
00714     bool ok = _hsConfig->lockForWriting(widget); // no GUI when locking
00715     if (ok) {
00716         // check again name in case the config file has been changed...
00717         // if it has, it is unfortunate because the WWW name is already
00718         // committed but should be very rare and not really problematic
00719         ok = ( !_playerInfos->isNameUsed(newName) );
00720         if (ok)
00721             _playerInfos->modifySettings(newName, comment, WWEnabled, newKey);
00722         _hsConfig->writeAndUnlock();
00723     }
00724     return ok;
00725 }
00726 
00727 void ManagerPrivate::convertToGlobal()
00728 {
00729     // read old highscores
00730     KHighscore *tmp = _hsConfig;
00731     _hsConfig = new KHighscore(true, 0);
00732     QVector<Score> scores(_scoreInfos->nbEntries());
00733     for (int i=0; i<scores.count(); i++)
00734         scores[i] = readScore(i);
00735 
00736     // commit them
00737     delete _hsConfig;
00738     _hsConfig = tmp;
00739     _hsConfig->lockForWriting();
00740     for (int i=0; i<scores.count(); i++)
00741         if ( scores[i].data("id").toUInt()==_playerInfos->oldLocalId()+1 )
00742             submitLocal(scores[i]);
00743     _hsConfig->writeAndUnlock();
00744 }
00745 
00746 void ManagerPrivate::setGameType(uint type)
00747 {
00748     if (_first) {
00749         _first = false;
00750         if ( _playerInfos->isNewPlayer() ) {
00751             // convert legacy highscores
00752             for (uint i=0; i<_nbGameTypes; i++) {
00753                 setGameType(i);
00754                 manager.convertLegacy(i);
00755             }
00756 
00757 #ifdef HIGHSCORE_DIRECTORY
00758             if ( _playerInfos->isOldLocalPlayer() ) {
00759                 // convert local to global highscores
00760                 for (uint i=0; i<_nbGameTypes; i++) {
00761                     setGameType(i);
00762                     convertToGlobal();
00763                 }
00764             }
00765 #endif
00766         }
00767     }
00768 
00769     Q_ASSERT( type<_nbGameTypes );
00770     _gameType = qMin(type, _nbGameTypes-1);
00771     QString str = "scores";
00772     QString lab = manager.gameTypeLabel(_gameType, Manager::Standard);
00773     if ( !lab.isEmpty() ) {
00774         _playerInfos->setSubGroup(lab);
00775         str += '_' + lab;
00776     }
00777     _scoreInfos->setGroup(str);
00778 }
00779 
00780 void ManagerPrivate::checkFirst()
00781 {
00782     if (_first) setGameType(0);
00783 }
00784 
00785 int ManagerPrivate::submitScore(const Score &ascore,
00786                                 QWidget *widget, bool askIfAnonymous)
00787 {
00788     checkFirst();
00789 
00790     Score score = ascore;
00791     score.setData("id", _playerInfos->id() + 1);
00792     score.setData("date", QDateTime::currentDateTime());
00793 
00794     // ask new name if anonymous and winner
00795     const char *dontAskAgainName = "highscore_ask_name_dialog";
00796     QString newName;
00797     KMessageBox::ButtonCode dummy;
00798     if ( score.type()==Won && askIfAnonymous && _playerInfos->isAnonymous()
00799      && KMessageBox::shouldBeShownYesNo(dontAskAgainName, dummy) ) {
00800          AskNameDialog d(widget);
00801          if ( d.exec()==QDialog::Accepted ) newName = d.name();
00802          if ( d.dontAskAgain() )
00803              KMessageBox::saveDontShowAgainYesNo(dontAskAgainName,
00804                                                  KMessageBox::No);
00805     }
00806 
00807     int rank = -1;
00808     if ( _hsConfig->lockForWriting(widget) ) { // no GUI when locking
00809         // check again new name in case the config file has been changed...
00810         if ( !newName.isEmpty() && !_playerInfos->isNameUsed(newName) )
00811              _playerInfos->modifyName(newName);
00812 
00813         // commit locally
00814         _playerInfos->