diff --git a/CMakeLists.txt b/CMakeLists.txt index e988dde..d40fb61 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,107 +1,110 @@ project(kreversi) cmake_minimum_required (VERSION 3.5 FATAL_ERROR) set (QT_MIN_VERSION "5.7.0") set (KF5_MIN_VERSION "5.30.0") find_package(ECM ${KF5_MIN_VERSION} REQUIRED NO_MODULE) set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR}) find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Widgets Qml Quick QuickWidgets Svg Test) find_package(KF5 ${KF5_MIN_VERSION} REQUIRED COMPONENTS Config ConfigWidgets CoreAddons Crash DBusAddons Declarative I18n IconThemes KIO XmlGui WidgetsAddons ) find_package(KF5DocTools ${KF5_MIN_VERSION}) find_package(KF5KDEGames 4.9.0 REQUIRED) include(FeatureSummary) include(ECMAddAppIcon) include(ECMInstallIcons) include(KDEInstallDirs) include(KDECompilerSettings NO_POLICY_SCOPE) include(KDECMakeSettings) add_definitions( -DQT_USE_FAST_CONCATENATION -DQT_USE_FAST_OPERATOR_PLUS -DQT_NO_CAST_FROM_ASCII -DQT_NO_CAST_TO_ASCII # -DQT_NO_CAST_FROM_BYTEARRAY -DQT_NO_URL_CAST_FROM_STRING -DQT_USE_QSTRINGBUILDER ) if (${KF5Config_VERSION} STRGREATER "5.56.0") add_definitions(-DQT_NO_FOREACH) MESSAGE(STATUS "compile without foreach") endif() +if (EXISTS "${CMAKE_SOURCE_DIR}/.git") + add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x060000) +endif() add_subdirectory(pics) add_subdirectory(sounds) add_subdirectory(icons) if (KF5DocTools_FOUND) add_subdirectory(doc) endif() ########### next target ############## set(kreversi_SRCS commondefs.cpp colorscheme.cpp kreversigame.cpp kreversiview.cpp kreversiplayer.cpp kreversihumanplayer.cpp kreversicomputerplayer.cpp startgamedialog.cpp Engine.cpp highscores.cpp kexthighscore.cpp kexthighscore_gui.cpp kexthighscore_internal.cpp kexthighscore_item.cpp kexthighscore_tab.cpp mainwindow.cpp main.cpp ) qt5_add_resources(kreversi_SRCS kreversi.qrc) ki18n_wrap_ui(kreversi_SRCS startgamedialog.ui) kconfig_add_kcfg_files(kreversi_SRCS preferences.kcfgc) add_executable(kreversi ${kreversi_SRCS}) target_link_libraries(kreversi Qt5::Svg KF5::ConfigCore KF5::ConfigGui KF5::CoreAddons KF5::Crash KF5::DBusAddons KF5::Declarative KF5::IconThemes KF5::KIOCore KF5::KIOFileWidgets KF5::WidgetsAddons KF5::XmlGui KF5KDEGames ) install(TARGETS kreversi ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) ########### install files ############### install(PROGRAMS org.kde.kreversi.desktop DESTINATION ${KDE_INSTALL_APPDIR}) install(DIRECTORY qml DESTINATION ${KDE_INSTALL_DATADIR}/kreversi) install(FILES org.kde.kreversi.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) feature_summary(WHAT ALL INCLUDE_QUIET_PACKAGES FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/highscores.cpp b/highscores.cpp index 0ab3c7f..c2a92e9 100644 --- a/highscores.cpp +++ b/highscores.cpp @@ -1,107 +1,107 @@ /* * Copyright (c) 2004 Nicolas HADACEK (hadacek@kde.org) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "highscores.h" #include #include #include #include #include #include namespace KExtHighscore { ExtManager::ExtManager() : Manager(7) { setShowMode(NeverShow); setShowStatistics(true); setShowDrawGamesStatistic(true); const uint RANGE[6] = { 0, 32, 40, 48, 56, 64 }; QVector s; s.resize(6); - qCopy(RANGE, RANGE + 6, s.begin()); + std::copy(RANGE, RANGE+6, s.begin()); setScoreHistogram(s, ScoreBound); QList< const KgDifficultyLevel * > diffList = Kg::difficulty()->levels(); for (int i = 0; i < diffList.size(); i++) m_typeLabels << diffList.at(i)->title(); } QString ExtManager::gameTypeLabel(uint gameType, LabelType type) const { switch (type) { case Standard: return QString::number(gameType); case I18N: return m_typeLabels.at(gameType); case Icon: // FIXME dimsuz: implement break; case WW: break; } return QString(); } // FIXME dimsuz: is this still needed? /* void ExtManager::convertLegacy(uint gameType) { // Since there is no information about the skill level // in the legacy highscore list, consider they are // for beginner skill ... if ( gameType!=0 ) return; KConfigGroup cg(KSharedConfig::openConfig(), "High Score"); for (uint i = 1; i <= 10; i++) { QString key = "Pos" + QString::number(i); QString name = cg.readEntry(key + "Name", QString()); if ( name.isEmpty() ) name = i18n("anonymous"); uint score = cg.readEntry(key + "NumChips", (uint)0); if ( score==0 ) continue; QString sdate = cg.readEntry(key + "Date", QString()); QDateTime date = QDateTime::fromString(sdate); Score s(Won); s.setScore(score); s.setData("name", name); if ( date.isValid() ) s.setData("date", date); submitLegacyScore(s); } } */ } // Namespace KExtHighscore diff --git a/kexthighscore_internal.cpp b/kexthighscore_internal.cpp index c041614..1bd57b2 100644 --- a/kexthighscore_internal.cpp +++ b/kexthighscore_internal.cpp @@ -1,889 +1,889 @@ /* This file is part of the KDE games library Copyright (C) 2001-2004 Nicolas Hadacek (hadacek@kde.org) 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 #include #include #include #include #include #include #include #include #include #include #include #include #include "kexthighscore.h" #include "kexthighscore_gui.h" #include "kemailsettings.h" // TODO Decide if want to support // a build time HIGHSCORE_DIRECTORY or not // #include 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 + QLatin1Char( '_' ) + _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(QLatin1String( "" )), _subGroup(QLatin1String( "" )) // no null groups {} ItemArray::~ItemArray() { for (int i=0; iname()==name ) return i; return -1; } const ItemContainer *ItemArray::item(const QString &name) const { QLoggingCategory::setFilterRules(QStringLiteral("games.highscore.debug = true")); int i = findIndex(name); if ( i==-1 ) qCCritical(GAMES_EXTHIGHSCORE) << "no item named \"" << name << "\""; return at(i); } ItemContainer *ItemArray::item(const QString &name) { QLoggingCategory::setFilterRules(QStringLiteral("games.highscore.debug = true")); int i = findIndex(name); if ( i==-1 ) qCCritical(GAMES_EXTHIGHSCORE) << "no item named \"" << name << "\""; return at(i); } void ItemArray::setItem(const QString &name, Item *item) { QLoggingCategory::setFilterRules(QStringLiteral("games.highscore.debug = true")); int i = findIndex(name); if ( i==-1 ) qCCritical(GAMES_EXTHIGHSCORE) << "no item named \"" << name << "\""; 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) { QLoggingCategory::setFilterRules(QStringLiteral("games.highscore.debug = true")); if ( findIndex(name)!=-1 ) qCCritical(GAMES_EXTHIGHSCORE) << "item already exists \"" << name << "\""; append(new ItemContainer); //at(i) = new ItemContainer; _setItem(size()-1, 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()); at(i)->setSubGroup(canHaveSubGroup ? _subGroup : QString()); } void ItemArray::setGroup(const QString &group) { Q_ASSERT( !group.isNull() ); _group = group; for (int i=0; iisStored() ) at(i)->setGroup(group); } void ItemArray::setSubGroup(const QString &subGroup) { Q_ASSERT( !subGroup.isNull() ); _subGroup = subGroup; for (int i=0; icanHaveSubGroup() ) at(i)->setSubGroup(subGroup); } void ItemArray::read(uint k, Score &data) const { for (int i=0; iisStored() ) continue; data.setData(at(i)->name(), at(i)->read(k)); } } void ItemArray::write(uint k, const Score &data, uint nb) const { for (int i=0; iisStored() ) 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; kitem(); 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 override { uint id = _score.item(QStringLiteral( "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(QStringLiteral( "id" ), new Item((uint)0)); addItem(QStringLiteral( "rank" ), new RankItem, false); addItem(QStringLiteral( "name" ), new ScoreNameItem(*this, infos)); addItem(QStringLiteral( "score" ), Manager::createItem(Manager::ScoreDefault)); addItem(QStringLiteral( "date" ), new DateItem); } uint ScoreInfos::nbEntries() const { uint i = 0; for (; i<_maxNbEntries; i++) if ( item(QStringLiteral( "score" ))->read(i)==item(QStringLiteral( "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(QStringLiteral( "players" )); // standard items addItem(QStringLiteral( "name" ), new NameItem); Item *it = new Item((uint)0, i18n("Games Count"),Qt::AlignRight); addItem(QStringLiteral( "nb games" ), it, true, true); it = Manager::createItem(Manager::MeanScoreDefault); addItem(QStringLiteral( "mean score" ), it, true, true); it = Manager::createItem(Manager::BestScoreDefault); addItem(QStringLiteral( "best score" ), it, true, true); addItem(QStringLiteral( "date" ), new DateItem, true, true); it = new Item(QString(), i18n("Comment"), Qt::AlignLeft); addItem(QStringLiteral( "comment" ), it); // statistics items addItem(QStringLiteral( "nb black marks" ), new Item((uint)0), true, true); // legacy addItem(QStringLiteral( "nb lost games" ), new Item((uint)0), true, true); addItem(QStringLiteral( "nb draw games" ), new Item((uint)0), true, true); addItem(QStringLiteral( "current trend" ), new Item((int)0), true, true); addItem(QStringLiteral( "max lost trend" ), new Item((uint)0), true, true); addItem(QStringLiteral( "max won trend" ), new Item((uint)0), true, true); QString username = KUser().loginName(); #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= QLatin1String(ItemContainer::ANONYMOUS); #ifdef HIGHSCORE_DIRECTORY internal->hsConfig().writeEntry(_id+1, "username", username); item("name")->write(_id, name); #endif ConfigGroup cg; _oldLocalPlayer = cg.hasKey(HS_ID); _oldLocalId = cg.readEntry(HS_ID).toUInt(); #ifdef HIGHSCORE_DIRECTORY if (_oldLocalPlayer) { // player already exists in local config file // copy player data QString prefix = QString::fromLatin1( "%1_").arg(_oldLocalId+1); #ifdef __GNUC__ #warning "kde4 port g.config()->entryMap"; #endif #if 0 QMap entries = cg.config()->entryMap("KHighscore_players"); QMap::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()); } } #endif } #else _newPlayer = !_oldLocalPlayer; if (_oldLocalPlayer) _id = _oldLocalId; else { _id = nbEntries(); cg.writeEntry(HS_ID, _id); item(QStringLiteral( "name" ))->write(_id, name); } #endif _bound = true; internal->hsConfig().writeAndUnlock(); } void PlayerInfos::createHistoItems(const QVector &scores, bool bound) { Q_ASSERT( _histogram.size()==0 ); _bound = bound; _histogram = scores; for (int i=1; ihsConfig().setHighscoreGroup(QStringLiteral( "players" )); const QStringList list = internal->hsConfig().readList(QStringLiteral( "name" ), -1); return list.count(); } QString PlayerInfos::key() const { ConfigGroup cg; return cg.readEntry(HS_KEY, QString()); } bool PlayerInfos::isWWEnabled() const { ConfigGroup cg; return cg.readEntry(HS_WW_ENABLED, false); } QString PlayerInfos::histoName(int i) const { const QVector &sh = _histogram; Q_ASSERT( iincrement(_id); switch (score.type()) { case Lost: item(QStringLiteral( "nb lost games" ))->increment(_id); break; case Won: break; case Draw: item(QStringLiteral( "nb draw games" ))->increment(_id); break; }; // update mean if ( score.type()==Won ) { uint nbWonGames = nbGames - item(QStringLiteral( "nb lost games" ))->read(_id).toUInt() - item(QStringLiteral( "nb draw games" ))->read(_id).toUInt() - item(QStringLiteral( "nb black marks" ))->read(_id).toUInt(); // legacy double mean = (nbWonGames==1 ? 0.0 : item(QStringLiteral( "mean score" ))->read(_id).toDouble()); mean += (double(score.score()) - mean) / nbWonGames; item(QStringLiteral( "mean score" ))->write(_id, mean); } // update best score Score best = score; // copy optional fields (there are not taken into account here) best.setScore( item(QStringLiteral( "best score" ))->read(_id).toUInt() ); if ( bestwrite(_id, score.score()); item(QStringLiteral( "date" ))->write(_id, score.data(QStringLiteral( "date" )).toDateTime()); } // update trends int current = item(QStringLiteral( "current trend" ))->read(_id).toInt(); switch (score.type()) { case Won: { if ( current<0 ) current = 0; current++; uint won = item(QStringLiteral( "max won trend" ))->read(_id).toUInt(); if ( (uint)current>won ) item(QStringLiteral( "max won trend" ))->write(_id, current); break; } case Lost: { if ( current>0 ) current = 0; current--; uint lost = item(QStringLiteral( "max lost trend" ))->read(_id).toUInt(); uint clost = -current; if ( clost>lost ) item(QStringLiteral( "max lost trend" ))->write(_id, clost); break; } case Draw: current = 0; break; } item(QStringLiteral( "current trend" ))->write(_id, current); // update histogram if ( score.type()==Won ) { const QVector &sh = _histogram; for (int i=1; iincrement(_id); break; } } } bool PlayerInfos::isNameUsed(const QString &newName) const { if ( newName==name() ) return false; // own name... for (uint i=0; iread(i).toString().toLower() ) return true; if ( newName==i18n(ItemContainer::ANONYMOUS_LABEL) ) return true; return false; } void PlayerInfos::modifyName(const QString &newName) const { item(QStringLiteral( "name" ))->write(_id, newName); } void PlayerInfos::modifySettings(const QString &newName, const QString &comment, bool WWEnabled, const QString &newKey) const { modifyName(newName); item(QStringLiteral( "comment" ))->write(_id, comment); ConfigGroup cg; cg.writeEntry(HS_WW_ENABLED, WWEnabled); if ( !newKey.isEmpty() ) cg.writeEntry(HS_KEY, newKey); if (WWEnabled) cg.writeEntry(HS_REGISTERED_NAME, newName); } QString PlayerInfos::registeredName() const { ConfigGroup cg; return cg.readEntry(HS_REGISTERED_NAME, QString()); } void PlayerInfos::removeKey() { ConfigGroup cg; // save old key/nickname uint i = 0; QString str = QStringLiteral( "%1 old #%2" ); QString sk; do { i++; sk = str.arg(QLatin1String( HS_KEY )).arg(i); } while ( !cg.readEntry(sk, QString()).isEmpty() ); cg.writeEntry(sk, key()); cg.writeEntry(str.arg(QLatin1String( HS_REGISTERED_NAME )).arg(i), registeredName()); // clear current key/nickname cg.deleteEntry(HS_KEY); cg.deleteEntry(HS_REGISTERED_NAME); cg.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; } QUrl ManagerPrivate::queryUrl(QueryType type, const QString &newName) const { QUrl url = serverURL; QString nameItem = QStringLiteral( "nickname" ); QString name = _playerInfos->registeredName(); bool withVersion = true; bool key = false; bool level = false; switch (type) { case Submit: url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + QStringLiteral( "submit.php" )); level = true; key = true; break; case Register: url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + QStringLiteral( "register.php" )); name = newName; break; case Change: url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + QStringLiteral( "change.php" )); key = true; if ( newName!=name ) Manager::addToQueryURL(url, QStringLiteral( "new_nickname" ), newName); break; case Players: url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + QStringLiteral( "players.php" )); nameItem = QStringLiteral( "highlight" ); withVersion = false; break; case Scores: url = url.adjusted(QUrl::StripTrailingSlash); url.setPath(url.path() + QLatin1Char('/') + QStringLiteral( "highscores.php" )); withVersion = false; if ( _nbGameTypes>1 ) level = true; break; } if (withVersion) Manager::addToQueryURL(url, QStringLiteral( "version" ), version); if ( !name.isEmpty() ) Manager::addToQueryURL(url, nameItem, name); if (key) Manager::addToQueryURL(url, QStringLiteral( "key" ), _playerInfos->key()); if (level) { QString label = manager.gameTypeLabel(_gameType, Manager::WW); if ( !label.isEmpty() ) Manager::addToQueryURL(url, QStringLiteral( "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 QUrl &url, QWidget *parent, QDomNamedNodeMap *map) { - KIO::http_update_cache(url, true, QDateTime::fromTime_t(0)); // remove cache ! + KIO::http_update_cache(url, true, QDateTime::fromSecsSinceEpoch(0)); // remove cache ! QTemporaryFile tmpFile; if ( !tmpFile.open() ) { QString details = i18n("Unable to open temporary file."); KMessageBox::detailedSorry(parent, i18n(UNABLE_TO_CONTACT), details); return false; } auto copyJob = KIO::file_copy(url, QUrl::fromLocalFile(tmpFile.fileName())); KJobWidgets::setWindow(copyJob, parent); copyJob->exec(); if( copyJob->error() ) { QString details = i18n("Server URL: %1", url.host()); KMessageBox::detailedSorry(parent, i18n(UNABLE_TO_CONTACT), details); return false; } QTextStream t(&tmpFile); QString content = t.readAll().trimmed(); tmpFile.close(); QDomDocument doc; if ( doc.setContent(content) ) { QDomElement root = doc.documentElement(); QDomElement element = root.firstChild().toElement(); if ( element.tagName()==QLatin1String( "success" ) ) { if (map) *map = element.attributes(); return true; } if ( element.tagName()==QLatin1String( "error" ) ) { QDomAttr attr = element.attributes().namedItem(QStringLiteral( "label" )).toAttr(); if ( !attr.isNull() ) { QString msg = i18n(attr.value().toLatin1()); 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", 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).", 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 (; imaxNbEntries() ? (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(); QUrl url = queryUrl((newPlayer ? Register : Change), newName); Manager::addToQueryURL(url, QStringLiteral( "comment" ), comment); QDomNamedNodeMap map; bool ok = doQuery(url, widget, &map); if ( !ok || (newPlayer && !getFromQuery(map, QStringLiteral( "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); QVector scores(_scoreInfos->nbEntries()); for (int i=0; ilockForWriting(); for (int i=0; ioldLocalId()+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 = qMin(type, _nbGameTypes-1); QString str = QStringLiteral( "scores" ); QString lab = manager.gameTypeLabel(_gameType, Manager::Standard); if ( !lab.isEmpty() ) { _playerInfos->setSubGroup(lab); str += QLatin1Char( '_' ) + 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(QStringLiteral( "id" ), _playerInfos->id() + 1); score.setData(QStringLiteral( "date" ), QDateTime::currentDateTime()); // ask new name if anonymous and winner const QLatin1String dontAskAgainName = QLatin1String( "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; QUrl 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, QStringLiteral( "score" ), str); QCryptographicHash context(QCryptographicHash::Md5); context.addData(QString(_playerInfos->registeredName() + str).toLatin1()); Manager::addToQueryURL(url, QStringLiteral( "check" ), QLatin1String( context.result().toHex() )); 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/mainwindow.cpp b/mainwindow.cpp index 77a2d46..96b8c58 100644 --- a/mainwindow.cpp +++ b/mainwindow.cpp @@ -1,442 +1,446 @@ /******************************************************************* * * Copyright 2006 Dmitry Suzdalev * Copyright 2010 Brian Croom * Copyright 2013 Denis Kuplyakov * * This file is part of the KDE project "KReversi" * * KReversi is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * KReversi is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with KReversi; see the file COPYING. If not, write to * the Free Software Foundation, 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. * ********************************************************************/ #include "mainwindow.h" #include #include #include #include #include +#include #include #include #include #include #include "commondefs.h" #include "kreversihumanplayer.h" #include "kreversicomputerplayer.h" #include "kexthighscore.h" static const int BLACK_STATUSBAR_ID = 1; static const int WHITE_STATUSBAR_ID = 2; static const int COMMON_STATUSBAR_ID = 0; KReversiMainWindow::KReversiMainWindow(QWidget* parent, bool startDemo) : KXmlGuiWindow(parent), m_startDialog(nullptr), m_view(nullptr), m_game(nullptr), m_historyDock(nullptr), m_historyView(nullptr), m_firstShow(true), m_startInDemoMode(startDemo), m_undoAct(nullptr), m_hintAct(nullptr) { memset(m_player, 0, sizeof(m_player)); m_provider = new KgThemeProvider(); m_provider->discoverThemes("appdata", QStringLiteral("pics")); for (auto &label : m_statusBarLabel) { label = new QLabel(this); label->setAlignment(Qt::AlignCenter); statusBar()->addWidget(label, 1); } m_statusBarLabel[common]->setText(i18n("Press start game!")); // initialize difficulty stuff Kg::difficulty()->addStandardLevelRange( KgDifficultyLevel::VeryEasy, KgDifficultyLevel::Impossible, KgDifficultyLevel::Easy //default ); KgDifficultyGUI::init(this); connect(Kg::difficulty(), &KgDifficulty::currentLevelChanged, this, &KReversiMainWindow::levelChanged); Kg::difficulty()->setEditable(false); // initialize history dock m_historyView = new QListWidget(this); m_historyView->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Expanding); m_historyDock = new QDockWidget(i18n("Move History")); m_historyDock->setWidget(m_historyView); m_historyDock->setObjectName(QStringLiteral("history_dock")); m_historyDock->setAllowedAreas(Qt::LeftDockWidgetArea | Qt::RightDockWidgetArea); addDockWidget(Qt::RightDockWidgetArea, m_historyDock); // create main game view m_view = new KReversiView(m_game, this, m_provider); setCentralWidget(m_view); // initialise dialog handler m_startDialog = new StartGameDialog(this, m_provider); connect(m_startDialog, &StartGameDialog::startGame, this, &KReversiMainWindow::slotDialogReady); // initialise actions setupActionsInit(); // load saved settings loadSettings(); - setupGUI(qApp->desktop()->availableGeometry().size() * 0.7); - +#if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) + setupGUI(QApplication::screens().at(0)->availableGeometry().size() * 0.7); +#else + setupGUI(screen()->availableGeometry().size() * 0.7); +#endif m_historyDock->hide(); } KReversiMainWindow::~KReversiMainWindow() { clearPlayers(); delete m_provider; } void KReversiMainWindow::setupActionsInit() { // Common actions KStandardGameAction::gameNew(this, SLOT(slotNewGame()), actionCollection()); KStandardGameAction::highscores(this, SLOT(slotHighscores()), actionCollection()); KStandardGameAction::quit(this, SLOT(close()), actionCollection()); // Undo m_undoAct = KStandardGameAction::undo(this, SLOT(slotUndo()), actionCollection()); m_undoAct->setEnabled(false); // nothing to undo at the start of the game // Hint m_hintAct = KStandardGameAction::hint(m_view, SLOT(slotHint()), actionCollection()); m_hintAct->setEnabled(false); // Last move m_showLast = new KToggleAction(QIcon::fromTheme(QStringLiteral("lastmoves")), i18n("Show Last Move"), this); actionCollection()->addAction(QStringLiteral("show_last_move"), m_showLast); connect(m_showLast, &KToggleAction::triggered, m_view, &KReversiView::setShowLastMove); // Legal moves m_showLegal = new KToggleAction(QIcon::fromTheme(QStringLiteral("legalmoves")), i18n("Show Legal Moves"), this); actionCollection()->addAction(QStringLiteral("show_legal_moves"), m_showLegal); connect(m_showLegal, &KToggleAction::triggered, m_view, &KReversiView::setShowLegalMoves); // Animation speed m_animSpeedAct = new KSelectAction(i18n("Animation Speed"), this); actionCollection()->addAction(QStringLiteral("anim_speed"), m_animSpeedAct); QStringList acts; acts << i18n("Slow") << i18n("Normal") << i18n("Fast"); m_animSpeedAct->setItems(acts); connect(m_animSpeedAct, static_cast(&KSelectAction::triggered), this, &KReversiMainWindow::slotAnimSpeedChanged); // Chip's color m_coloredChipsAct = new KToggleAction(i18n("Use Colored Chips"), this); actionCollection()->addAction(QStringLiteral("use_colored_chips"), m_coloredChipsAct); connect(m_coloredChipsAct, &KToggleAction::triggered, this, &KReversiMainWindow::slotUseColoredChips); // Move history // NOTE: read/write this from/to config file? Or not necessary? m_showMovesAct = m_historyDock->toggleViewAction(); m_showMovesAct->setIcon(QIcon::fromTheme(QStringLiteral("view-history"))); m_showMovesAct->setText(i18n("Show Move History")); actionCollection()->addAction(QStringLiteral("show_moves"), m_showMovesAct); connect(m_historyDock, &QDockWidget::visibilityChanged, this, &KReversiMainWindow::slotToggleBoardLabels); } void KReversiMainWindow::loadSettings() { // Animation speed m_animSpeedAct->setCurrentItem(Preferences::animationSpeed()); m_view->setAnimationSpeed(Preferences::animationSpeed()); // Chip's color m_coloredChipsAct->setChecked(Preferences::useColoredChips()); m_view->setChipsPrefix(Preferences::useColoredChips() ? Colored : BlackWhite); m_startDialog->setChipsPrefix(Preferences::useColoredChips() ? Colored : BlackWhite); } void KReversiMainWindow::levelChanged() { // we are assuming that level can be changed here only when it is // USER-AI or AI-USER match int skill = Utils::difficultyLevelToInt(); if (m_nowPlayingInfo.type[White] == GameStartInformation::AI) ((KReversiComputerPlayer *)(m_player[White]))->setSkill(skill); else if (m_nowPlayingInfo.type[Black] == GameStartInformation::Human) ((KReversiComputerPlayer *)(m_player[Black]))->setSkill(skill); } void KReversiMainWindow::slotAnimSpeedChanged(int speed) { m_view->setAnimationSpeed(speed); Preferences::setAnimationSpeed(speed); Preferences::self()->save(); } void KReversiMainWindow::slotUseColoredChips(bool toggled) { ChipsPrefix chipsPrefix = m_coloredChipsAct->isChecked() ? Colored : BlackWhite; m_view->setChipsPrefix(chipsPrefix); m_startDialog->setChipsPrefix(chipsPrefix); Preferences::setUseColoredChips(toggled); Preferences::self()->save(); } void KReversiMainWindow::slotToggleBoardLabels(bool toggled) { m_view->setShowBoardLabels(toggled); } void KReversiMainWindow::slotNewGame() { m_startDialog->exec(); } void KReversiMainWindow::slotGameOver() { m_hintAct->setEnabled(false); m_undoAct->setEnabled(m_game->canUndo()); int blackScore = m_game->playerScore(Black); int whiteScore = m_game->playerScore(White); bool storeScore = false; KExtHighscore::Score score; QString res; if (m_nowPlayingInfo.type[Black] == GameStartInformation::Human && m_nowPlayingInfo.type[White] == GameStartInformation::AI) { // we are playing black storeScore = true; KExtHighscore::setGameType(((KReversiComputerPlayer *)m_player[White])->lowestSkill()); score.setScore(blackScore); if (blackScore == whiteScore) { res = i18n("Game is drawn!"); score.setType(KExtHighscore::Draw); } else if (blackScore > whiteScore) { res = i18n("You win!"); score.setType(KExtHighscore::Won); } else { res = i18n("You have lost!"); score.setType(KExtHighscore::Lost); } } else if (m_nowPlayingInfo.type[White] == GameStartInformation::Human && m_nowPlayingInfo.type[Black] == GameStartInformation::AI) { // we are playing white storeScore = true; KExtHighscore::setGameType(((KReversiComputerPlayer *)m_player[Black])->lowestSkill()); score.setScore(whiteScore); if (blackScore == whiteScore) { res = i18n("Game is drawn!"); score.setType(KExtHighscore::Draw); } else if (blackScore < whiteScore) { res = i18n("You win!"); score.setType(KExtHighscore::Won); } else { res = i18n("You have lost!"); score.setType(KExtHighscore::Lost); } } else if (m_nowPlayingInfo.type[Black] == GameStartInformation::Human && m_nowPlayingInfo.type[White] == GameStartInformation::Human) { // friends match if (blackScore == whiteScore) { res = i18n("Game is drawn!"); } else if (blackScore > whiteScore) { res = i18n("%1 has won!", m_nowPlayingInfo.name[Black]); } else { res = i18n("%1 has won!", m_nowPlayingInfo.name[White]); } } else { // using Black White names in other cases if (blackScore == whiteScore) { res = i18n("Game is drawn!"); } else if (blackScore > whiteScore) { res = i18n("%1 has won!", Utils::colorToString(Black)); } else { res = i18n("%1 has won!", Utils::colorToString(White)); } } if (m_nowPlayingInfo.type[Black] == GameStartInformation::AI && m_nowPlayingInfo.type[White] == GameStartInformation::AI) { res += i18n("\n%1: %2", Utils::colorToString(Black), blackScore); res += i18n("\n%1: %2", Utils::colorToString(White), whiteScore); } else { res += i18n("\n%1: %2", m_nowPlayingInfo.name[Black], blackScore); res += i18n("\n%1: %2", m_nowPlayingInfo.name[White], whiteScore); } KMessageBox::information(this, res, i18n("Game over")); if (storeScore) KExtHighscore::submitScore(score, this); } void KReversiMainWindow::slotMoveFinished() { updateHistory(); updateStatusBar(); m_hintAct->setEnabled(m_game->isHintAllowed()); m_undoAct->setEnabled(m_game->canUndo()); } void KReversiMainWindow::updateHistory() { MoveList history = m_game->getHistory(); m_historyView->clear(); for (int i = 0; i < history.size(); i++) { QString numStr = QString::number(i + 1) + QStringLiteral(". "); m_historyView->addItem(numStr + Utils::moveToString(history.at(i))); } QListWidgetItem *last = m_historyView->item(m_historyView->count() - 1); m_historyView->setCurrentItem(last); m_historyView->scrollToItem(last); } void KReversiMainWindow::slotUndo() { // scene will automatically notice that it needs to update m_game->undo(); updateHistory(); updateStatusBar(); m_undoAct->setEnabled(m_game->canUndo()); m_hintAct->setEnabled(m_game->isHintAllowed()); } void KReversiMainWindow::slotHighscores() { KExtHighscore::show(this); } void KReversiMainWindow::slotDialogReady() { GameStartInformation info = m_startDialog->createGameStartInformation(); receivedGameStartInformation(info); } void KReversiMainWindow::showEvent(QShowEvent*) { if (m_firstShow && m_startInDemoMode) { qDebug() << "starting demo..."; startDemo(); } else if (m_firstShow) { QTimer::singleShot(0, this, &KReversiMainWindow::slotNewGame); } m_firstShow = false; } void KReversiMainWindow::updateStatusBar() { if (m_game->isGameOver()) { m_statusBarLabel[common]->setText(i18n("GAME OVER")); } if (m_nowPlayingInfo.type[Black] == GameStartInformation::AI && m_nowPlayingInfo.type[White] == GameStartInformation::AI) { // using Black White names m_statusBarLabel[black]->setText(i18n("%1: %2", Utils::colorToString(Black), m_game->playerScore(Black))); m_statusBarLabel[white]->setText(i18n("%1: %2", Utils::colorToString(White), m_game->playerScore(White))); if (!m_game->isGameOver()) { m_statusBarLabel[common]->setText(i18n("%1 turn", Utils::colorToString(m_game->currentPlayer()))); } } else { // using player's names m_statusBarLabel[black]->setText(i18n("%1: %2", m_nowPlayingInfo.name[Black], m_game->playerScore(Black))); m_statusBarLabel[white]->setText(i18n("%1: %2", m_nowPlayingInfo.name[White], m_game->playerScore(White))); if (!m_game->isGameOver() && m_game->currentPlayer() != NoColor) { m_statusBarLabel[common]->setText(i18n("%1's turn", m_nowPlayingInfo.name[m_game->currentPlayer()])); } } } // TODO: test it!!! void KReversiMainWindow::startDemo() { GameStartInformation info; info.name[0] = info.name[1] = i18n("Computer"); info.type[0] = info.type[1] = GameStartInformation::AI; info.skill[0] = info.skill[1] = Utils::difficultyLevelToInt(); receivedGameStartInformation(info); } void KReversiMainWindow::clearPlayers() { for (int i = 0; i < 2; i++) // iterating through white to black if (m_player[i]) { m_player[i]->disconnect(); delete m_player[i]; m_player[i] = nullptr; } } void KReversiMainWindow::receivedGameStartInformation(const GameStartInformation &info) { clearPlayers(); m_nowPlayingInfo = info; for (int i = 0; i < 2; i++) // iterating through black and white if (info.type[i] == GameStartInformation::AI) { m_player[i] = new KReversiComputerPlayer(ChipColor(i), info.name[i]); ((KReversiComputerPlayer *)(m_player[i]))->setSkill(info.skill[i]); levelChanged(); } else { m_player[i] = new KReversiHumanPlayer(ChipColor(i), info.name[i]); } m_game = new KReversiGame(m_player[Black], m_player[White]); m_view->setGame(m_game); connect(m_game, &KReversiGame::gameOver, this, &KReversiMainWindow::slotGameOver); connect(m_game, &KReversiGame::moveFinished, this, &KReversiMainWindow::slotMoveFinished); for (int i = 0; i < 2; i++) // iterating white to black if (info.type[i] == GameStartInformation::Human) connect(m_view, &KReversiView::userMove, (KReversiHumanPlayer *)(m_player[i]), &KReversiHumanPlayer::onUICellClick); updateStatusBar(); updateHistory(); if (info.type[White] == GameStartInformation::AI && info.type[Black] == GameStartInformation::Human) { Kg::difficulty()->setEditable(true); Kg::difficulty()->select(Utils::intToDifficultyLevel(info.skill[White])); } else if (info.type[White] == GameStartInformation::Human && info.type[Black] == GameStartInformation::AI) { Kg::difficulty()->setEditable(true); Kg::difficulty()->select(Utils::intToDifficultyLevel(info.skill[Black])); } else Kg::difficulty()->setEditable(false); m_hintAct->setEnabled(m_game->isHintAllowed()); m_undoAct->setEnabled(m_game->canUndo()); }