1/***************************************************************************
2 * Copyright (C) 1999-2006 by Éric Bischoff <ebischoff@nerim.net> *
3 * Copyright (C) 2007 by Albert Astals Cid <aacid@kde.org> *
4 * *
5 * This program is free software; you can redistribute it and/or modify *
6 * it under the terms of the GNU General Public License as published by *
7 * the Free Software Foundation; either version 2 of the License, or *
8 * (at your option) any later version. *
9 ***************************************************************************/
10
11/* Top level window */
12
13#include "toplevel.h"
14
15#include <kapplication.h>
16#include <kmessagebox.h>
17#include <kfiledialog.h>
18#include <klocale.h>
19#include <kstandarddirs.h>
20#include <kio/netaccess.h>
21#include <kaction.h>
22#include <kstandardaction.h>
23#include <kstandardshortcut.h>
24#include <kstandardgameaction.h>
25#include <kactioncollection.h>
26#include <ktoggleaction.h>
27#include <ktogglefullscreenaction.h>
28#include <kimageio.h>
29#include <kmimetype.h>
30#include <kconfiggroup.h>
31#include <ktemporaryfile.h>
32#include <kdeprintdialog.h>
33#include <kcombobox.h>
34
35#include <QClipboard>
36#include <QPrintDialog>
37#include <QPrinter>
38
39#include "playground.h"
40#include "soundfactory.h"
41#include "playgrounddelegate.h"
42
43static const char *DEFAULT_THEME = "default_theme.theme";
44
45// Constructor
46TopLevel::TopLevel()
47 : KXmlGuiWindow(0)
48{
49 QString board, language;
50
51 playGround = new PlayGround(this);
52 playGround->setObjectName( QLatin1String( "playGround" ) );
53
54 soundFactory = new SoundFactory(this);
55
56 setCentralWidget(playGround);
57
58 playgroundsGroup = new QActionGroup(this);
59 playgroundsGroup->setExclusive(true);
60
61 languagesGroup = new QActionGroup(this);
62 languagesGroup->setExclusive(true);
63
64 setupKAction();
65
66 playGround->registerPlayGrounds();
67 soundFactory->registerLanguages();
68
69 readOptions(board, language);
70 changeGameboard(board);
71 changeLanguage(language);
72}
73
74// Destructor
75TopLevel::~TopLevel()
76{
77 delete soundFactory;
78}
79
80static bool actionSorterByName(QAction *a, QAction *b)
81{
82 return a->text().localeAwareCompare(b->text()) < 0;
83}
84
85// Register an available gameboard
86void TopLevel::registerGameboard(const QString &menuText, const QString &board, const QPixmap &pixmap)
87{
88 KToggleAction *t = new KToggleAction(menuText, this);
89 actionCollection()->addAction(board, t);
90 t->setData(board);
91 connect(t, SIGNAL(toggled(bool)), SLOT(changeGameboard()));
92 playgroundsGroup->addAction(t);
93 QList<QAction*> actionList = playgroundsGroup->actions();
94 qSort(actionList.begin(), actionList.end(), actionSorterByName);
95 unplugActionList( QLatin1String( "playgroundList" ) );
96 plugActionList( QLatin1String( "playgroundList" ), actionList );
97
98 playgroundCombo->addItem(menuText,QVariant(pixmap));
99 playgroundCombo->setItemData(playgroundCombo->count()-1,QVariant(board),BOARD_THEME);
100}
101
102// Register an available language
103void TopLevel::registerLanguage(const QString &code, const QString &soundFile, bool enabled)
104{
105 KToggleAction *t = new KToggleAction(KGlobal::locale()->languageCodeToName(code), this);
106 t->setEnabled(enabled);
107 actionCollection()->addAction(soundFile, t);
108 t->setData(soundFile);
109 sounds.insert(code, soundFile);
110 connect(t, SIGNAL(toggled(bool)), SLOT(changeLanguage()));
111 languagesGroup->addAction(t);
112 QList<QAction*> actionList = languagesGroup->actions();
113 actionList.removeAll(actionCollection()->action(QLatin1String( "speech_no_sound" )));
114 qSort(actionList.begin(), actionList.end(), actionSorterByName);
115 unplugActionList( QLatin1String( "languagesList" ) );
116 plugActionList( QLatin1String( "languagesList" ), actionList );
117}
118
119// Switch to another gameboard
120void TopLevel::changeGameboardFromCombo(int index)
121{
122 QString newBoard = playgroundCombo->itemData(index,BOARD_THEME).toString();
123 changeGameboard(newBoard);
124}
125
126void TopLevel::changeGameboard()
127{
128 QAction *action = qobject_cast<QAction*>(sender());
129 // ignore toggling of "nonchecked" actions
130 if (action->isChecked())
131 {
132 QString newGameBoard = action->data().toString();
133 changeGameboard(newGameBoard);
134 }
135}
136
137void TopLevel::changeGameboard(const QString &newGameBoard)
138{
139 if (newGameBoard == playGround->currentGameboard()) return;
140
141 QString fileToLoad;
142 QFileInfo fi(newGameBoard);
143 if (fi.isRelative())
144 {
145 QStringList list = KGlobal::dirs()->findAllResources("appdata", QLatin1String( "pics/" ) + newGameBoard);
146 if (!list.isEmpty()) fileToLoad = list.first();
147 }
148 else
149 {
150 fileToLoad = newGameBoard;
151 }
152
153 int index = playgroundCombo->findData(fileToLoad, BOARD_THEME);
154 playgroundCombo->setCurrentIndex(index);
155 QAction *action = actionCollection()->action(fileToLoad);
156 if (action && playGround->loadPlayGround(fileToLoad))
157 {
158 action->setChecked(true);
159
160 // Change gameboard in the remembered options
161 writeOptions();
162 }
163 else
164 {
165 // Something bad just happened, try the default playground
166 if (newGameBoard != QLatin1String(DEFAULT_THEME))
167 {
168 changeGameboard(QLatin1String(DEFAULT_THEME));
169 }
170 else
171 {
172 KMessageBox::error(this, i18n("Error while loading the playground."));
173 }
174 }
175}
176
177void TopLevel::changeLanguage()
178{
179 QAction *action = qobject_cast<QAction*>(sender());
180 QString soundFile = action->data().toString();
181 changeLanguage(soundFile);
182}
183
184// Switch to another language
185void TopLevel::changeLanguage(const QString &soundFile)
186{
187 if (soundFile == soundFactory->currentSoundFile()) return;
188
189 QString fileToLoad;
190 QFileInfo fi(soundFile);
191 if (fi.isRelative())
192 {
193 const QStringList list = KGlobal::dirs()->findAllResources("appdata", QLatin1String( "sounds/" ) + soundFile);
194 if (!list.isEmpty()) fileToLoad = list.first();
195 }
196 else
197 {
198 fileToLoad = soundFile;
199 }
200
201 // Change language effectively
202 QAction *action = actionCollection()->action(fileToLoad);
203 if (action && soundFactory->loadLanguage(fileToLoad))
204 {
205 action->setChecked(true);
206
207 // Change language in the remembered options
208 writeOptions();
209 }
210 else
211 {
212 KMessageBox::error(this, i18n("Error while loading the sound file."));
213 soundOff();
214 }
215}
216
217// Play a sound
218void TopLevel::playSound(const QString &ref) const
219{
220 soundFactory->playSound(ref);
221}
222
223// Read options from preferences file
224void TopLevel::readOptions(QString &board, QString &language)
225{
226 KConfigGroup config(KGlobal::config(), "General");
227
228 QString option = config.readEntry("Sound", "on" );
229 bool soundEnabled = option.indexOf(QLatin1String( "on" )) == 0;
230 board = config.readEntry("Gameboard", DEFAULT_THEME);
231 language = config.readEntry("Language", "" );
232 bool keepAspectRatio = config.readEntry("KeepAspectRatio", false);
233
234 if (soundEnabled)
235 {
236 if (language.isEmpty())
237 {
238 language = sounds.value(KGlobal::locale()->language(), QLatin1String( "en.soundtheme" ));
239 }
240 }
241 else
242 {
243 soundOff();
244 language = QString();
245 }
246
247 lockAspectRatio(keepAspectRatio);
248}
249
250// Write options to preferences file
251void TopLevel::writeOptions()
252{
253 KConfigGroup config(KGlobal::config(), "General");
254 config.writeEntry("Sound", actionCollection()->action(QLatin1String( "speech_no_sound" ))->isChecked() ? "off": "on");
255
256 config.writeEntry("Gameboard", playGround->currentGameboard());
257
258 config.writeEntry("Language", soundFactory->currentSoundFile());
259
260 config.writeEntry("KeepAspectRatio", playGround->isAspectRatioLocked());
261}
262
263// KAction initialization (aka menubar + toolbar init)
264void TopLevel::setupKAction()
265{
266 QAction *action;
267
268 //Game
269 KStandardGameAction::gameNew(this, SLOT(fileNew()), actionCollection());
270 KStandardGameAction::load(this, SLOT(fileOpen()), actionCollection());
271 KStandardGameAction::save(this, SLOT(fileSave()), actionCollection());
272 KStandardGameAction::print(this, SLOT(filePrint()), actionCollection());
273 KStandardGameAction::quit(kapp, SLOT(quit()), actionCollection());
274
275 action = actionCollection()->addAction( QLatin1String( "game_save_picture" ));
276 action->setText(i18n("Save &as Picture..."));
277 connect(action, SIGNAL(triggered(bool)), SLOT(filePicture()));
278
279 //Edit
280 action = KStandardAction::copy(this, SLOT(editCopy()), actionCollection());
281 actionCollection()->addAction(action->objectName(), action);
282
283 action = KStandardAction::undo(0, 0, actionCollection());
284 playGround->connectUndoAction(action);
285 action = KStandardAction::redo(0, 0, actionCollection());
286 playGround->connectRedoAction(action);
287
288 //Speech
289 KToggleAction *t = new KToggleAction(i18n("&No Sound"), this);
290 actionCollection()->addAction( QLatin1String( "speech_no_sound" ), t);
291 connect(t, SIGNAL(triggered(bool)), SLOT(soundOff()));
292 languagesGroup->addAction(t);
293
294 KStandardAction::fullScreen(this, SLOT(toggleFullScreen()), this, actionCollection());
295
296 t = new KToggleAction(i18n("&Lock Aspect Ratio"), this);
297 actionCollection()->addAction( QLatin1String( "lock_aspect_ratio" ), t);
298 connect(t, SIGNAL(triggered(bool)), this, SLOT(lockAspectRatio(bool)));
299
300 playgroundCombo = new KComboBox(this);
301 playgroundCombo->setMinimumWidth(200);
302 playgroundCombo->view()->setMinimumHeight(100);
303 playgroundCombo->view()->setMinimumWidth(200);
304 playgroundCombo->view()->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
305
306 PlaygroundDelegate *playgroundDelegate = new PlaygroundDelegate(playgroundCombo->view());
307 playgroundCombo->setItemDelegate(playgroundDelegate);
308
309 connect(playgroundCombo, SIGNAL(currentIndexChanged(int)),this,SLOT(changeGameboardFromCombo(int)));
310
311 KAction *widgetAction = new KAction(this);
312 widgetAction->setDefaultWidget(playgroundCombo);
313 actionCollection()->addAction( QLatin1String( "playgroundSelection" ),widgetAction);
314
315 setupGUI(ToolBar | Keys | Save | Create);
316}
317
318void TopLevel::saveNewToolbarConfig()
319{
320 // this destroys our actions lists ...
321 KXmlGuiWindow::saveNewToolbarConfig();
322 // ... so plug them again
323 plugActionList( QLatin1String( "playgroundList" ), playgroundsGroup->actions() );
324 plugActionList( QLatin1String( "languagesList" ), languagesGroup->actions() );
325}
326
327// Reset gameboard
328void TopLevel::fileNew()
329{
330 playGround->reset();
331}
332
333// Load gameboard
334void TopLevel::fileOpen()
335{
336 KUrl url = KFileDialog::getOpenUrl(KUrl("kfiledialog:///<ktuberling>"),
337 QString::fromLatin1("*.tuberling|%1\n*|%2").arg(i18n("KTuberling files"), i18n("All files")));
338
339 open(url);
340}
341
342void TopLevel::open(const KUrl &url)
343{
344 if (url.isEmpty())
345 return;
346
347 QString name;
348
349 KIO::NetAccess::download(url, name, this);
350
351 switch(playGround->loadFrom(name))
352 {
353 case PlayGround::NoError:
354 // good
355 break;
356
357 case PlayGround::OldFileVersionError:
358 KMessageBox::error(this, i18n("The saved file is from an old version of KTuberling and unfortunately cannot be opened with this version."));
359 break;
360
361 case PlayGround::OtherError:
362 KMessageBox::error(this, i18n("Could not load file."));
363 break;
364 }
365
366
367 KIO::NetAccess::removeTempFile( name );
368}
369
370// Save gameboard
371void TopLevel::fileSave()
372{
373 KUrl url = KFileDialog::getSaveUrl
374 ( KUrl("kfiledialog:///<ktuberling>"),
375 QString::fromLatin1("*.tuberling|%1").arg(i18n("KTuberling files")), this, QString(), KFileDialog::ConfirmOverwrite);
376
377 if (url.isEmpty())
378 return;
379
380 KTemporaryFile tempFile; // for network saving
381 QString name;
382 if( !url.isLocalFile() )
383 {
384 if (tempFile.open()) name = tempFile.fileName();
385 else
386 {
387 KMessageBox::error(this, i18n("Could not save file."));
388 return;
389 }
390 }
391 else
392 {
393 name = url.path();
394 }
395
396 if( !playGround->saveAs( name ) )
397 {
398 KMessageBox::error(this, i18n("Could not save file."));
399 return;
400 }
401
402 if( !url.isLocalFile() )
403 {
404 if (!KIO::NetAccess::upload(name, url, this))
405 KMessageBox::error(this, i18n("Could not save file."));
406 }
407}
408
409// Save gameboard as picture
410void TopLevel::filePicture()
411{
412 const QString patternsString = KImageIO::pattern(KImageIO::Writing);
413 QStringList patterns = patternsString.split("\n");
414 // Favor png
415 if (!patterns.isEmpty()) {
416 QString firstLine = patterns[0];
417 patterns.removeAt(0);
418 if (firstLine.contains(" *.png")) {
419 firstLine.remove(" *.png");
420 firstLine.prepend("*.png ");
421 }
422 patterns.prepend(firstLine);
423 }
424 const KUrl url = KFileDialog::getSaveUrl(KUrl(QString("kfiledialog:///<ktuberling>")), patterns.join("\n"), this, QString(), KFileDialog::ConfirmOverwrite);
425
426 if( url.isEmpty() )
427 return;
428
429 KTemporaryFile tempFile; // for network saving
430 QString name;
431 if( !url.isLocalFile() )
432 {
433 tempFile.open();
434 name = tempFile.fileName();
435 }
436 else
437 {
438 name = url.path();
439 }
440
441 KMimeType::Ptr mime = KMimeType::findByUrl(url, 0, true, true);
442 if (!KImageIO::isSupported(mime->name(), KImageIO::Writing))
443 {
444 KMessageBox::error(this, i18n("Unknown picture format."));
445 return;
446 };
447
448 QStringList types = KImageIO::typeForMime(mime->name());
449 if (types.isEmpty()) return; // TODO error dialog?
450
451 QPixmap picture(playGround->getPicture());
452
453 if (!picture.save(name, types.at(0).toLatin1()))
454 {
455 KMessageBox::error
456 (this, i18n("Could not save file."));
457 return;
458 }
459
460 if( !url.isLocalFile() )
461 {
462 if (! KIO::NetAccess::upload(name, url, this))
463 KMessageBox::error(this, i18n("Could not save file."));
464 }
465
466}
467
468// Save gameboard as picture
469void TopLevel::filePrint()
470{
471 QPrinter printer;
472 bool ok;
473
474 QPrintDialog *printDialog = KdePrint::createPrintDialog(&printer, this);
475 printDialog->setWindowTitle(i18n("Print %1", actionCollection()->action(playGround->currentGameboard())->iconText()));
476 ok = printDialog->exec();
477 delete printDialog;
478 if (!ok) return;
479 playGround->repaint();
480 if (!playGround->printPicture(printer))
481 KMessageBox::error(this,
482 i18n("Could not print picture."));
483 else
484 KMessageBox::information(this,
485 i18n("Picture successfully printed."));
486}
487
488// Copy modified area to clipboard
489void TopLevel::editCopy()
490{
491 QClipboard *clipboard = QApplication::clipboard();
492 QPixmap picture(playGround->getPicture());
493
494 clipboard->setPixmap(picture);
495}
496
497// Toggle sound off
498void TopLevel::soundOff()
499{
500 actionCollection()->action(QLatin1String( "speech_no_sound" ))->setChecked(true);
501 writeOptions();
502}
503
504bool TopLevel::isSoundEnabled() const
505{
506 return !actionCollection()->action(QLatin1String( "speech_no_sound" ))->isChecked();
507}
508
509void TopLevel::toggleFullScreen()
510{
511 KToggleFullScreenAction::setFullScreen( this, actionCollection()->action(QLatin1String( "fullscreen" ))->isChecked());
512}
513
514void TopLevel::lockAspectRatio(bool lock)
515{
516 actionCollection()->action(QLatin1String( "lock_aspect_ratio" ))->setChecked(lock);
517 playGround->lockAspectRatio(lock);
518 writeOptions();
519}
520