File indexing completed on 2024-04-28 04:05:02

0001 /*
0002     SPDX-FileCopyrightText: 2007 Nicolas Roffet <nicolas-kde@roffet.com>
0003     SPDX-FileCopyrightText: 2007 Pino Toscano <toscano.pino@tiscali.it>
0004     SPDX-FileCopyrightText: 2011-2012 Stefan Majewsky <majewsky@gmx.net>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-only
0007 */
0008 
0009 #include "kgamedifficulty.h"
0010 
0011 // KF
0012 #include <KActionCollection>
0013 #include <KComboBox>
0014 #include <KConfigGroup>
0015 #include <KGuiItem>
0016 #include <KLocalizedString>
0017 #include <KMessageBox>
0018 #include <KSelectAction>
0019 #include <KSharedConfig>
0020 #include <KXmlGuiWindow>
0021 // Qt
0022 #include <QCoreApplication>
0023 #include <QIcon>
0024 #include <QList>
0025 #include <QStatusBar>
0026 // Std
0027 #include <algorithm>
0028 #include <utility>
0029 
0030 // BEGIN KGameDifficultyLevel
0031 
0032 class KGameDifficultyLevelPrivate
0033 {
0034 public:
0035     bool m_isDefault;
0036     int m_hardness;
0037     KGameDifficultyLevel::StandardLevel m_level;
0038     QByteArray m_key;
0039     QString m_title;
0040 
0041 public:
0042     KGameDifficultyLevelPrivate(int hardness, const QByteArray &key, const QString &title, KGameDifficultyLevel::StandardLevel level, bool isDefault);
0043     static KGameDifficultyLevelPrivate *fromStandardLevel(KGameDifficultyLevel::StandardLevel level, bool isDefault);
0044 };
0045 
0046 KGameDifficultyLevel::KGameDifficultyLevel(int hardness, const QByteArray &key, const QString &title, bool isDefault)
0047     : d_ptr(new KGameDifficultyLevelPrivate(hardness, key, title, Custom, isDefault))
0048 {
0049 }
0050 
0051 KGameDifficultyLevelPrivate::KGameDifficultyLevelPrivate(int hardness,
0052                                                          const QByteArray &key,
0053                                                          const QString &title,
0054                                                          KGameDifficultyLevel::StandardLevel level,
0055                                                          bool isDefault)
0056     : m_isDefault(isDefault)
0057     , m_hardness(hardness)
0058     , m_level(level)
0059     , m_key(key)
0060     , m_title(title)
0061 {
0062 }
0063 
0064 KGameDifficultyLevel::KGameDifficultyLevel(StandardLevel level, bool isDefault)
0065     : d_ptr(KGameDifficultyLevelPrivate::fromStandardLevel(level, isDefault))
0066 {
0067 }
0068 
0069 KGameDifficultyLevelPrivate *KGameDifficultyLevelPrivate::fromStandardLevel(KGameDifficultyLevel::StandardLevel level, bool isDefault)
0070 {
0071     Q_ASSERT_X(level != KGameDifficultyLevel::Custom, "KGameDifficultyLevel(StandardLevel) constructor", "Custom level not allowed here");
0072     // The first entry in the pair is to be used as a key so don't change it. It doesn't have to match the string to be translated
0073     QPair<QByteArray, QString> data;
0074     switch (level) {
0075     case KGameDifficultyLevel::RidiculouslyEasy:
0076         data = qMakePair(QByteArrayLiteral("Ridiculously Easy"), i18nc("Game difficulty level 1 out of 8", "Ridiculously Easy"));
0077         break;
0078     case KGameDifficultyLevel::VeryEasy:
0079         data = qMakePair(QByteArrayLiteral("Very Easy"), i18nc("Game difficulty level 2 out of 8", "Very Easy"));
0080         break;
0081     case KGameDifficultyLevel::Easy:
0082         data = qMakePair(QByteArrayLiteral("Easy"), i18nc("Game difficulty level 3 out of 8", "Easy"));
0083         break;
0084     case KGameDifficultyLevel::Medium:
0085         data = qMakePair(QByteArrayLiteral("Medium"), i18nc("Game difficulty level 4 out of 8", "Medium"));
0086         break;
0087     case KGameDifficultyLevel::Hard:
0088         data = qMakePair(QByteArrayLiteral("Hard"), i18nc("Game difficulty level 5 out of 8", "Hard"));
0089         break;
0090     case KGameDifficultyLevel::VeryHard:
0091         data = qMakePair(QByteArrayLiteral("Very Hard"), i18nc("Game difficulty level 6 out of 8", "Very Hard"));
0092         break;
0093     case KGameDifficultyLevel::ExtremelyHard:
0094         data = qMakePair(QByteArrayLiteral("Extremely Hard"), i18nc("Game difficulty level 7 out of 8", "Extremely Hard"));
0095         break;
0096     case KGameDifficultyLevel::Impossible:
0097         data = qMakePair(QByteArrayLiteral("Impossible"), i18nc("Game difficulty level 8 out of 8", "Impossible"));
0098         break;
0099     case KGameDifficultyLevel::Custom:
0100         return nullptr;
0101     }
0102     return new KGameDifficultyLevelPrivate(level, data.first, data.second, level, isDefault);
0103 }
0104 
0105 KGameDifficultyLevel::~KGameDifficultyLevel() = default;
0106 
0107 bool KGameDifficultyLevel::isDefault() const
0108 {
0109     Q_D(const KGameDifficultyLevel);
0110 
0111     return d->m_isDefault;
0112 }
0113 
0114 int KGameDifficultyLevel::hardness() const
0115 {
0116     Q_D(const KGameDifficultyLevel);
0117 
0118     return d->m_hardness;
0119 }
0120 
0121 QByteArray KGameDifficultyLevel::key() const
0122 {
0123     Q_D(const KGameDifficultyLevel);
0124 
0125     return d->m_key;
0126 }
0127 
0128 QString KGameDifficultyLevel::title() const
0129 {
0130     Q_D(const KGameDifficultyLevel);
0131 
0132     return d->m_title;
0133 }
0134 
0135 KGameDifficultyLevel::StandardLevel KGameDifficultyLevel::standardLevel() const
0136 {
0137     Q_D(const KGameDifficultyLevel);
0138 
0139     return d->m_level;
0140 }
0141 
0142 // END KGameDifficultyLevel
0143 // BEGIN KGameDifficulty
0144 
0145 class KGameDifficultyPrivate
0146 {
0147 public:
0148     QList<const KGameDifficultyLevel *> m_levels;
0149     mutable const KGameDifficultyLevel *m_currentLevel = nullptr;
0150     bool m_editable = true;
0151     bool m_gameRunning = false;
0152 
0153 public:
0154     KGameDifficultyPrivate() = default;
0155 };
0156 
0157 static void saveLevel()
0158 {
0159     // save current difficulty level in config file (no sync() call here; this
0160     // will most likely be called at application shutdown when others are also
0161     // writing to KGlobal::config(); also KConfig's dtor will sync automatically)
0162     KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KgDifficulty"));
0163     cg.writeEntry("Level", KGameDifficulty::global()->currentLevel()->key());
0164 }
0165 
0166 KGameDifficulty::KGameDifficulty(QObject *parent)
0167     : QObject(parent)
0168     , d_ptr(new KGameDifficultyPrivate)
0169 {
0170     qRegisterMetaType<const KGameDifficultyLevel *>();
0171     qAddPostRoutine(saveLevel);
0172 }
0173 
0174 KGameDifficulty::~KGameDifficulty()
0175 {
0176     Q_D(KGameDifficulty);
0177 
0178     qDeleteAll(d->m_levels);
0179 }
0180 
0181 void KGameDifficulty::addLevel(KGameDifficultyLevel *level)
0182 {
0183     Q_D(KGameDifficulty);
0184 
0185     // The intended use is to create the KGameDifficulty object, add levels, *then*
0186     // start to work with the currentLevel(). The first call to currentLevel()
0187     // will load the previous selection from the config, and the level list will
0188     // be considered immutable from this point.
0189     Q_ASSERT_X(d->m_currentLevel == nullptr, "KGameDifficulty::addLevel", "Only allowed before currentLevel() is called.");
0190     // ensure that list stays sorted
0191     const int newLevelHardness = level->hardness();
0192     auto it = std::find_if(d->m_levels.begin(), d->m_levels.end(), [newLevelHardness](const KGameDifficultyLevel *l) {
0193         return l->hardness() >= newLevelHardness;
0194     });
0195     d->m_levels.insert(it, level);
0196     level->setParent(this);
0197 }
0198 
0199 typedef KGameDifficultyLevel::StandardLevel DS;
0200 
0201 void KGameDifficulty::addStandardLevel(DS level, bool isDefault)
0202 {
0203     addLevel(new KGameDifficultyLevel(level, isDefault));
0204 }
0205 
0206 void KGameDifficulty::addStandardLevelRange(DS from, DS to)
0207 {
0208     // every level in range != Custom, therefore no level is default
0209     addStandardLevelRange(from, to, KGameDifficultyLevel::Custom);
0210 }
0211 
0212 void KGameDifficulty::addStandardLevelRange(DS from, DS to, DS defaultLevel)
0213 {
0214     const QList<DS> levels{
0215         KGameDifficultyLevel::RidiculouslyEasy,
0216         KGameDifficultyLevel::VeryEasy,
0217         KGameDifficultyLevel::Easy,
0218         KGameDifficultyLevel::Medium,
0219         KGameDifficultyLevel::Hard,
0220         KGameDifficultyLevel::VeryHard,
0221         KGameDifficultyLevel::ExtremelyHard,
0222         KGameDifficultyLevel::Impossible,
0223     };
0224     const int fromIndex = levels.indexOf(from);
0225     const int toIndex = levels.indexOf(to);
0226     const int defaultLevelIndex = levels.indexOf(defaultLevel);
0227     Q_ASSERT_X(fromIndex >= 0 && toIndex > fromIndex
0228                    && (defaultLevelIndex == KGameDifficultyLevel::Custom || (defaultLevelIndex >= fromIndex && defaultLevelIndex <= toIndex)),
0229                "KGameDifficulty::addStandardLevelRange",
0230                "No argument may be KGameDifficultyLevel::Custom.");
0231     for (int i = fromIndex; i <= toIndex; ++i) {
0232         addLevel(new KGameDifficultyLevel(levels[i], levels[i] == defaultLevel));
0233     }
0234 }
0235 
0236 QList<const KGameDifficultyLevel *> KGameDifficulty::levels() const
0237 {
0238     Q_D(const KGameDifficulty);
0239 
0240     return d->m_levels;
0241 }
0242 
0243 const KGameDifficultyLevel *KGameDifficulty::currentLevel() const
0244 {
0245     Q_D(const KGameDifficulty);
0246 
0247     if (d->m_currentLevel) {
0248         return d->m_currentLevel;
0249     }
0250     Q_ASSERT(!d->m_levels.isEmpty());
0251     // check configuration file for saved difficulty level
0252     KConfigGroup cg(KSharedConfig::openConfig(), QStringLiteral("KgDifficulty"));
0253     const QByteArray key = cg.readEntry("Level", QByteArray());
0254     for (const KGameDifficultyLevel *level : std::as_const(d->m_levels)) {
0255         if (level->key() == key) {
0256             return d->m_currentLevel = level;
0257         }
0258     }
0259     // no level predefined - look for a default level
0260     for (const KGameDifficultyLevel *level : std::as_const(d->m_levels)) {
0261         if (level->isDefault()) {
0262             return d->m_currentLevel = level;
0263         }
0264     }
0265     // no default level predefined - easiest level is probably a sane default
0266     return d->m_currentLevel = d->m_levels[0];
0267 }
0268 
0269 bool KGameDifficulty::isEditable() const
0270 {
0271     Q_D(const KGameDifficulty);
0272 
0273     return d->m_editable;
0274 }
0275 
0276 void KGameDifficulty::setEditable(bool editable)
0277 {
0278     Q_D(KGameDifficulty);
0279 
0280     if (d->m_editable == editable) {
0281         return;
0282     }
0283     d->m_editable = editable;
0284     Q_EMIT editableChanged(editable);
0285 }
0286 
0287 bool KGameDifficulty::isGameRunning() const
0288 {
0289     Q_D(const KGameDifficulty);
0290 
0291     return d->m_gameRunning;
0292 }
0293 
0294 void KGameDifficulty::setGameRunning(bool gameRunning)
0295 {
0296     Q_D(KGameDifficulty);
0297 
0298     if (d->m_gameRunning == gameRunning) {
0299         return;
0300     }
0301     d->m_gameRunning = gameRunning;
0302     Q_EMIT gameRunningChanged(gameRunning);
0303 }
0304 
0305 void KGameDifficulty::select(const KGameDifficultyLevel *level)
0306 {
0307     Q_D(KGameDifficulty);
0308 
0309     Q_ASSERT(d->m_levels.contains(level));
0310     if (d->m_currentLevel == level) {
0311         return;
0312     }
0313     // ask for confirmation if necessary
0314     if (d->m_gameRunning) {
0315         const int result = KMessageBox::warningContinueCancel(nullptr,
0316                                                               i18n("Changing the difficulty level will end the current game!"),
0317                                                               QString(),
0318                                                               KGuiItem(i18n("Change the difficulty level")));
0319         if (result != KMessageBox::Continue) {
0320             Q_EMIT selectedLevelChanged(d->m_currentLevel);
0321             return;
0322         }
0323     }
0324     d->m_currentLevel = level;
0325     Q_EMIT selectedLevelChanged(level);
0326     Q_EMIT currentLevelChanged(level);
0327 }
0328 
0329 // END KGameDifficulty
0330 
0331 Q_GLOBAL_STATIC(KGameDifficulty, g_difficulty)
0332 
0333 KGameDifficulty *KGameDifficulty::global()
0334 {
0335     return g_difficulty;
0336 }
0337 
0338 KGameDifficultyLevel::StandardLevel KGameDifficulty::globalLevel()
0339 {
0340     return g_difficulty->currentLevel()->standardLevel();
0341 }
0342 
0343 // BEGIN KGameDifficultyGUI
0344 
0345 namespace KGameDifficultyGUI
0346 {
0347 class Selector : public KComboBox
0348 {
0349     Q_OBJECT
0350 
0351 private:
0352     KGameDifficulty *d;
0353 
0354 public:
0355     Selector(KGameDifficulty *difficulty, QWidget *parent = nullptr)
0356         : KComboBox(parent)
0357         , d(difficulty)
0358     {
0359     }
0360 
0361 Q_SIGNALS:
0362     void signalSelected(int levelIndex);
0363 
0364 public Q_SLOTS:
0365     void slotActivated(int levelIndex)
0366     {
0367         d->select(d->levels().value(levelIndex));
0368     }
0369     void slotSelected(const KGameDifficultyLevel *level)
0370     {
0371         Q_EMIT signalSelected(d->levels().indexOf(level));
0372     }
0373 };
0374 
0375 class Menu : public KSelectAction
0376 {
0377     Q_OBJECT
0378 
0379 public:
0380     Menu(const QIcon &i, const QString &s, QWidget *p)
0381         : KSelectAction(i, s, p)
0382     {
0383     }
0384 
0385 public Q_SLOTS:
0386     // this whole class just because the following is not a slot
0387     void setCurrentItem(int index)
0388     {
0389         KSelectAction::setCurrentItem(index);
0390     }
0391 };
0392 }
0393 
0394 void KGameDifficultyGUI::init(KXmlGuiWindow *window, KGameDifficulty *difficulty)
0395 {
0396     const bool useSingleton = !difficulty;
0397     if (useSingleton)
0398         difficulty = KGameDifficulty::global();
0399 
0400     // create selector (resides in status bar)
0401     KGameDifficultyGUI::Selector *selector = new KGameDifficultyGUI::Selector(difficulty, window);
0402     selector->setToolTip(i18nc("Game difficulty level", "Difficulty"));
0403     QObject::connect(selector, &QComboBox::activated, selector, &Selector::slotActivated);
0404     QObject::connect(difficulty, &KGameDifficulty::editableChanged, selector, &QWidget::setEnabled);
0405     QObject::connect(difficulty, &KGameDifficulty::selectedLevelChanged, selector, &Selector::slotSelected);
0406     QObject::connect(selector, &Selector::signalSelected, selector, &QComboBox::setCurrentIndex);
0407 
0408     // create menu action
0409     const QIcon icon = QIcon::fromTheme(QStringLiteral("games-difficult"));
0410     KSelectAction *menu = new KGameDifficultyGUI::Menu(icon, i18nc("Game difficulty level", "Difficulty"), window);
0411     menu->setToolTip(i18n("Set the difficulty level"));
0412     menu->setWhatsThis(i18n("Set the difficulty level of the game."));
0413     QObject::connect(menu, &KSelectAction::indexTriggered, selector, &Selector::slotActivated);
0414     QObject::connect(difficulty, &KGameDifficulty::editableChanged, menu, &QAction::setEnabled);
0415     QObject::connect(selector, &Selector::signalSelected, menu, &KSelectAction::setCurrentItem);
0416 
0417     // fill menu and selector
0418     const auto levels = difficulty->levels();
0419     for (const KGameDifficultyLevel *level : levels) {
0420         selector->addItem(icon, level->title());
0421         menu->addAction(level->title());
0422     }
0423     // initialize selection in selector
0424     selector->slotSelected(difficulty->currentLevel());
0425 
0426     // add selector to statusbar
0427     window->statusBar()->addPermanentWidget(selector);
0428     // add menu action to window
0429     menu->setObjectName(QStringLiteral("options_game_difficulty"));
0430     window->actionCollection()->addAction(menu->objectName(), menu);
0431 
0432     // ensure that the KGameDifficulty instance gets deleted
0433     if (!useSingleton && !difficulty->parent()) {
0434         difficulty->setParent(window);
0435     }
0436 }
0437 
0438 // END KGameDifficultyGUI
0439 
0440 #include "kgamedifficulty.moc"
0441 #include "moc_kgamedifficulty.cpp"