toplevel.cpp 15.1 KB
Newer Older
1
2
3
4
5
6
7
8
9
10
11
/***************************************************************************
 *   Copyright (C) 1999-2006 by Éric Bischoff <ebischoff@nerim.net>        *
 *   Copyright (C) 2007 by Albert Astals Cid <aacid@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.                                   *
 ***************************************************************************/

/* Top level window */
12

Aleix Pol Gonzalez's avatar
   
Aleix Pol Gonzalez committed
13
14
#include "toplevel.h"

Hans Petter Bieker's avatar
Hans Petter Bieker committed
15
#include <kmessagebox.h>
Laurent Montel's avatar
Laurent Montel committed
16
#include <KLocalizedString>
17
#include <KLanguageName>
Aaron J. Seigo's avatar
Aaron J. Seigo committed
18
#include <kstandardaction.h>
19
#include <kstandardshortcut.h>
20
#include <kstandardgameaction.h>
Laurent Montel's avatar
Compile    
Laurent Montel committed
21
22
#include <kactioncollection.h>
#include <ktoggleaction.h>
23
#include <ktogglefullscreenaction.h>
Pino Toscano's avatar
Pino Toscano committed
24
#include <kconfiggroup.h>
25
#include <kcombobox.h>
26
#include <ksharedconfig.h>
Albert Astals Cid's avatar
Albert Astals Cid committed
27
#include <kio/job.h>
28

29
#include <QApplication>
30
#include <QClipboard>
31
#include <QFileDialog>
32
#include <QFileInfo>
33
34
#include <QImageWriter>
#include <QMimeDatabase>
Albert Astals Cid's avatar
Albert Astals Cid committed
35
36
#include <QPrintDialog>
#include <QPrinter>
37
38
#include <QTemporaryFile>
#include <QWidgetAction>
39

40
#include "filefactory.h"
41
#include "playground.h"
42
#include "soundfactory.h"
43
#include "playgrounddelegate.h"
44

45

46
47
static const char *DEFAULT_THEME = "default_theme.theme";

48
49
// Constructor
TopLevel::TopLevel()
Kevin Ottens's avatar
Kevin Ottens committed
50
  : KXmlGuiWindow(0)
51
{
52
  QString board, language;
Éric Bischoff's avatar
Éric Bischoff committed
53

54
  playGround = new PlayGround(this, this);
55
  playGround->setObjectName( QStringLiteral( "playGround" ) );
56
57

  soundFactory = new SoundFactory(this);
58

59
  setCentralWidget(playGround);
Éric Bischoff's avatar
Éric Bischoff committed
60

61
62
63
64
65
66
  playgroundsGroup = new QActionGroup(this);
  playgroundsGroup->setExclusive(true);

  languagesGroup = new QActionGroup(this);
  languagesGroup->setExclusive(true);

67
  setupKAction();
68

69
70
  playGround->registerPlayGrounds();
  soundFactory->registerLanguages();
71

72
  readOptions(board, language);
73
74
  changeGameboard(board);
  changeLanguage(language);
75
76
77
78
79
}

// Destructor
TopLevel::~TopLevel()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
80
  delete soundFactory;
81
82
}

83
84
85
86
87
static bool actionSorterByName(QAction *a, QAction *b)
{
  return a->text().localeAwareCompare(b->text()) < 0;
}

88
// Register an available gameboard
89
void TopLevel::registerGameboard(const QString &menuText, const QString &board, const QPixmap &pixmap)
90
{
91
  KToggleAction *t = new KToggleAction(menuText, this);
92
  actionCollection()->addAction(board, t);
93
94
95
96
97
98
  connect(t, &KToggleAction::toggled, this, [this, t, board] {
    if (t->isChecked())
    {
      changeGameboard(board);
    }
  });
99
  playgroundsGroup->addAction(t);
100
  QList<QAction*> actionList = playgroundsGroup->actions();
Laurent Montel's avatar
Laurent Montel committed
101
  std::sort(actionList.begin(), actionList.end(), actionSorterByName);
102
103
  unplugActionList( QStringLiteral( "playgroundList" ) );
  plugActionList( QStringLiteral( "playgroundList" ), actionList );
104
105
106

  playgroundCombo->addItem(menuText,QVariant(pixmap));
  playgroundCombo->setItemData(playgroundCombo->count()-1,QVariant(board),BOARD_THEME);
107
108
}

