• Skip to content
  • Skip to link menu
KDE API Reference
  • KDE API Reference
  • kdegames API Reference
  • KDE Home
  • Contact Us
 

libkdegames/highscore

  • sources
  • kde-4.14
  • kdegames
  • libkdegames
  • highscore
kexthighscore_internal.cpp
Go to the documentation of this file.
1 /*
2  This file is part of the KDE games library
3  Copyright (C) 2001-2004 Nicolas Hadacek (hadacek@kde.org)
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Library General Public
7  License version 2 as published by the Free Software Foundation.
8 
9  This library is distributed in the hope that it will be useful,
10  but WITHOUT ANY WARRANTY; without even the implied warranty of
11  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12  Library General Public License for more details.
13 
14  You should have received a copy of the GNU Library General Public License
15  along with this library; see the file COPYING.LIB. If not, write to
16  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
17  Boston, MA 02110-1301, USA.
18 */
19 
20 #include "kexthighscore_internal.h"
21 
22 #include <pwd.h>
23 #include <sys/types.h>
24 #include <unistd.h>
25 
26 #include <QFile>
27 #include <QLayout>
28 #include <qdom.h>
29 //Added by qt3to4:
30 #include <QTextStream>
31 #include <QVector>
32 #include <kuser.h>
33 
34 #include <kglobal.h>
35 #include <kio/netaccess.h>
36 #include <kio/job.h>
37 #include <kmessagebox.h>
38 #include <kcodecs.h>
39 #include <kdebug.h>
40 
41 #include "kexthighscore.h"
42 #include "kexthighscore_gui.h"
43 #include "kemailsettings.h"
44 
45 #include <config-highscore.h>
46 
47 namespace KExtHighscore
48 {
49 
50 //-----------------------------------------------------------------------------
51 const char ItemContainer::ANONYMOUS[] = "_";
52 const char ItemContainer::ANONYMOUS_LABEL[] = I18N_NOOP("anonymous");
53 
54 ItemContainer::ItemContainer()
55  : _item(0)
56 {}
57 
58 ItemContainer::~ItemContainer()
59 {
60  delete _item;
61 }
62 
63 void ItemContainer::setItem(Item *item)
64 {
65  delete _item;
66  _item = item;
67 }
68 
69 QString ItemContainer::entryName() const
70 {
71  if ( _subGroup.isEmpty() ) return _name;
72  return _name + QLatin1Char( '_' ) + _subGroup;
73 }
74 
75 QVariant ItemContainer::read(uint i) const
76 {
77  Q_ASSERT(_item);
78 
79  QVariant v = _item->defaultValue();
80  if ( isStored() ) {
81  internal->hsConfig().setHighscoreGroup(_group);
82  v = internal->hsConfig().readPropertyEntry(i+1, entryName(), v);
83  }
84  return _item->read(i, v);
85 }
86 
87 QString ItemContainer::pretty(uint i) const
88 {
89  Q_ASSERT(_item);
90  return _item->pretty(i, read(i));
91 }
92 
93 void ItemContainer::write(uint i, const QVariant &value) const
94 {
95  Q_ASSERT( isStored() );
96  Q_ASSERT( internal->hsConfig().isLocked() );
97  internal->hsConfig().setHighscoreGroup(_group);
98  internal->hsConfig().writeEntry(i+1, entryName(), value);
99 }
100 
101 uint ItemContainer::increment(uint i) const
102 {
103  uint v = read(i).toUInt() + 1;
104  write(i, v);
105  return v;
106 }
107 
108 //-----------------------------------------------------------------------------
109 ItemArray::ItemArray()
110  : _group(QLatin1String( "" )), _subGroup(QLatin1String( "" )) // no null groups
111 {}
112 
113 ItemArray::~ItemArray()
114 {
115  for (int i=0; i<size(); i++) delete at(i);
116 }
117 
118 int ItemArray::findIndex(const QString &name) const
119 {
120  for (int i=0; i<size(); i++)
121  if ( at(i)->name()==name ) return i;
122  return -1;
123 }
124 
125 const ItemContainer *ItemArray::item(const QString &name) const
126 {
127  int i = findIndex(name);
128  if ( i==-1 ) kError(11002) << "no item named \"" << name
129  << "\"";
130  return at(i);
131 }
132 
133 ItemContainer *ItemArray::item(const QString &name)
134 {
135  int i = findIndex(name);
136  if ( i==-1 ) kError(11002) << "no item named \"" << name
137  << "\"";
138  return at(i);
139 }
140 
141 void ItemArray::setItem(const QString &name, Item *item)
142 {
143  int i = findIndex(name);
144  if ( i==-1 ) kError(11002) << "no item named \"" << name
145  << "\"";
146  bool stored = at(i)->isStored();
147  bool canHaveSubGroup = at(i)->canHaveSubGroup();
148  _setItem(i, name, item, stored, canHaveSubGroup);
149 }
150 
151 void ItemArray::addItem(const QString &name, Item *item,
152  bool stored, bool canHaveSubGroup)
153 {
154  if ( findIndex(name)!=-1 )
155  kError(11002) << "item already exists \"" << name << "\"";
156 
157  append(new ItemContainer);
158  //at(i) = new ItemContainer;
159  _setItem(size()-1, name, item, stored, canHaveSubGroup);
160 }
161 
162 void ItemArray::_setItem(uint i, const QString &name, Item *item,
163  bool stored, bool canHaveSubGroup)
164 {
165  at(i)->setItem(item);
166  at(i)->setName(name);
167  at(i)->setGroup(stored ? _group : QString());
168  at(i)->setSubGroup(canHaveSubGroup ? _subGroup : QString());
169 }
170 
171 void ItemArray::setGroup(const QString &group)
172 {
173  Q_ASSERT( !group.isNull() );
174  _group = group;
175  for (int i=0; i<size(); i++)
176  if ( at(i)->isStored() ) at(i)->setGroup(group);
177 }
178 
179 void ItemArray::setSubGroup(const QString &subGroup)
180 {
181  Q_ASSERT( !subGroup.isNull() );
182  _subGroup = subGroup;
183  for (int i=0; i<size(); i++)
184  if ( at(i)->canHaveSubGroup() ) at(i)->setSubGroup(subGroup);
185 }
186 
187 void ItemArray::read(uint k, Score &data) const
188 {
189  for (int i=0; i<size(); i++) {
190  if ( !at(i)->isStored() ) continue;
191  data.setData(at(i)->name(), at(i)->read(k));
192  }
193 }
194 
195 void ItemArray::write(uint k, const Score &data, uint nb) const
196 {
197  for (int i=0; i<size(); i++) {
198  if ( !at(i)->isStored() ) continue;
199  for (uint j=nb-1; j>k; j--) at(i)->write(j, at(i)->read(j-1));
200  at(i)->write(k, data.data(at(i)->name()));
201  }
202 }
203 
204 void ItemArray::exportToText(QTextStream &s) const
205 {
206  for (uint k=0; k<nbEntries()+1; k++) {
207  for (int i=0; i<size(); i++) {
208  const Item *item = at(i)->item();
209  if ( item->isVisible() ) {
210  if ( i!=0 ) s << '\t';
211  if ( k==0 ) s << item->label();
212  else s << at(i)->pretty(k-1);
213  }
214  }
215  s << endl;
216  }
217 }
218 
219 //-----------------------------------------------------------------------------
220 class ScoreNameItem : public NameItem
221 {
222  public:
223  ScoreNameItem(const ScoreInfos &score, const PlayerInfos &infos)
224  : _score(score), _infos(infos) {}
225 
226  QString pretty(uint i, const QVariant &v) const {
227  uint id = _score.item(QLatin1String( "id" ))->read(i).toUInt();
228  if ( id==0 ) return NameItem::pretty(i, v);
229  return _infos.prettyName(id-1);
230  }
231 
232  private:
233  const ScoreInfos &_score;
234  const PlayerInfos &_infos;
235 };
236 
237 //-----------------------------------------------------------------------------
238 ScoreInfos::ScoreInfos(uint maxNbEntries, const PlayerInfos &infos)
239  : _maxNbEntries(maxNbEntries)
240 {
241  addItem(QLatin1String( "id" ), new Item((uint)0));
242  addItem(QLatin1String( "rank" ), new RankItem, false);
243  addItem(QLatin1String( "name" ), new ScoreNameItem(*this, infos));
244  addItem(QLatin1String( "score" ), Manager::createItem(Manager::ScoreDefault));
245  addItem(QLatin1String( "date" ), new DateItem);
246 }
247 
248 uint ScoreInfos::nbEntries() const
249 {
250  uint i = 0;
251  for (; i<_maxNbEntries; i++)
252  if ( item(QLatin1String( "score" ))->read(i)==item(QLatin1String( "score" ))->item()->defaultValue() )
253  break;
254  return i;
255 }
256 
257 //-----------------------------------------------------------------------------
258 const char *HS_ID = "player id";
259 const char *HS_REGISTERED_NAME = "registered name";
260 const char *HS_KEY = "player key";
261 const char *HS_WW_ENABLED = "ww hs enabled";
262 
263 PlayerInfos::PlayerInfos()
264 {
265  setGroup(QLatin1String( "players" ));
266 
267  // standard items
268  addItem(QLatin1String( "name" ), new NameItem);
269  Item *it = new Item((uint)0, i18n("Games Count"),Qt::AlignRight);
270  addItem(QLatin1String( "nb games" ), it, true, true);
271  it = Manager::createItem(Manager::MeanScoreDefault);
272  addItem(QLatin1String( "mean score" ), it, true, true);
273  it = Manager::createItem(Manager::BestScoreDefault);
274  addItem(QLatin1String( "best score" ), it, true, true);
275  addItem(QLatin1String( "date" ), new DateItem, true, true);
276  it = new Item(QString(), i18n("Comment"), Qt::AlignLeft);
277  addItem(QLatin1String( "comment" ), it);
278 
279  // statistics items
280  addItem(QLatin1String( "nb black marks" ), new Item((uint)0), true, true); // legacy
281  addItem(QLatin1String( "nb lost games" ), new Item((uint)0), true, true);
282  addItem(QLatin1String( "nb draw games" ), new Item((uint)0), true, true);
283  addItem(QLatin1String( "current trend" ), new Item((int)0), true, true);
284  addItem(QLatin1String( "max lost trend" ), new Item((uint)0), true, true);
285  addItem(QLatin1String( "max won trend" ), new Item((uint)0), true, true);
286 
287  QString username = KUser().loginName();
288 
289 #ifdef HIGHSCORE_DIRECTORY
290  internal->hsConfig().setHighscoreGroup("players");
291  for (uint i=0; ;i++) {
292  if ( !internal->hsConfig().hasEntry(i+1, "username") ) {
293  _newPlayer = true;
294  _id = i;
295  break;
296  }
297  if ( internal->hsConfig().readEntry(i+1, "username")==username ) {
298  _newPlayer = false;
299  _id = i;
300  return;
301  }
302  }
303 #endif
304  internal->hsConfig().lockForWriting();
305  KEMailSettings emailConfig;
306  emailConfig.setProfile(emailConfig.defaultProfileName());
307  QString name = emailConfig.getSetting(KEMailSettings::RealName);
308  if ( name.isEmpty() || isNameUsed(name) ) name = username;
309  if ( isNameUsed(name) ) name= QLatin1String(ItemContainer::ANONYMOUS);
310 #ifdef HIGHSCORE_DIRECTORY
311  internal->hsConfig().writeEntry(_id+1, "username", username);
312  item("name")->write(_id, name);
313 #endif
314 
315  ConfigGroup cg;
316  _oldLocalPlayer = cg.hasKey(HS_ID);
317  _oldLocalId = cg.readEntry(HS_ID).toUInt();
318 #ifdef HIGHSCORE_DIRECTORY
319  if (_oldLocalPlayer) { // player already exists in local config file
320  // copy player data
321  QString prefix = QString::fromLatin1( "%1_").arg(_oldLocalId+1);
322 #ifdef __GNUC__
323 #warning "kde4 port g.config()->entryMap";
324 #endif
325 #if 0
326  QMap<QString, QString> entries =
327  cg.config()->entryMap("KHighscore_players");
328  QMap<QString, QString>::const_iterator it;
329  for (it=entries.begin(); it!=entries.end(); ++it) {
330  QString key = it.key();
331  if ( key.find(prefix)==0 ) {
332  QString name = key.right(key.length()-prefix.length());
333  if ( name!="name" || !isNameUsed(it.data()) )
334  internal->hsConfig().writeEntry(_id+1, name, it.data());
335  }
336  }
337 #endif
338  }
339 #else
340  _newPlayer = !_oldLocalPlayer;
341  if (_oldLocalPlayer) _id = _oldLocalId;
342  else {
343  _id = nbEntries();
344  cg.writeEntry(HS_ID, _id);
345  item(QLatin1String( "name" ))->write(_id, name);
346  }
347 #endif
348  _bound = true;
349  internal->hsConfig().writeAndUnlock();
350 }
351 
352 void PlayerInfos::createHistoItems(const QVector<uint> &scores, bool bound)
353 {
354  Q_ASSERT( _histogram.size()==0 );
355  _bound = bound;
356  _histogram = scores;
357  for (int i=1; i<histoSize(); i++)
358  addItem(histoName(i), new Item((uint)0), true, true);
359 }
360 
361 bool PlayerInfos::isAnonymous() const
362 {
363  return ( name()==QLatin1String( ItemContainer::ANONYMOUS ) );
364 }
365 
366 uint PlayerInfos::nbEntries() const
367 {
368  internal->hsConfig().setHighscoreGroup(QLatin1String( "players" ));
369  const QStringList list = internal->hsConfig().readList(QLatin1String( "name" ), -1);
370  return list.count();
371 }
372 
373 QString PlayerInfos::key() const
374 {
375  ConfigGroup cg;
376  return cg.readEntry(HS_KEY, QString());
377 }
378 
379 bool PlayerInfos::isWWEnabled() const
380 {
381  ConfigGroup cg;
382  return cg.readEntry(HS_WW_ENABLED, false);
383 }
384 
385 QString PlayerInfos::histoName(int i) const
386 {
387  const QVector<uint> &sh = _histogram;
388  Q_ASSERT( i<sh.size() || (_bound || i==sh.size()) );
389  if ( i==sh.size() )
390  return QString::fromLatin1( "nb scores greater than %1").arg(sh[sh.size()-1]);
391  return QString::fromLatin1( "nb scores less than %1").arg(sh[i]);
392 }
393 
394 int PlayerInfos::histoSize() const
395 {
396  return _histogram.size() + (_bound ? 0 : 1);
397 }
398 
399 void PlayerInfos::submitScore(const Score &score) const
400 {
401  // update counts
402  uint nbGames = item(QLatin1String( "nb games" ))->increment(_id);
403  switch (score.type()) {
404  case Lost:
405  item(QLatin1String( "nb lost games" ))->increment(_id);
406  break;
407  case Won: break;
408  case Draw:
409  item(QLatin1String( "nb draw games" ))->increment(_id);
410  break;
411  };
412 
413  // update mean
414  if ( score.type()==Won ) {
415  uint nbWonGames = nbGames - item(QLatin1String( "nb lost games" ))->read(_id).toUInt()
416  - item(QLatin1String( "nb draw games" ))->read(_id).toUInt()
417  - item(QLatin1String( "nb black marks" ))->read(_id).toUInt(); // legacy
418  double mean = (nbWonGames==1 ? 0.0
419  : item(QLatin1String( "mean score" ))->read(_id).toDouble());
420  mean += (double(score.score()) - mean) / nbWonGames;
421  item(QLatin1String( "mean score" ))->write(_id, mean);
422  }
423 
424  // update best score
425  Score best = score; // copy optional fields (there are not taken into account here)
426  best.setScore( item(QLatin1String( "best score" ))->read(_id).toUInt() );
427  if ( best<score ) {
428  item(QLatin1String( "best score" ))->write(_id, score.score());
429  item(QLatin1String( "date" ))->write(_id, score.data(QLatin1String( "date" )).toDateTime());
430  }
431 
432  // update trends
433  int current = item(QLatin1String( "current trend" ))->read(_id).toInt();
434  switch (score.type()) {
435  case Won: {
436  if ( current<0 ) current = 0;
437  current++;
438  uint won = item(QLatin1String( "max won trend" ))->read(_id).toUInt();
439  if ( (uint)current>won ) item(QLatin1String( "max won trend" ))->write(_id, current);
440  break;
441  }
442  case Lost: {
443  if ( current>0 ) current = 0;
444  current--;
445  uint lost = item(QLatin1String( "max lost trend" ))->read(_id).toUInt();
446  uint clost = -current;
447  if ( clost>lost ) item(QLatin1String( "max lost trend" ))->write(_id, clost);
448  break;
449  }
450  case Draw:
451  current = 0;
452  break;
453  }
454  item(QLatin1String( "current trend" ))->write(_id, current);
455 
456  // update histogram
457  if ( score.type()==Won ) {
458  const QVector<uint> &sh = _histogram;
459  for (int i=1; i<histoSize(); i++)
460  if ( i==sh.size() || score.score()<sh[i] ) {
461  item(histoName(i))->increment(_id);
462  break;
463  }
464  }
465 }
466 
467 bool PlayerInfos::isNameUsed(const QString &newName) const
468 {
469  if ( newName==name() ) return false; // own name...
470  for (uint i=0; i<nbEntries(); i++)
471  if ( newName.toLower()==item(QLatin1String( "name" ))->read(i).toString().toLower() ) return true;
472  if ( newName==i18n(ItemContainer::ANONYMOUS_LABEL) ) return true;
473  return false;
474 }
475 
476 void PlayerInfos::modifyName(const QString &newName) const
477 {
478  item(QLatin1String( "name" ))->write(_id, newName);
479 }
480 
481 void PlayerInfos::modifySettings(const QString &newName,
482  const QString &comment, bool WWEnabled,
483  const QString &newKey) const
484 {
485  modifyName(newName);
486  item(QLatin1String( "comment" ))->write(_id, comment);
487  ConfigGroup cg;
488  cg.writeEntry(HS_WW_ENABLED, WWEnabled);
489  if ( !newKey.isEmpty() ) cg.writeEntry(HS_KEY, newKey);
490  if (WWEnabled) cg.writeEntry(HS_REGISTERED_NAME, newName);
491 }
492 
493 QString PlayerInfos::registeredName() const
494 {
495  ConfigGroup cg;
496  return cg.readEntry(HS_REGISTERED_NAME, QString());
497 }
498 
499 void PlayerInfos::removeKey()
500 {
501  ConfigGroup cg;
502 
503  // save old key/nickname
504  uint i = 0;
505  QString str = QLatin1String( "%1 old #%2" );
506  QString sk;
507  do {
508  i++;
509  sk = str.arg(QLatin1String( HS_KEY )).arg(i);
510  } while ( !cg.readEntry(sk, QString()).isEmpty() );
511  cg.writeEntry(sk, key());
512  cg.writeEntry(str.arg(QLatin1String( HS_REGISTERED_NAME )).arg(i),
513  registeredName());
514 
515  // clear current key/nickname
516  cg.deleteEntry(HS_KEY);
517  cg.deleteEntry(HS_REGISTERED_NAME);
518  cg.writeEntry(HS_WW_ENABLED, false);
519 }
520 
521 //-----------------------------------------------------------------------------
522 ManagerPrivate::ManagerPrivate(uint nbGameTypes, Manager &m)
523  : manager(m), showStatistics(false), showDrawGames(false),
524  trackLostGames(false), trackDrawGames(false),
525  showMode(Manager::ShowForHigherScore),
526  _first(true), _nbGameTypes(nbGameTypes), _gameType(0)
527 {}
528 
529 void ManagerPrivate::init(uint maxNbEntries)
530 {
531  _hsConfig = new KHighscore(false, 0);
532  _playerInfos = new PlayerInfos;
533  _scoreInfos = new ScoreInfos(maxNbEntries, *_playerInfos);
534 }
535 
536 ManagerPrivate::~ManagerPrivate()
537 {
538  delete _scoreInfos;
539  delete _playerInfos;
540  delete _hsConfig;
541 }
542 
543 KUrl ManagerPrivate::queryUrl(QueryType type, const QString &newName) const
544 {
545  KUrl url = serverURL;
546  QString nameItem = QLatin1String( "nickname" );
547  QString name = _playerInfos->registeredName();
548  bool withVersion = true;
549  bool key = false;
550  bool level = false;
551 
552  switch (type) {
553  case Submit:
554  url.addPath(QLatin1String( "submit.php" ));
555  level = true;
556  key = true;
557  break;
558  case Register:
559  url.addPath(QLatin1String( "register.php" ));
560  name = newName;
561  break;
562  case Change:
563  url.addPath(QLatin1String( "change.php" ));
564  key = true;
565  if ( newName!=name )
566  Manager::addToQueryURL(url, QLatin1String( "new_nickname" ), newName);
567  break;
568  case Players:
569  url.addPath(QLatin1String( "players.php" ));
570  nameItem = QLatin1String( "highlight" );
571  withVersion = false;
572  break;
573  case Scores:
574  url.addPath(QLatin1String( "highscores.php" ));
575  withVersion = false;
576  if ( _nbGameTypes>1 ) level = true;
577  break;
578  }
579 
580  if (withVersion) Manager::addToQueryURL(url, QLatin1String( "version" ), version);
581  if ( !name.isEmpty() ) Manager::addToQueryURL(url, nameItem, name);
582  if (key) Manager::addToQueryURL(url, QLatin1String( "key" ), _playerInfos->key());
583  if (level) {
584  QString label = manager.gameTypeLabel(_gameType, Manager::WW);
585  if ( !label.isEmpty() ) Manager::addToQueryURL(url, QLatin1String( "level" ), label);
586  }
587 
588  return url;
589 }
590 
591 // strings that needs to be translated (coming from the highscores server)
592 const char *DUMMY_STRINGS[] = {
593  I18N_NOOP("Undefined error."),
594  I18N_NOOP("Missing argument(s)."),
595  I18N_NOOP("Invalid argument(s)."),
596 
597  I18N_NOOP("Unable to connect to MySQL server."),
598  I18N_NOOP("Unable to select database."),
599  I18N_NOOP("Error on database query."),
600  I18N_NOOP("Error on database insert."),
601 
602  I18N_NOOP("Nickname already registered."),
603  I18N_NOOP("Nickname not registered."),
604  I18N_NOOP("Invalid key."),
605  I18N_NOOP("Invalid submit key."),
606 
607  I18N_NOOP("Invalid level."),
608  I18N_NOOP("Invalid score.")
609 };
610 
611 const char *UNABLE_TO_CONTACT =
612  I18N_NOOP("Unable to contact world-wide highscore server");
613 
614 bool ManagerPrivate::doQuery(const KUrl &url, QWidget *parent,
615  QDomNamedNodeMap *map)
616 {
617  KIO::http_update_cache(url, true, 0); // remove cache !
618 
619  QString tmpFile;
620  if ( !KIO::NetAccess::download(url, tmpFile, parent) ) {
621  QString details = i18n("Server URL: %1", url.host());
622  KMessageBox::detailedSorry(parent, i18n(UNABLE_TO_CONTACT), details);
623  return false;
624  }
625 
626  QFile file(tmpFile);
627  if ( !file.open(QIODevice::ReadOnly) ) {
628  KIO::NetAccess::removeTempFile(tmpFile);
629  QString details = i18n("Unable to open temporary file.");
630  KMessageBox::detailedSorry(parent, i18n(UNABLE_TO_CONTACT), details);
631  return false;
632  }
633 
634  QTextStream t(&file);
635  QString content = t.readAll().trimmed();
636  file.close();
637  KIO::NetAccess::removeTempFile(tmpFile);
638 
639  QDomDocument doc;
640  if ( doc.setContent(content) ) {
641  QDomElement root = doc.documentElement();
642  QDomElement element = root.firstChild().toElement();
643  if ( element.tagName()==QLatin1String( "success" ) ) {
644  if (map) *map = element.attributes();
645  return true;
646  }
647  if ( element.tagName()==QLatin1String( "error" ) ) {
648  QDomAttr attr = element.attributes().namedItem(QLatin1String( "label" )).toAttr();
649  if ( !attr.isNull() ) {
650  QString msg = i18n(attr.value().toLatin1());
651  QString caption = i18n("Message from world-wide highscores "
652  "server");
653  KMessageBox::sorry(parent, msg, caption);
654  return false;
655  }
656  }
657  }
658  QString msg = i18n("Invalid answer from world-wide highscores server.");
659  QString details = i18n("Raw message: %1", content);
660  KMessageBox::detailedSorry(parent, msg, details);
661  return false;
662 }
663 
664 bool ManagerPrivate::getFromQuery(const QDomNamedNodeMap &map,
665  const QString &name, QString &value,
666  QWidget *parent)
667 {
668  QDomAttr attr = map.namedItem(name).toAttr();
669  if ( attr.isNull() ) {
670  KMessageBox::sorry(parent,
671  i18n("Invalid answer from world-wide "
672  "highscores server (missing item: %1).", name));
673  return false;
674  }
675  value = attr.value();
676  return true;
677 }
678 
679 Score ManagerPrivate::readScore(uint i) const
680 {
681  Score score(Won);
682  _scoreInfos->read(i, score);
683  return score;
684 }
685 
686 int ManagerPrivate::rank(const Score &score) const
687 {
688  uint nb = _scoreInfos->nbEntries();
689  uint i = 0;
690  for (; i<nb; i++)
691  if ( readScore(i)<score ) break;
692  return (i<_scoreInfos->maxNbEntries() ? (int)i : -1);
693 }
694 
695 bool ManagerPrivate::modifySettings(const QString &newName,
696  const QString &comment, bool WWEnabled,
697  QWidget *widget)
698 {
699  QString newKey;
700  bool newPlayer = false;
701 
702  if (WWEnabled) {
703  newPlayer = _playerInfos->key().isEmpty()
704  || _playerInfos->registeredName().isEmpty();
705  KUrl url = queryUrl((newPlayer ? Register : Change), newName);
706  Manager::addToQueryURL(url, QLatin1String( "comment" ), comment);
707 
708  QDomNamedNodeMap map;
709  bool ok = doQuery(url, widget, &map);
710  if ( !ok || (newPlayer && !getFromQuery(map, QLatin1String( "key" ), newKey, widget)) )
711  return false;
712  }
713 
714  bool ok = _hsConfig->lockForWriting(widget); // no GUI when locking
715  if (ok) {
716  // check again name in case the config file has been changed...
717  // if it has, it is unfortunate because the WWW name is already
718  // committed but should be very rare and not really problematic
719  ok = ( !_playerInfos->isNameUsed(newName) );
720  if (ok)
721  _playerInfos->modifySettings(newName, comment, WWEnabled, newKey);
722  _hsConfig->writeAndUnlock();
723  }
724  return ok;
725 }
726 
727 void ManagerPrivate::convertToGlobal()
728 {
729  // read old highscores
730  KHighscore *tmp = _hsConfig;
731  _hsConfig = new KHighscore(true, 0);
732  QVector<Score> scores(_scoreInfos->nbEntries());
733  for (int i=0; i<scores.count(); i++)
734  scores[i] = readScore(i);
735 
736  // commit them
737  delete _hsConfig;
738  _hsConfig = tmp;
739  _hsConfig->lockForWriting();
740  for (int i=0; i<scores.count(); i++)
741  if ( scores[i].data(QLatin1String( "id" )).toUInt()==_playerInfos->oldLocalId()+1 )
742  submitLocal(scores[i]);
743  _hsConfig->writeAndUnlock();
744 }
745 
746 void ManagerPrivate::setGameType(uint type)
747 {
748  if (_first) {
749  _first = false;
750  if ( _playerInfos->isNewPlayer() ) {
751  // convert legacy highscores
752  for (uint i=0; i<_nbGameTypes; i++) {
753  setGameType(i);
754  manager.convertLegacy(i);
755  }
756 
757 #ifdef HIGHSCORE_DIRECTORY
758  if ( _playerInfos->isOldLocalPlayer() ) {
759  // convert local to global highscores
760  for (uint i=0; i<_nbGameTypes; i++) {
761  setGameType(i);
762  convertToGlobal();
763  }
764  }
765 #endif
766  }
767  }
768 
769  Q_ASSERT( type<_nbGameTypes );
770  _gameType = qMin(type, _nbGameTypes-1);
771  QString str = QLatin1String( "scores" );
772  QString lab = manager.gameTypeLabel(_gameType, Manager::Standard);
773  if ( !lab.isEmpty() ) {
774  _playerInfos->setSubGroup(lab);
775  str += QLatin1Char( '_' ) + lab;
776  }
777  _scoreInfos->setGroup(str);
778 }
779 
780 void ManagerPrivate::checkFirst()
781 {
782  if (_first) setGameType(0);
783 }
784 
785 int ManagerPrivate::submitScore(const Score &ascore,
786  QWidget *widget, bool askIfAnonymous)
787 {
788  checkFirst();
789 
790  Score score = ascore;
791  score.setData(QLatin1String( "id" ), _playerInfos->id() + 1);
792  score.setData(QLatin1String( "date" ), QDateTime::currentDateTime());
793 
794  // ask new name if anonymous and winner
795  const QLatin1String dontAskAgainName = QLatin1String( "highscore_ask_name_dialog" );
796  QString newName;
797  KMessageBox::ButtonCode dummy;
798  if ( score.type()==Won && askIfAnonymous && _playerInfos->isAnonymous()
799  && KMessageBox::shouldBeShownYesNo(dontAskAgainName, dummy) ) {
800  AskNameDialog d(widget);
801  if ( d.exec()==QDialog::Accepted ) newName = d.name();
802  if ( d.dontAskAgain() )
803  KMessageBox::saveDontShowAgainYesNo(dontAskAgainName,
804  KMessageBox::No);
805  }
806 
807  int rank = -1;
808  if ( _hsConfig->lockForWriting(widget) ) { // no GUI when locking
809  // check again new name in case the config file has been changed...
810  if ( !newName.isEmpty() && !_playerInfos->isNameUsed(newName) )
811  _playerInfos->modifyName(newName);
812 
813  // commit locally
814  _playerInfos->submitScore(score);
815  if ( score.type()==Won ) rank = submitLocal(score);
816  _hsConfig->writeAndUnlock();
817  }
818 
819  if ( _playerInfos->isWWEnabled() )
820  submitWorldWide(score, widget);
821 
822  return rank;
823 }
824 
825 int ManagerPrivate::submitLocal(const Score &score)
826 {
827  int r = rank(score);
828  if ( r!=-1 ) {
829  uint nb = _scoreInfos->nbEntries();
830  if ( nb<_scoreInfos->maxNbEntries() ) nb++;
831  _scoreInfos->write(r, score, nb);
832  }
833  return r;
834 }
835 
836 bool ManagerPrivate::submitWorldWide(const Score &score,
837  QWidget *widget) const
838 {
839  if ( score.type()==Lost && !trackLostGames ) return true;
840  if ( score.type()==Draw && !trackDrawGames ) return true;
841 
842  KUrl url = queryUrl(Submit);
843  manager.additionalQueryItems(url, score);
844  int s = (score.type()==Won ? score.score() : (int)score.type());
845  QString str = QString::number(s);
846  Manager::addToQueryURL(url, QLatin1String( "score" ), str);
847  KMD5 context(QString(_playerInfos->registeredName() + str).toLatin1());
848  Manager::addToQueryURL(url, QLatin1String( "check" ), QLatin1String( context.hexDigest() ));
849 
850  return doQuery(url, widget);
851 }
852 
853 void ManagerPrivate::exportHighscores(QTextStream &s)
854 {
855  uint tmp = _gameType;
856 
857  for (uint i=0; i<_nbGameTypes; i++) {
858  setGameType(i);
859  if ( _nbGameTypes>1 ) {
860  if ( i!=0 ) s << endl;
861  s << "--------------------------------" << endl;
862  s << "Game type: "
863  << manager.gameTypeLabel(_gameType, Manager::I18N) << endl;
864  s << endl;
865  }
866  s << "Players list:" << endl;
867  _playerInfos->exportToText(s);
868  s << endl;
869  s << "Highscores list:" << endl;
870  _scoreInfos->exportToText(s);
871  }
872 
873  setGameType(tmp);
874 }
875 
876 } // namespace
KExtHighscore::PlayerInfos
Definition: kexthighscore_internal.h:188
KHighscore
Class for managing highscore tables.
Definition: khighscore.h:82
KExtHighscore::Lost
Definition: kexthighscore_item.h:168
KExtHighscore::ItemArray::setGroup
void setGroup(const QString &group)
Definition: kexthighscore_internal.cpp:171
KExtHighscore::RankItem
Definition: kexthighscore_internal.h:49
KExtHighscore::ItemArray::setSubGroup
void setSubGroup(const QString &subGroup)
Definition: kexthighscore_internal.cpp:179
KExtHighscore::PlayerInfos::isWWEnabled
bool isWWEnabled() const
Definition: kexthighscore_internal.cpp:379
KExtHighscore::PlayerInfos::submitScore
void submitScore(const Score &) const
Definition: kexthighscore_internal.cpp:399
QWidget
KExtHighscore::ItemContainer::read
QVariant read(uint i) const
Definition: kexthighscore_internal.cpp:75
KExtHighscore::PlayerInfos::isAnonymous
bool isAnonymous() const
Definition: kexthighscore_internal.cpp:361
KExtHighscore::ItemContainer::increment
uint increment(uint i) const
Definition: kexthighscore_internal.cpp:101
KExtHighscore::ManagerPrivate::queryUrl
KUrl queryUrl(QueryType type, const QString &newName=QLatin1String("")) const
Definition: kexthighscore_internal.cpp:543
KExtHighscore::HS_KEY
const char * HS_KEY
Definition: kexthighscore_internal.cpp:260
KExtHighscore::Manager
This class manages highscores and players entries (several players can share the same highscores list...
Definition: kexthighscore.h:141
KExtHighscore::ManagerPrivate::~ManagerPrivate
~ManagerPrivate()
Definition: kexthighscore_internal.cpp:536
QVector< ItemContainer * >::append
void append(const T &value)
KExtHighscore::ItemArray::exportToText
void exportToText(QTextStream &) const
Definition: kexthighscore_internal.cpp:204
KExtHighscore::Score::score
uint score() const
Definition: kexthighscore_item.h:212
KExtHighscore::AskNameDialog::name
QString name() const
Definition: kexthighscore_gui.h:190
KHighscore::isLocked
bool isLocked() const
Definition: khighscore.cpp:83
QVariant::toDateTime
QDateTime toDateTime() const
KExtHighscore::ScoreInfos::nbEntries
uint nbEntries() const
Definition: kexthighscore_internal.cpp:248
KExtHighscore::ManagerPrivate::Players
Definition: kexthighscore_internal.h:249
KExtHighscore::ManagerPrivate::exportHighscores
void exportHighscores(QTextStream &)
Definition: kexthighscore_internal.cpp:853
QMap
KExtHighscore::ManagerPrivate::serverURL
KUrl serverURL
Definition: kexthighscore_internal.h:255
KExtHighscore::PlayerInfos::key
QString key() const
Definition: kexthighscore_internal.cpp:373
KExtHighscore::PlayerInfos::histoSize
int histoSize() const
Definition: kexthighscore_internal.cpp:394
QDomDocument::documentElement
QDomElement documentElement() const
KExtHighscore::Score::setData
void setData(const QString &name, const QVariant &value)
Set the data associated with the named Item.
Definition: kexthighscore_item.cpp:165
KExtHighscore::ItemContainer::ANONYMOUS_LABEL
static const char ANONYMOUS_LABEL[]
Definition: kexthighscore_internal.h:109
KExtHighscore::PlayerInfos::registeredName
QString registeredName() const
Definition: kexthighscore_internal.cpp:493
KExtHighscore::NameItem
Definition: kexthighscore_internal.h:60
KExtHighscore::Score::type
ScoreType type() const
Definition: kexthighscore_item.h:188
KExtHighscore::ManagerPrivate::modifySettings
bool modifySettings(const QString &newName, const QString &comment, bool WWEnabled, QWidget *widget)
Definition: kexthighscore_internal.cpp:695
KExtHighscore::Draw
Definition: kexthighscore_item.h:168
KExtHighscore::ItemArray::addItem
void addItem(const QString &name, Item *, bool stored=true, bool canHaveSubGroup=false)
Definition: kexthighscore_internal.cpp:151
QFile
KExtHighscore::Item::defaultValue
QVariant defaultValue() const
Definition: kexthighscore_item.h:128
QTextStream
KExtHighscore::Item::read
virtual QVariant read(uint i, const QVariant &value) const
Definition: kexthighscore_item.cpp:48
KExtHighscore::ItemContainer::item
const Item * item() const
Definition: kexthighscore_internal.h:96
QString::isNull
bool isNull() const
KExtHighscore::PlayerInfos::id
uint id() const
Definition: kexthighscore_internal.h:204
KExtHighscore::ManagerPrivate::readScore
Score readScore(uint i) const
Definition: kexthighscore_internal.cpp:679
KExtHighscore::Won
Definition: kexthighscore_item.h:168
KExtHighscore::ManagerPrivate::Change
Definition: kexthighscore_internal.h:249
QDomNode::toElement
QDomElement toElement() const
KHighscore::hasEntry
bool hasEntry(int entry, const QString &key) const
Definition: khighscore.cpp:216
kexthighscore_gui.h
QDomNamedNodeMap
KExtHighscore::ManagerPrivate::submitScore
int submitScore(const Score &score, QWidget *widget, bool askIfAnonymous)
Definition: kexthighscore_internal.cpp:785
KExtHighscore::PlayerInfos::removeKey
void removeKey()
Definition: kexthighscore_internal.cpp:499
KExtHighscore::internal
ManagerPrivate * internal
Definition: kexthighscore.cpp:36
KExtHighscore::AskNameDialog
Definition: kexthighscore_gui.h:184
QString::number
QString number(int n, int base)
QList::count
int count(const T &value) const
QVariant::toUInt
uint toUInt(bool *ok) const
KExtHighscore::Item
This class defines how to convert and how to display a highscore element (such as the score...
Definition: kexthighscore_item.h:39
QVariant::toInt
int toInt(bool *ok) const
KExtHighscore::PlayerInfos::histoName
QString histoName(int i) const
Definition: kexthighscore_internal.cpp:385
QDomAttr
KExtHighscore::Manager::additionalQueryItems
virtual void additionalQueryItems(KUrl &url, const Score &score) const
This method is called before submitting a score to the world-wide highscores server.
Definition: kexthighscore.h:340
KExtHighscore::Item::isVisible
bool isVisible() const
Definition: kexthighscore_item.h:103
KExtHighscore::ManagerPrivate::version
QString version
Definition: kexthighscore_internal.h:256
KExtHighscore::ManagerPrivate::ManagerPrivate
ManagerPrivate(uint nbGameTypes, Manager &manager)
Definition: kexthighscore_internal.cpp:522
KExtHighscore::Score::setScore
void setScore(uint score)
Set the score value.
Definition: kexthighscore_item.h:219
QString::isEmpty
bool isEmpty() const
KExtHighscore::ManagerPrivate::setGameType
void setGameType(uint type)
Definition: kexthighscore_internal.cpp:746
QString::trimmed
QString trimmed() const
kexthighscore_internal.h
KExtHighscore::ManagerPrivate::Register
Definition: kexthighscore_internal.h:249
KExtHighscore::Manager::createItem
static Item * createItem(ItemType type)
Create a predefined item.
Definition: kexthighscore.cpp:203
KExtHighscore::ItemContainer::ANONYMOUS
static const char ANONYMOUS[]
Definition: kexthighscore_internal.h:108
KExtHighscore::ManagerPrivate::QueryType
QueryType
Definition: kexthighscore_internal.h:249
QMap::const_iterator
KHighscore::writeAndUnlock
void writeAndUnlock()
Effectively write and unlock the system-wide highscore file (.
Definition: khighscore.cpp:147
KExtHighscore::ManagerPrivate::init
void init(uint maxNbentries)
Definition: kexthighscore_internal.cpp:529
KExtHighscore::ManagerPrivate::checkFirst
void checkFirst()
Definition: kexthighscore_internal.cpp:780
KExtHighscore::ManagerPrivate::trackLostGames
bool trackLostGames
Definition: kexthighscore_internal.h:257
KExtHighscore::ItemArray::write
void write(uint k, const Score &data, uint maxNbLines) const
Definition: kexthighscore_internal.cpp:195
KExtHighscore::Manager::gameTypeLabel
virtual QString gameTypeLabel(uint gameType, LabelType type) const
Definition: kexthighscore.cpp:261
KExtHighscore::Manager::WW
Definition: kexthighscore.h:298
QString
KExtHighscore::PlayerInfos::createHistoItems
void createHistoItems(const QVector< uint > &scores, bool bound)
Definition: kexthighscore_internal.cpp:352
KExtHighscore::PlayerInfos::name
QString name() const
Definition: kexthighscore_internal.h:196
QMap::end
iterator end()
KExtHighscore::ItemContainer::pretty
QString pretty(uint i) const
Definition: kexthighscore_internal.cpp:87
KExtHighscore::PlayerInfos::PlayerInfos
PlayerInfos()
Definition: kexthighscore_internal.cpp:263
QMap::begin
iterator begin()
KExtHighscore::Manager::addToQueryURL
static void addToQueryURL(KUrl &url, const QString &item, const QString &content)
Add an entry to the url to be submitted (.
Definition: kexthighscore.cpp:275
QStringList
QString::right
QString right(int n) const
QDomNamedNodeMap::namedItem
QDomNode namedItem(const QString &name) const
KExtHighscore::ItemContainer::ItemContainer
ItemContainer()
Definition: kexthighscore_internal.cpp:54
QString::toLower
QString toLower() const
KExtHighscore::ManagerPrivate::manager
Manager & manager
Definition: kexthighscore_internal.h:254
KExtHighscore::PlayerInfos::isOldLocalPlayer
bool isOldLocalPlayer() const
Definition: kexthighscore_internal.h:194
QLatin1Char
KExtHighscore::HS_WW_ENABLED
const char * HS_WW_ENABLED
Definition: kexthighscore_internal.cpp:261
QDomDocument
KExtHighscore::Manager::MeanScoreDefault
Definition: kexthighscore.h:250
KExtHighscore::AskNameDialog::dontAskAgain
bool dontAskAgain() const
Definition: kexthighscore_gui.h:191
QDomAttr::value
QString value() const
KExtHighscore::PlayerInfos::isNameUsed
bool isNameUsed(const QString &name) const
Definition: kexthighscore_internal.cpp:467
QDomNode::isNull
bool isNull() const
KExtHighscore::ItemContainer::isStored
bool isStored() const
Definition: kexthighscore_internal.h:103
QVector< ItemContainer * >::at
const T & at(int i) const
KExtHighscore::HS_ID
const char * HS_ID
Definition: kexthighscore_internal.cpp:258
KExtHighscore::ScoreInfos::ScoreInfos
ScoreInfos(uint maxNbEntries, const PlayerInfos &infos)
Definition: kexthighscore_internal.cpp:238
QDateTime::currentDateTime
QDateTime currentDateTime()
QDomNode::firstChild
QDomNode firstChild() const
QString::toLatin1
QByteArray toLatin1() const
KExtHighscore::PlayerInfos::modifySettings
void modifySettings(const QString &newName, const QString &comment, bool WWEnabled, const QString &newKey) const
Definition: kexthighscore_internal.cpp:481
KExtHighscore::ManagerPrivate::Submit
Definition: kexthighscore_internal.h:249
QVector< uint >
KHighscore::readEntry
QString readEntry(int entry, const QString &key, const QString &pDefault=QLatin1String("")) const
Reads an entry from the highscore table.
Definition: khighscore.cpp:202
QLatin1String
KExtHighscore::HS_REGISTERED_NAME
const char * HS_REGISTERED_NAME
Definition: kexthighscore_internal.cpp:259
KExtHighscore::PlayerInfos::oldLocalId
uint oldLocalId() const
Definition: kexthighscore_internal.h:205
KExtHighscore::DateItem
Definition: kexthighscore_internal.h:69
KExtHighscore::ItemArray::read
void read(uint k, Score &data) const
Definition: kexthighscore_internal.cpp:187
KExtHighscore::Manager::ScoreDefault
Definition: kexthighscore.h:250
KExtHighscore::ItemArray::nbEntries
virtual uint nbEntries() const =0
KExtHighscore::ItemContainer
Definition: kexthighscore_internal.h:89
KExtHighscore::Manager::BestScoreDefault
Definition: kexthighscore.h:250
KExtHighscore::ItemContainer::write
void write(uint i, const QVariant &value) const
Definition: kexthighscore_internal.cpp:93
KExtHighscore::Manager::I18N
Definition: kexthighscore.h:298
KExtHighscore::Score
This class contains data for a score.
Definition: kexthighscore_item.h:178
KExtHighscore::UNABLE_TO_CONTACT
const char * UNABLE_TO_CONTACT
Definition: kexthighscore_internal.cpp:611
KExtHighscore::Manager::Standard
Definition: kexthighscore.h:298
QDomNode::toAttr
QDomAttr toAttr() const
KExtHighscore::ItemArray::ItemArray
ItemArray()
Definition: kexthighscore_internal.cpp:109
KExtHighscore::ManagerPrivate::trackDrawGames
bool trackDrawGames
Definition: kexthighscore_internal.h:257
QString::length
int length() const
KExtHighscore::Item::pretty
virtual QString pretty(uint i, const QVariant &value) const
Definition: kexthighscore_item.cpp:110
KExtHighscore::ScoreInfos
Definition: kexthighscore_internal.h:167
QString::fromLatin1
QString fromLatin1(const char *str, int size)
KExtHighscore::ItemArray::findIndex
int findIndex(const QString &name) const
Definition: kexthighscore_internal.cpp:118
KExtHighscore::PlayerInfos::nbEntries
uint nbEntries() const
Definition: kexthighscore_internal.cpp:366
KExtHighscore::ManagerPrivate::submitLocal
int submitLocal(const Score &score)
Definition: kexthighscore_internal.cpp:825
KExtHighscore::DUMMY_STRINGS
const char * DUMMY_STRINGS[]
Definition: kexthighscore_internal.cpp:592
QVariant::toDouble
double toDouble(bool *ok) const
KExtHighscore::ConfigGroup
Definition: kexthighscore_internal.h:180
KExtHighscore::ManagerPrivate::hsConfig
KHighscore & hsConfig()
Definition: kexthighscore_internal.h:248
QDomElement::tagName
QString tagName() const
QString::find
int find(QChar c, int i, bool cs) const
kexthighscore.h
KExtHighscore::PlayerInfos::modifyName
void modifyName(const QString &newName) const
Definition: kexthighscore_internal.cpp:476
KExtHighscore::ItemContainer::setItem
void setItem(Item *item)
Definition: kexthighscore_internal.cpp:63
KExtHighscore::Manager::convertLegacy
virtual void convertLegacy(uint gameType)
This method is called once for each player (ie for each user).
Definition: kexthighscore.h:315
KExtHighscore::ItemArray::item
const ItemContainer * item(const QString &name) const
Definition: kexthighscore_internal.cpp:125
QVector< ItemContainer * >::size
int size() const
QDomElement
KHighscore::lockForWriting
bool lockForWriting(QWidget *widget=0)
Lock the system-wide highscore file for writing (does nothing and return true if the local file is us...
Definition: khighscore.cpp:119
QString::arg
QString arg(qlonglong a, int fieldWidth, int base, const QChar &fillChar) const
QVariant::toString
QString toString() const
KExtHighscore::Item::label
QString label() const
Definition: kexthighscore_item.h:113
KExtHighscore::ItemArray::setItem
void setItem(const QString &name, Item *)
Definition: kexthighscore_internal.cpp:141
KExtHighscore::ItemContainer::~ItemContainer
~ItemContainer()
Definition: kexthighscore_internal.cpp:58
KExtHighscore::Score::data
QVariant data(const QString &name) const
Definition: kexthighscore_item.cpp:159
KExtHighscore::PlayerInfos::isNewPlayer
bool isNewPlayer() const
Definition: kexthighscore_internal.h:193
KExtHighscore::ItemArray::~ItemArray
virtual ~ItemArray()
Definition: kexthighscore_internal.cpp:113
KExtHighscore::ManagerPrivate::Scores
Definition: kexthighscore_internal.h:249
QDomDocument::setContent
bool setContent(const QByteArray &data, bool namespaceProcessing, QString *errorMsg, int *errorLine, int *errorColumn)
QString::toUInt
uint toUInt(bool *ok, int base) const
QDomElement::attributes
QDomNamedNodeMap attributes() const
QVariant
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Mon Jun 22 2020 13:18:46 by doxygen 1.8.7 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.

libkdegames/highscore

Skip menu "libkdegames/highscore"
  • Main Page
  • Namespace List
  • Namespace Members
  • Alphabetical List
  • Class List
  • Class Hierarchy
  • Class Members
  • File List
  • File Members
  • Related Pages

kdegames API Reference

Skip menu "kdegames API Reference"
  • granatier
  • kapman
  • kblackbox
  • kgoldrunner
  • kigo
  • kmahjongg
  • KShisen
  • ksquares
  • libkdegames
  •   highscore
  •   libkdegamesprivate
  •     kgame
  • libkmahjongg
  • palapeli
  •   libpala

Search



Report problems with this website to our bug tracking system.
Contact the specific authors with questions and comments about the page contents.

KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal