1/***************************************************************************
2 * Copyright 2007 Nicolas Roffet <nicolas-kde@roffet.com> *
3 * Copyright 2007 Pino Toscano <toscano.pino@tiscali.it> *
4 * Copyright 2011-2012 Stefan Majewsky <majewsky@gmx.net> *
5 * *
6 * This program is free software; you can redistribute it and/or modify *
7 * it under the terms of the GNU Library General Public License *
8 * version 2 as published by the Free Software Foundation *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU Library General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU Library General Public *
16 * License along with this program; if not, write to the *
17 * Free Software Foundation, Inc., *
18 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. *
19 ***************************************************************************/
20
21#include "kgdifficulty.h"
22
23#include <QtCore/QVector>
24#include <KDE/KConfigGroup>
25#include <KDE/KGlobal>
26#include <KDE/KGuiItem>
27#include <KDE/KLocale>
28#include <KDE/KMessageBox>
29//the following only used by KgDifficultyGUI
30#include <KDE/KActionCollection>
31#include <KDE/KComboBox>
32#include <KDE/KSelectAction>
33#include <KDE/KStatusBar>
34#include <KDE/KXmlGuiWindow>
35
36//BEGIN KgDifficultyLevel
37
38class KgDifficultyLevel::Private
39{
40 public:
41 bool m_isDefault;
42 int m_hardness;
43 StandardLevel m_level;
44 QByteArray m_key;
45 QString m_title;
46
47 Private(int hardness, const QByteArray& key, const QString& title, StandardLevel level, bool isDefault);
48 static Private* fromStandardLevel(StandardLevel level, bool isDefault);
49};
50
51KgDifficultyLevel::KgDifficultyLevel(int hardness, const QByteArray& key, const QString& title, bool isDefault)
52 : d(new Private(hardness, key, title, Custom, isDefault))
53{
54}
55
56KgDifficultyLevel::Private::Private(int hardness, const QByteArray& key, const QString& title, StandardLevel level, bool isDefault)
57 : m_isDefault(isDefault)
58 , m_hardness(hardness)
59 , m_level(level)
60 , m_key(key)
61 , m_title(title)
62{
63}
64
65KgDifficultyLevel::KgDifficultyLevel(StandardLevel level, bool isDefault)
66 : d(Private::fromStandardLevel(level, isDefault))
67{
68}
69
70KgDifficultyLevel::Private* KgDifficultyLevel::Private::fromStandardLevel(KgDifficultyLevel::StandardLevel level, bool isDefault)
71{
72 Q_ASSERT_X(level != Custom,
73 "KgDifficultyLevel(StandardLevel) constructor",
74 "Custom level not allowed here"
75 );
76 //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
77 QPair<QByteArray, QString> data;
78 switch (level)
79 {
80 case RidiculouslyEasy:
81 data = qMakePair(QByteArray("Ridiculously Easy"), i18nc("Game difficulty level 1 out of 8", "Ridiculously Easy"));
82 break;
83 case VeryEasy:
84 data = qMakePair(QByteArray("Very Easy"), i18nc("Game difficulty level 2 out of 8", "Very Easy"));
85 break;
86 case Easy:
87 data = qMakePair(QByteArray("Easy"), i18nc("Game difficulty level 3 out of 8", "Easy"));
88 break;
89 case Medium:
90 data = qMakePair(QByteArray("Medium"), i18nc("Game difficulty level 4 out of 8", "Medium"));
91 break;
92 case Hard:
93 data = qMakePair(QByteArray("Hard"), i18nc("Game difficulty level 5 out of 8", "Hard"));
94 break;
95 case VeryHard:
96 data = qMakePair(QByteArray("Very Hard"), i18nc("Game difficulty level 6 out of 8", "Very Hard"));
97 break;
98 case ExtremelyHard:
99 data = qMakePair(QByteArray("Extremely Hard"), i18nc("Game difficulty level 7 out of 8", "Extremely Hard"));
100 break;
101 case Impossible:
102 data = qMakePair(QByteArray("Impossible"), i18nc("Game difficulty level 8 out of 8", "Impossible"));
103 break;
104 case Custom:
105 return 0;
106 }
107 return new KgDifficultyLevel::Private(level, data.first, data.second, level, isDefault);
108}
109
110KgDifficultyLevel::~KgDifficultyLevel()
111{
112 delete d;
113}
114
115bool KgDifficultyLevel::isDefault() const
116{
117 return d->m_isDefault;
118}
119
120int KgDifficultyLevel::hardness() const
121{
122 return d->m_hardness;
123}
124
125QByteArray KgDifficultyLevel::key() const
126{
127 return d->m_key;
128}
129
130QString KgDifficultyLevel::title() const
131{
132 return d->m_title;
133}
134
135KgDifficultyLevel::StandardLevel KgDifficultyLevel::standardLevel() const
136{
137 return d->m_level;
138}
139
140//END KgDifficultyLevel
141//BEGIN KgDifficulty
142
143class KgDifficulty::Private
144{
145 public:
146 QList<const KgDifficultyLevel*> m_levels;
147 const KgDifficultyLevel* m_currentLevel;
148 bool m_editable, m_gameRunning;
149
150 Private() : m_currentLevel(0), m_editable(true), m_gameRunning(false) {}
151};
152
153KgDifficulty::KgDifficulty(QObject* parent)
154 : QObject(parent)
155 , d(new Private)
156{
157 qRegisterMetaType<const KgDifficultyLevel*>();
158}
159
160KgDifficulty::~KgDifficulty()
161{
162 if (d->m_levels.isEmpty())
163 {
164 return;
165 }
166 //save current difficulty level in config file (no sync() call here; this
167 //will most likely be called at application shutdown when others are also
168 //writing to KGlobal::config(); also KConfig's dtor will sync automatically)
169 KConfigGroup cg(KGlobal::config(), "KgDifficulty");
170 cg.writeEntry("Level", currentLevel()->key());
171 //cleanup
172 while (!d->m_levels.isEmpty())
173 {
174 delete const_cast<KgDifficultyLevel*>(d->m_levels.takeFirst());
175 }
176}
177
178void KgDifficulty::addLevel(KgDifficultyLevel* level)
179{
180 //The intended use is to create the KgDifficulty object, add levels, *then*
181 //start to work with the currentLevel(). The first call to currentLevel()
182 //will load the previous selection from the config, and the level list will
183 //be considered immutable from this point.
184 Q_ASSERT_X(d->m_currentLevel == 0,
185 "KgDifficulty::addLevel",
186 "Only allowed before currentLevel() is called."
187 );
188 //ensure that list stays sorted
189 QList<const KgDifficultyLevel*>::iterator it = d->m_levels.begin();
190 while (it != d->m_levels.end() && (*it)->hardness() < level->hardness())
191 {
192 ++it;
193 }
194 d->m_levels.insert(it, level);
195 level->setParent(this);
196}
197
198typedef KgDifficultyLevel::StandardLevel DS;
199
200void KgDifficulty::addStandardLevel(DS level, bool isDefault)
201{
202 addLevel(new KgDifficultyLevel(level, isDefault));
203}
204
205void KgDifficulty::addStandardLevelRange(DS from, DS to)
206{
207 //every level in range != Custom, therefore no level is default
208 addStandardLevelRange(from, to, KgDifficultyLevel::Custom);
209}
210
211void KgDifficulty::addStandardLevelRange(DS from, DS to, DS defaultLevel)
212{
213 const QVector<DS> levels = QVector<DS>()
214 << KgDifficultyLevel::RidiculouslyEasy
215 << KgDifficultyLevel::VeryEasy
216 << KgDifficultyLevel::Easy
217 << KgDifficultyLevel::Medium
218 << KgDifficultyLevel::Hard
219 << KgDifficultyLevel::VeryHard
220 << KgDifficultyLevel::ExtremelyHard
221 << KgDifficultyLevel::Impossible
222 ;
223 const int fromIndex = levels.indexOf(from);
224 const int toIndex = levels.indexOf(to);
225 const int defaultLevelIndex = levels.indexOf(defaultLevel);
226 Q_ASSERT_X(fromIndex >= 0 && toIndex > fromIndex && (defaultLevelIndex == KgDifficultyLevel::Custom || (defaultLevelIndex >= fromIndex && defaultLevelIndex <= toIndex)),
227 "KgDifficulty::addStandardLevelRange",
228 "No argument may be KgDifficultyLevel::Custom."
229 );
230 for (int i = fromIndex; i <= toIndex; ++i)
231 {
232 addLevel(new KgDifficultyLevel(levels[i], levels[i] == defaultLevel));
233 }
234}
235
236QList<const KgDifficultyLevel*> KgDifficulty::levels() const
237{
238 return d->m_levels;
239}
240
241const KgDifficultyLevel* KgDifficulty::currentLevel() const
242{
243 if (d->m_currentLevel)
244 {
245 return d->m_currentLevel;
246 }
247 Q_ASSERT(!d->m_levels.isEmpty());
248 //check configuration file for saved difficulty level
249 KConfigGroup cg(KGlobal::config(), "KgDifficulty");
250 const QByteArray key = cg.readEntry("Level", QByteArray());
251 foreach (const KgDifficultyLevel* level, d->m_levels)
252 {
253 if (level->key() == key)
254 {
255 return d->m_currentLevel = level;
256 }
257 }
258 //no level predefined - look for a default level
259 foreach (const KgDifficultyLevel* level, d->m_levels)
260 {
261 if (level->isDefault())
262 {
263 return d->m_currentLevel = level;
264 }
265 }
266 //no default level predefined - easiest level is probably a sane default
267 return d->m_currentLevel = d->m_levels[0];
268}
269
270bool KgDifficulty::isEditable() const
271{
272 return d->m_editable;
273}
274
275void KgDifficulty::setEditable(bool editable)
276{
277 if (d->m_editable == editable)
278 {
279 return;
280 }
281 d->m_editable = editable;
282 emit editableChanged(editable);
283}
284
285bool KgDifficulty::isGameRunning() const
286{
287 return d->m_gameRunning;
288}
289
290void KgDifficulty::setGameRunning(bool gameRunning)
291{
292 if (d->m_gameRunning == gameRunning)
293 {
294 return;
295 }
296 d->m_gameRunning = gameRunning;
297 emit gameRunningChanged(gameRunning);
298}
299
300void KgDifficulty::select(const KgDifficultyLevel* level)
301{
302 Q_ASSERT(d->m_levels.contains(level));
303 if (d->m_currentLevel == level)
304 {
305 return;
306 }
307 //ask for confirmation if necessary
308 if (d->m_gameRunning)
309 {
310 const int result = KMessageBox::warningContinueCancel(0,
311 i18n("Changing the difficulty level will end the current game!"),
312 QString(), KGuiItem(i18n("Change the difficulty level"))
313 );
314 if (result != KMessageBox::Continue)
315 {
316 emit selectedLevelChanged(d->m_currentLevel);
317 return;
318 }
319 }
320 d->m_currentLevel = level;
321 emit selectedLevelChanged(level);
322 emit currentLevelChanged(level);
323}
324
325//END KgDifficulty
326
327K_GLOBAL_STATIC(KgDifficulty, g_difficulty)
328
329KgDifficulty* Kg::difficulty()
330{
331 return g_difficulty;
332}
333
334KgDifficultyLevel::StandardLevel Kg::difficultyLevel()
335{
336 return g_difficulty->currentLevel()->standardLevel();
337}
338
339//BEGIN KgDifficultyGUI
340
341namespace KgDifficultyGUI
342{
343 class Selector : public KComboBox
344 {
345 Q_OBJECT
346 private:
347 KgDifficulty* d;
348 public:
349 Selector(KgDifficulty* difficulty, QWidget* parent = 0)
350 : KComboBox(parent), d(difficulty) {}
351 Q_SIGNALS:
352 void signalSelected(int levelIndex);
353 public Q_SLOTS:
354 void slotActivated(int levelIndex)
355 {
356 d->select(d->levels().value(levelIndex));
357 }
358 void slotSelected(const KgDifficultyLevel* level)
359 {
360 emit signalSelected(d->levels().indexOf(level));
361 }
362 };
363 class Menu : public KSelectAction
364 {
365 Q_OBJECT
366 public:
367 Menu(const KIcon& i, const QString& s, QWidget* p) : KSelectAction(i,s,p){}
368 public Q_SLOTS:
369 //this whole class just because the following is not a slot
370 void setCurrentItem(int index) { KSelectAction::setCurrentItem(index); }
371 };
372}
373
374void KgDifficultyGUI::init(KXmlGuiWindow* window, KgDifficulty* difficulty)
375{
376 const bool useSingleton = !difficulty;
377 if (useSingleton)
378 difficulty = Kg::difficulty();
379
380 //create selector (resides in status bar)
381 KgDifficultyGUI::Selector* selector = new KgDifficultyGUI::Selector(difficulty, window);
382 selector->setToolTip(i18nc("Game difficulty level", "Difficulty"));
383 QObject::connect(selector, SIGNAL(activated(int)), selector, SLOT(slotActivated(int)));
384 QObject::connect(difficulty, SIGNAL(editableChanged(bool)), selector, SLOT(setEnabled(bool)));
385 QObject::connect(difficulty, SIGNAL(selectedLevelChanged(const KgDifficultyLevel*)),
386 selector, SLOT(slotSelected(const KgDifficultyLevel*)));
387 QObject::connect(selector, SIGNAL(signalSelected(int)), selector, SLOT(setCurrentIndex(int)));
388
389 //create menu action
390 const KIcon icon("games-difficult");
391 KSelectAction* menu = new KgDifficultyGUI::Menu(icon, i18nc("Game difficulty level", "Difficulty"), window);
392 menu->setToolTip(i18n("Set the difficulty level"));
393 menu->setWhatsThis(i18n("Set the difficulty level of the game."));
394 QObject::connect(menu, SIGNAL(triggered(int)), selector, SLOT(slotActivated(int)));
395 QObject::connect(difficulty, SIGNAL(editableChanged(bool)), menu, SLOT(setEnabled(bool)));
396 QObject::connect(selector, SIGNAL(signalSelected(int)), menu, SLOT(setCurrentItem(int)));
397
398 //fill menu and selector
399 foreach (const KgDifficultyLevel* level, difficulty->levels())
400 {
401 selector->addItem(icon, level->title());
402 menu->addAction(level->title());
403 }
404 //initialize selection in selector
405 selector->slotSelected(difficulty->currentLevel());
406
407 //add selector to statusbar
408 window->statusBar()->addPermanentWidget(selector);
409 //add menu action to window
410 menu->setObjectName(QLatin1String("options_game_difficulty"));
411 window->actionCollection()->addAction(menu->objectName(), menu);
412
413 //ensure that the KgDifficulty instance gets deleted
414 if (!useSingleton && !difficulty->parent())
415 {
416 difficulty->setParent(window);
417 }
418}
419
420//END KgDifficultyGUI
421
422#include "kgdifficulty.moc"
423#include "moc_kgdifficulty.cpp"
424