109
// Register an available language
110
void TopLevel::registerLanguage(const QString &code, const QString &soundFile, bool enabled)
111
{
112
  KToggleAction *t = new KToggleAction(KLanguageName::nameForCode(code), this);
113
  t->setEnabled(enabled);
114
  actionCollection()->addAction(soundFile, t);
115
  sounds.insert(code, soundFile);
116
117
118
  connect(t, &KToggleAction::toggled, this, [this, soundFile] {
    changeLanguage(soundFile);
  });
119
  languagesGroup->addAction(t);
120
  QList<QAction*> actionList = languagesGroup->actions();
121
  actionList.removeAll(actionCollection()->action(QStringLiteral( "speech_no_sound" )));
Laurent Montel's avatar
Laurent Montel committed
122
  std::sort(actionList.begin(), actionList.end(), actionSorterByName);
123
124
  unplugActionList( QStringLiteral( "languagesList" ) );
  plugActionList( QStringLiteral( "languagesList" ), actionList );
125
126
}

127
// Switch to another gameboard
128
129
130
131
132
133
void TopLevel::changeGameboardFromCombo(int index)
{
  QString newBoard = playgroundCombo->itemData(index,BOARD_THEME).toString();
  changeGameboard(newBoard);
}

134
135
136
void TopLevel::changeGameboard(const QString &newGameBoard)
{
  if (newGameBoard == playGround->currentGameboard()) return;
137

138
139
140
141
  QString fileToLoad;
  QFileInfo fi(newGameBoard);
  if (fi.isRelative())
  {
142
    fileToLoad = FileFactory::locate(QLatin1String( "pics/" ) + newGameBoard);
143
144
145
146
147
  }
  else
  {
    fileToLoad = newGameBoard;
  }
148

149
150
  int index = playgroundCombo->findData(fileToLoad, BOARD_THEME);
  playgroundCombo->setCurrentIndex(index);
151
152
  QAction *action = actionCollection()->action(fileToLoad);
  if (action && playGround->loadPlayGround(fileToLoad))
153
  {
154
    action->setChecked(true);
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
155

156
157
158
159
160
    // Change gameboard in the remembered options
    writeOptions();
  }
  else
  {
161
    // Something bad just happened, try the default playground
Albert Astals Cid's avatar
Albert Astals Cid committed
162
    if (newGameBoard != QLatin1String(DEFAULT_THEME))
163
    {
Albert Astals Cid's avatar
Albert Astals Cid committed
164
      changeGameboard(QLatin1String(DEFAULT_THEME));
165
166
167
168
169
    }
    else
    {
      KMessageBox::error(this, i18n("Error while loading the playground."));
    }
170
  }
171
}
172

173
// Switch to another language
174
void TopLevel::changeLanguage(const QString &soundFile)
175
{
176
  if (soundFile == soundFactory->currentSoundFile()) return;
177

178
179
180
181
  QString fileToLoad;
  QFileInfo fi(soundFile);
  if (fi.isRelative())
  {
182
    fileToLoad = FileFactory::locate(QLatin1String( "sounds/" ) + soundFile);
183
184
185
186
187
  }
  else
  {
    fileToLoad = soundFile;
  }
188

189
  // Change language effectively
190
191
  QAction *action = actionCollection()->action(fileToLoad);
  if (action && soundFactory->loadLanguage(fileToLoad))
192
  {
193
    action->setChecked(true);
194

195
196
197
198
    // Change language in the remembered options
    writeOptions();
  }
  else
199
  {
200
201
    KMessageBox::error(this, i18n("Error while loading the sound file."));
    soundOff();
202
203
204
205
  }
}

// Play a sound
206
void TopLevel::playSound(const QString &ref)
207
208
209
210
{
  soundFactory->playSound(ref);
}

211
// Read options from preferences file
212
void TopLevel::readOptions(QString &board, QString &language)
213
{
Laurent Montel's avatar
Laurent Montel committed
214
  KConfigGroup config(KSharedConfig::openConfig(), "General");
215

216
217
  QString option = config.readEntry("Sound",  "on" );
  bool soundEnabled = option.indexOf(QLatin1String( "on" )) == 0;
218
  board = config.readEntry("Gameboard", DEFAULT_THEME);
219
  language = config.readEntry("Language", "" );
220
221
  bool keepAspectRatio = config.readEntry("KeepAspectRatio", false);

222
  if (soundEnabled && language.isEmpty())
223
  {
224
225
226
227
228
229
230
231
232
233
    const QStringList &systemLanguages = KLocalizedString::languages();
    for (const QString &systemLanguage : systemLanguages)
    {
      QString sound = sounds.value(systemLanguage);
      if (!sound.isEmpty())
      {
        language = sound;
        break;
      }
    }
234
235
    if (language.isEmpty())
    {
236
      language = QStringLiteral("en.soundtheme");
237
238
    }
  }
239
240

  if (!soundEnabled)
241
242
243
244
  {
    soundOff();
    language = QString();
  }
245

246
  lockAspectRatio(keepAspectRatio);
247
248
249
250
251
}

// Write options to preferences file
void TopLevel::writeOptions()
{
Laurent Montel's avatar
Laurent Montel committed
252
  KConfigGroup config(KSharedConfig::openConfig(), "General");
253
  config.writeEntry("Sound", actionCollection()->action(QStringLiteral( "speech_no_sound" ))->isChecked() ? "off": "on");
254

255
  config.writeEntry("Gameboard", playGround->currentGameboard());
256

257
  config.writeEntry("Language", soundFactory->currentSoundFile());
258

259
  config.writeEntry("KeepAspectRatio", playGround->isAspectRatioLocked());
260
261
}

262
// KAction initialization (aka menubar + toolbar init)
263
void TopLevel::setupKAction()
264
{
265
266
  QAction *action;

Simon Huerlimann's avatar
Simon Huerlimann committed
267
268
269
270
271
  //Game
  KStandardGameAction::gameNew(this, SLOT(fileNew()), actionCollection());
  KStandardGameAction::load(this, SLOT(fileOpen()), actionCollection());
  KStandardGameAction::save(this, SLOT(fileSave()), actionCollection());
  KStandardGameAction::print(this, SLOT(filePrint()), actionCollection());
Laurent Montel's avatar
Laurent Montel committed
272
  KStandardGameAction::quit(qApp, SLOT(quit()), actionCollection());
Simon Huerlimann's avatar
Simon Huerlimann committed
273

274
  action = actionCollection()->addAction( QStringLiteral( "game_save_picture" ));
275
  action->setText(i18n("Save &as Picture..."));
276
  connect(action, &QAction::triggered, this, &TopLevel::filePicture);
277

Simon Huerlimann's avatar
Simon Huerlimann committed
278
  //Edit
279
  action = KStandardAction::copy(this, &TopLevel::editCopy, actionCollection());
280
  actionCollection()->addAction(action->objectName(), action);
281

282
283
284
285
  action = KStandardAction::undo(0, 0, actionCollection());
  playGround->connectUndoAction(action);
  action = KStandardAction::redo(0, 0, actionCollection());
  playGround->connectRedoAction(action);
Stephan Binner's avatar
Stephan Binner committed
286

Simon Huerlimann's avatar
Simon Huerlimann committed
287
  //Speech
288
  KToggleAction *t = new KToggleAction(i18n("&No Sound"), this);
289
290
  actionCollection()->addAction( QStringLiteral( "speech_no_sound" ), t);
  connect(t, &QAction::triggered, this, &TopLevel::soundOff);
291
  languagesGroup->addAction(t);
292

293
  KStandardAction::fullScreen(this, &TopLevel::toggleFullScreen, this, actionCollection());
Albert Astals Cid's avatar
Albert Astals Cid committed
294

295
  t = new KToggleAction(i18n("&Lock Aspect Ratio"), this);
296
297
  actionCollection()->addAction( QStringLiteral( "lock_aspect_ratio" ), t);
  connect(t, &QAction::triggered, this, &TopLevel::lockAspectRatio);
298

299
300
301
302
303
304
305
306
  playgroundCombo = new KComboBox(this);
  playgroundCombo->setMinimumWidth(200);
  playgroundCombo->view()->setMinimumHeight(100);
  playgroundCombo->view()->setMinimumWidth(200);
  playgroundCombo->view()->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);

  PlaygroundDelegate *playgroundDelegate = new PlaygroundDelegate(playgroundCombo->view());
  playgroundCombo->setItemDelegate(playgroundDelegate);
307
  connect(playgroundCombo, QOverload<int>::of(&KComboBox::currentIndexChanged), this, &TopLevel::changeGameboardFromCombo);
308
  QWidgetAction *widgetAction = new QWidgetAction(this);
309
  widgetAction->setDefaultWidget(playgroundCombo);
310
  actionCollection()->addAction( QStringLiteral( "playgroundSelection" ),widgetAction);
311

Albert Astals Cid's avatar
Albert Astals Cid committed
312
  setupGUI(ToolBar | Keys | Save | Create);
313
314
}

315
316
317
318
319
void TopLevel::saveNewToolbarConfig()
{
  // this destroys our actions lists ...
  KXmlGuiWindow::saveNewToolbarConfig();
  // ... so plug them again
320
321
  plugActionList( QStringLiteral( "playgroundList" ), playgroundsGroup->actions() );
  plugActionList( QStringLiteral( "languagesList" ), languagesGroup->actions() );
322
323
}

324
325
326
// Reset gameboard
void TopLevel::fileNew()
{
327
  playGround->reset();
328
329
330
331
332
}

// Load gameboard
void TopLevel::fileOpen()
{
333
334
  QUrl url = QFileDialog::getOpenFileUrl(this, i18n("Load file"), QUrl(),
                                     i18n("KTuberling files (%1)", QStringLiteral("*.tuberling")));
Simon Hausmann's avatar
Simon Hausmann committed
335

Waldo Bastian's avatar
Waldo Bastian committed
336
337
338
  open(url);
}

Laurent Montel's avatar
Laurent Montel committed
339
void TopLevel::open(const QUrl &url)
Waldo Bastian's avatar
Waldo Bastian committed
340
{
Michael Koch's avatar
Michael Koch committed
341
342
  if (url.isEmpty())
    return;
Simon Hausmann's avatar
Simon Hausmann committed
343

Waldo Bastian's avatar
Waldo Bastian committed
344
  QString name;
Albert Astals Cid's avatar
Albert Astals Cid committed
345
346
347
348
349
350
351
352
353
354
355
356
357
358
  if (url.isLocalFile()) {
    // file protocol. We do not need the network
    name = url.toLocalFile();
  } else {
    QTemporaryFile tmpFile;
    tmpFile.setAutoRemove(false);
    tmpFile.open();
    name = tmpFile.fileName();
    const QUrl dest = QUrl::fromLocalFile(name);
    KIO::Job *job = KIO::file_copy(url, dest, -1, KIO::Overwrite | KIO::HideProgressInfo);
    QEventLoop eventLoop;
    connect(job, &KIO::Job::result, &eventLoop, &QEventLoop::quit);
    eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
  }
Simon Hausmann's avatar
Simon Hausmann committed
359

360
361
362
363
364
  switch(playGround->loadFrom(name))
  {
    case PlayGround::NoError:
     // good
    break;
Michael Koch's avatar
Michael Koch committed
365

366
    case PlayGround::OldFileVersionError:
367
      KMessageBox::error(this, i18n("The saved file is from an old version of KTuberling and unfortunately cannot be opened with this version."));
368
    break;
369

370
371
372
373
    case PlayGround::OtherError:
      KMessageBox::error(this, i18n("Could not load file."));
    break;
  }
374

Albert Astals Cid's avatar
Albert Astals Cid committed
375
376
377
  if (!url.isLocalFile()) {
    QFile::remove(name);
  }
378
379
380
381
382
}

// Save gameboard
void TopLevel::fileSave()
{
383
  QUrl url = QFileDialog::getSaveFileUrl( this, QString(), QUrl(), i18n("KTuberling files (%1)", QStringLiteral("*.tuberling")) );
Simon Hausmann's avatar
Simon Hausmann committed
384

Michael Koch's avatar
Michael Koch committed
385
386
  if (url.isEmpty())
    return;
Simon Hausmann's avatar
Simon Hausmann committed
387

388
  QTemporaryFile tempFile; // for network saving
389
  QString name;
Michael Koch's avatar
Michael Koch committed
390
  if( !url.isLocalFile() )
391
  {
392
393
394
395
396
397
    if (tempFile.open()) name = tempFile.fileName();
    else
    {
      KMessageBox::error(this, i18n("Could not save file."));
      return;
    }
398
  }
399
  else
400
  {
401
    name = url.path();
402
403
404
  }

  if( !playGround->saveAs( name ) )
405
  {
Hans Petter Bieker's avatar
Hans Petter Bieker committed
406
    KMessageBox::error(this, i18n("Could not save file."));
407
408
    return;
  }
409

410
411
  if( !url.isLocalFile() )
  {
Albert Astals Cid's avatar
Albert Astals Cid committed
412
    if (!upload(name, url))
413
414
      KMessageBox::error(this, i18n("Could not save file."));
  }
415
416
417
418
419
}

// Save gameboard as picture
void TopLevel::filePicture()
{
420
421
422
  const QMimeDatabase mimedb;
  const QList<QByteArray> imageWriterMimetypes = QImageWriter::supportedMimeTypes();
  QStringList patterns;
Laurent Montel's avatar
Laurent Montel committed
423
  for(const auto &mimeName : imageWriterMimetypes)
424
425
426
427
428
429
430
  {
    const QMimeType mime = mimedb.mimeTypeForName(mimeName);
    if (mime.isValid())
    {
      QStringList suffixes;
      for(const QString &suffix : mime.suffixes())
      {
Laurent Montel's avatar
Laurent Montel committed
431
          suffixes << QStringLiteral("*.%1").arg(suffix);
432
433
434
435
436
437
438
439
440
441
442
      }

      // Favor png
      const QString pattern = i18nc("%1 is mimetype and %2 is the file extensions", "%1 (%2)", mime.comment(), suffixes.join(' '));
      if (mimeName == "image/png")
      {
        patterns.prepend(pattern);
      }
      else
      {
        patterns << pattern;
Albert Astals Cid's avatar
Albert Astals Cid committed
443
      }
444
    }
Albert Astals Cid's avatar
Albert Astals Cid committed
445
  }
Laurent Montel's avatar
Laurent Montel committed
446
  const QUrl url = QFileDialog::getSaveFileUrl( this, QString(), QUrl(), patterns.join(QStringLiteral(";;")) );
447

Michael Koch's avatar
Michael Koch committed
448
449
  if( url.isEmpty() )
    return;
Simon Hausmann's avatar
Simon Hausmann committed
450

451
  QTemporaryFile tempFile; // for network saving
452
  QString name;
Michael Koch's avatar
Michael Koch committed
453
  if( !url.isLocalFile() )
454
  {
455
456
457
458
459
460
    tempFile.open();
    name = tempFile.fileName();
  }
  else
  {
    name = url.path();
Michael Koch's avatar
Michael Koch committed
461
  }
Simon Hausmann's avatar
Simon Hausmann committed
462

463
464
  QPixmap picture(playGround->getPicture());

465
  if (!picture.save(name))
466
  {
Hans Petter Bieker's avatar
Hans Petter Bieker committed
467
468
    KMessageBox::error
      (this, i18n("Could not save file."));
469
470
471
472
473
    return;
  }

  if( !url.isLocalFile() )
  {
Albert Astals Cid's avatar
Albert Astals Cid committed
474
    if (!upload(name, url))
475
476
477
      KMessageBox::error(this, i18n("Could not save file."));
  }

478
479
480
481
482
}

// Save gameboard as picture
void TopLevel::filePrint()
{
Albert Astals Cid's avatar
Albert Astals Cid committed
483
  QPrinter printer;
484
  bool ok;
485

486
  QPrintDialog *printDialog = new QPrintDialog(&printer, this);
487
488
489
  printDialog->setWindowTitle(i18n("Print %1", actionCollection()->action(playGround->currentGameboard())->iconText()));
  ok = printDialog->exec();
  delete printDialog;
490
  if (!ok) return;
Dmitry Suzdalev's avatar
Dmitry Suzdalev committed
491
  playGround->repaint();
492
  if (!playGround->printPicture(printer))
Hans Petter Bieker's avatar
Hans Petter Bieker committed
493
    KMessageBox::error(this,
Simon Hausmann's avatar
Simon Hausmann committed
494
                         i18n("Could not print picture."));
495
  else
Hans Petter Bieker's avatar
Hans Petter Bieker committed
496
    KMessageBox::information(this,
Simon Hausmann's avatar
Simon Hausmann committed
497
                             i18n("Picture successfully printed."));
498
499
500
501
502
503
}

// Copy modified area to clipboard
void TopLevel::editCopy()
{
  QClipboard *clipboard = QApplication::clipboard();
504
  QPixmap picture(playGround->getPicture());
505
506
507
508

  clipboard->setPixmap(picture);
}

509
510
511
// Toggle sound off
void TopLevel::soundOff()
{
512
  actionCollection()->action(QStringLiteral( "speech_no_sound" ))->setChecked(true);
513
514
  writeOptions();
}
Albert Astals Cid's avatar
Albert Astals Cid committed
515

516
517
bool TopLevel::isSoundEnabled() const
{
518
  return !actionCollection()->action(QStringLiteral( "speech_no_sound" ))->isChecked();
519
520
}

Albert Astals Cid's avatar
Albert Astals Cid committed
521
522
void TopLevel::toggleFullScreen()
{
523
  KToggleFullScreenAction::setFullScreen( this, actionCollection()->action(QStringLiteral( "fullscreen" ))->isChecked());
Albert Astals Cid's avatar
Albert Astals Cid committed
524
}
525
526
527

void TopLevel::lockAspectRatio(bool lock)
{
528
  actionCollection()->action(QStringLiteral( "lock_aspect_ratio" ))->setChecked(lock);
529
530
531
  playGround->lockAspectRatio(lock);
  writeOptions();
}
Albert Astals Cid's avatar
Albert Astals Cid committed
532
533
534
535
536
537
538
539
540
541
542

bool TopLevel::upload(const QString &src, const QUrl &target)
{
  bool success = true;
  const QUrl srcUrl = QUrl::fromLocalFile(src);
  KIO::Job *job = KIO::file_copy(srcUrl, target, -1, KIO::Overwrite | KIO::HideProgressInfo);
  QEventLoop eventLoop;
  connect(job, &KIO::Job::result, this, [job, &eventLoop, &success] { success = !job->error(); eventLoop.quit(); } );
  eventLoop.exec(QEventLoop::ExcludeUserInputEvents);
  return success;
}