1 /*
2 * This file is part of Auralquiz
3 * Copyright 2011-2017 JanKusanagi JRR <jancoding@gmx.com>
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 * 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 General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * 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 "auralwindow.h"
22
23
24 /*****************************************************************************************
25 *
26 * Constructor
27 */
AuralWindow(QWidget * parent)28 AuralWindow::AuralWindow(QWidget *parent) : QWidget(parent)
29 {
30 this->setWindowTitle("Auralquiz");
31 this->setWindowIcon(QIcon(":/icon/64x64/auralquiz.png"));
32
33 QSettings settings;
34 this->resize(settings.value("size", QSize(800, 540)).toSize());
35 this->firstRun = settings.value("firstRun", true).toBool();
36
37
38 if (firstRun)
39 {
40 qDebug() << "This is the first run";
41 QMessageBox::about(this,
42 "Auralquiz - " + tr("First usage"),
43 tr("This seems to be the first time you use Auralquiz.\n"
44 "Before playing, your music will be analyzed.\n"
45 "If needed, you should click the Options button "
46 "and select the folder where your "
47 "Ogg, FLAC and MP3 files are stored.\n\n"
48 "This folder, and sub-folders will be scanned "
49 "so Auralquiz can generate questions and answers "
50 "about your music.\n"
51 "\n"
52 "You need files correctly tagged in order for "
53 "the game to work correctly.\n"
54 "\n"
55 "The scan can take some time, and the program "
56 "will not be responsive until it is complete. "
57 "Please be patient."));
58 }
59
60 useOwnColorTheme = settings.value("useOwnColorTheme", false).toBool();
61 if (useOwnColorTheme)
62 {
63 qDebug() << "Using own color theme";
64 }
65 else
66 {
67 qDebug() << "Using system colors";
68 }
69
70
71 QString defaultMusicDirectory;
72 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
73 defaultMusicDirectory = QDesktopServices::storageLocation(QDesktopServices::MusicLocation);
74 #else
75 defaultMusicDirectory = QStandardPaths::standardLocations(QStandardPaths::MusicLocation).first();
76 #endif
77
78 // If MusicLocation is the same as HOME, don't use it.
79 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
80 if (defaultMusicDirectory == QDesktopServices::storageLocation(QDesktopServices::HomeLocation))
81 {
82 defaultMusicDirectory.clear();
83 }
84 #else
85 if (defaultMusicDirectory == QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first())
86 {
87 defaultMusicDirectory.clear();
88 }
89 #endif
90
91 musicDirectory = settings.value("musicDirectory",
92 defaultMusicDirectory).toString();
93 if (!musicDirectory.isEmpty() && !musicDirectory.endsWith("/"))
94 {
95 // Adding final "/" if it's not present in chosen path
96 musicDirectory.append("/");
97 }
98 qDebug() << "Music directory:" << this->musicDirectory;
99
100
101 // Get data directory path
102 #if QT_VERSION < QT_VERSION_CHECK(5, 0, 0)
103 this->dataDirectory = QDesktopServices::storageLocation(QDesktopServices::DataLocation);
104 #else
105 this->dataDirectory = QStandardPaths::standardLocations(QStandardPaths::DataLocation).first();
106 #endif
107 qDebug() << "Data directory:" << this->dataDirectory;
108
109
110 // Create data directory if needed, to store music info later...
111 QDir dataDir;
112 if (!dataDir.exists(this->dataDirectory))
113 {
114 qDebug() << "Data directory did not exist. Creating...";
115 if (dataDir.mkpath(this->dataDirectory))
116 {
117 qDebug() << this->dataDirectory << "directory created successfully!";
118 }
119 else
120 {
121 qDebug() << this->dataDirectory << "directory could NOT be created";
122 }
123 }
124
125 difficultyLevel = settings.value("difficultyLevel",
126 2).toInt(); // normal(2) by default
127 numQuestions = settings.value("numQuestions",
128 25).toInt(); // 25 questions by default
129 numPlayers = settings.value("numPlayers",
130 1).toInt(); // 1 player by default
131
132
133 qDebug() << "Phonon runtime version:" << Phonon::phononVersion();
134 qDebug() << "Built with Phonon:" << PHONON_VERSION_STR;
135 qDebug() << "* Backend::audioEffects:"
136 << Phonon::BackendCapabilities::availableAudioEffects();
137 qDebug() << "* Backend::mimeTypes:"
138 << Phonon::BackendCapabilities::availableMimeTypes();
139 //qDebug() << "* Backend::audioOutputDevices:"
140 // << Phonon::BackendCapabilities::availableAudioOutputDevices();
141
142
143 playing = false;
144
145 mainLayout = new QVBoxLayout(this);
146 mainLayout->setContentsMargins(3, 1, 3, 1);
147
148 initWelcomeScreen();
149 initPlayingScreen();
150
151
152 this->playerNames.clear();
153 playerNames = settings.value("playerNames",
154 QStringList() << "1" << "2" // overriden
155 << "3" << "4" // in OptionsDialog
156 << "5" << "6"
157 << "7" << "8").toStringList();
158
159
160 this->setLayout(mainLayout);
161
162 musicAnalyzer = new MusicAnalyzer(musicDirectory, dataDirectory,
163 musicFiles, this);
164 connect(musicAnalyzer, SIGNAL(setStartGameButton(bool,QString,QString)),
165 this, SLOT(modifyStartGameButton(bool,QString,QString)));
166
167
168 postInitTimer = new QTimer(this);
169 postInitTimer->setSingleShot(true);
170 postInitTimer->setInterval(500);
171 connect(postInitTimer, SIGNAL(timeout()),
172 musicAnalyzer, SLOT(loadSongList()));
173 postInitTimer->start(); // Call loadSongList() from the timer, to avoid the
174 // first-run analyzing all songs without visible window
175
176
177 // this timer will call createSongList() to reload music, after config update
178 postConfigUpdatedTimer = new QTimer(this);
179 postConfigUpdatedTimer->setSingleShot(true);
180 postConfigUpdatedTimer->setInterval(500);
181 connect(postConfigUpdatedTimer, SIGNAL(timeout()),
182 musicAnalyzer, SLOT(createSongList()));
183
184
185 // Timer used to show the Ranking window after a moment
186 rankingTimer = new QTimer(this);
187
188
189 // TEMPORARY ranking tests - START
190 #if 0
191 this->goodAnswers.clear();
192 goodAnswers << 3 << 17 << 3 << 4 << 5 << 6 << 8 << 8;
193 this->badAnswers.clear();
194 badAnswers << 11 << 22 << 33 << 44 << 55 << 66 << 77 << 88;
195 this->timedOutAnswers.clear();
196 timedOutAnswers << 111 << 222 << 333 << 444 << 555 << 666 << 777 << 888;
197 this->score.clear();
198 score << goodAnswers[0]*123 << goodAnswers[1]*123 << goodAnswers[2]*123
199 << goodAnswers[3]*123 << goodAnswers[4]*123 << goodAnswers[5]*123
200 << goodAnswers[6]*123 << goodAnswers[7]*123;
201
202 qDebug() << "Testing scores:" << score;
203 Ranking *rankingTest;
204 rankingTest = new Ranking(this->score.length(), this->playerNames, this->score,
205 this->goodAnswers, this->badAnswers, this->timedOutAnswers);
206 rankingTest->show();
207 qDebug() << "test ranking created and shown";
208 #endif
209 // TEMPORARY ranking tests - END
210 }
211
212
213 /*********************************************************************************
214 *
215 * Destructor
216 */
~AuralWindow()217 AuralWindow::~AuralWindow()
218 {
219 qDebug() << "AuralWindow destroyed";
220 }
221
222
223
224
225 /*******************************************************************************
226 *
227 * Shuffle song list
228 */
shuffleMusicFiles()229 void AuralWindow::shuffleMusicFiles()
230 {
231 uint randomSeed;
232 randomSeed = (QTime::currentTime().hour())
233 + (QTime::currentTime().minute() * 4)
234 + (QTime::currentTime().second() * 5)
235 + (QTime::currentTime().msec() * 6);
236 randomSeed *= 8;
237 randomSeed += qrand() % (randomSeed / (QTime::currentTime().second()+1));
238
239
240 qsrand(randomSeed); // ensure decent randomness based on current time
241
242
243
244 int newPosition;
245 for (int counter=0; counter != musicFiles[0].length(); ++counter)
246 { newPosition = qrand() % musicFiles[0].length();
247 musicFiles[0].swap(0, newPosition); // filename
248 musicFiles[1].swap(0, newPosition); // artist
249 musicFiles[2].swap(0, newPosition); // title
250 }
251 qDebug() << "Music Files shuffled. randomSeed:" << randomSeed;
252 }
253
254
255
256 /*
257 * Update statistics panel
258 */
updateStatistics()259 void AuralWindow::updateStatistics()
260 {
261 QString statsTable;
262 statsTable = "<table width=95%>"
263 "<tr>"
264 "<td valign=middle>" + tr("Good") + "</td>"
265 "<td> </td>"
266 "<td valign=middle align=right>"
267 + QString("<b>%1</b>").arg(this->goodAnswers[currentPlayer])
268 + "</td></tr>"
269
270 "<tr></tr>"
271
272 "<tr>"
273 "<td valign=middle>" + tr("Bad") + "</td>"
274 "<td> </td>"
275 "<td valign=middle align=right>"
276 + QString("<b>%1</b>").arg(this->badAnswers[currentPlayer])
277 + "</td></tr>"
278
279 "<tr></tr>"
280
281 "<tr>"
282 "<td valign=middle>" + tr("Timed out") + "</td>"
283 "<td> </td>"
284 "<td valign=middle align=right>"
285 + QString("<b>%1</b>").arg(this->timedOutAnswers[currentPlayer])
286 + "</tr>"
287 "</table>";
288
289 this->statisticsLabel->setText(statsTable);
290 }
291
292
293 /*****************************************************************************************
294 *
295 * Set up the welcome screen, with the logo and main menu
296 */
initWelcomeScreen()297 void AuralWindow::initWelcomeScreen()
298 {
299 qDebug() << "Init welcome screen";
300
301 optionsDialog = new OptionsDialog(this);
302 connect(optionsDialog, SIGNAL(optionsChanged(bool,QString,bool,int,int,int,QStringList,bool)),
303 this, SLOT(updateConfig(bool,QString,bool,int,int,int,QStringList,bool)));
304
305
306 logoLabel = new QLabel(this);
307 logoLabel->setPixmap(QPixmap(":/images/logo.png"));
308
309
310 startGameButton = new QPushButton(QIcon::fromTheme("media-playback-start",
311 QIcon(":/images/button-arrow.png")),
312 "\n" + tr("&Start game") + "\n",
313 this);
314 connect(startGameButton, SIGNAL(clicked()),
315 optionsDialog, SLOT(showPlayMode()));
316 startGameButton->setDisabled(true);
317
318
319 configureButton = new QPushButton(QIcon::fromTheme("configure",
320 QIcon(":/images/button-configure.png")),
321 tr("&Options"),
322 this);
323 connect(configureButton, SIGNAL(clicked()),
324 optionsDialog, SLOT(showConfigMode()));
325
326
327 aboutButton = new QPushButton(QIcon::fromTheme("help-about"),
328 tr("&About..."),
329 this);
330 connect(aboutButton, SIGNAL(clicked()),
331 this, SLOT(showAbout()));
332
333 quitButton = new QPushButton(QIcon::fromTheme("application-exit"),
334 tr("&Quit"),
335 this);
336 connect(quitButton, SIGNAL(clicked()),
337 this, SLOT(close()));
338
339 // Control+Q is handled separately to avoid disabling Alt+Q (or translated equivalent)
340 quitAction = new QAction(this);
341 quitAction->setShortcut(QKeySequence("Ctrl+Q"));
342 connect(quitAction, SIGNAL(triggered()),
343 this, SLOT(close()));
344 this->addAction(quitAction);
345
346
347 welcomeLayout = new QVBoxLayout();
348 welcomeLayout->setAlignment(Qt::AlignHCenter);
349
350 welcomeLayout->addWidget(logoLabel);
351 welcomeLayout->addSpacing(24);
352
353 welcomeLayout->addWidget(startGameButton);
354 welcomeLayout->addSpacing(16);
355
356 welcomeLayout->addWidget(configureButton);
357 welcomeLayout->addWidget(aboutButton);
358 welcomeLayout->addWidget(quitButton);
359 welcomeLayout->addSpacing(16);
360
361 welcomeWidget = new QWidget(this);
362 welcomeWidget->setLayout(welcomeLayout);
363
364 mainLayout->addWidget(welcomeWidget);
365 }
366
367
368
369 /*****************************************************************************************
370 *
371 * Set up the playing screen, with progress bar, time bar, score, etc.
372 */
initPlayingScreen()373 void AuralWindow::initPlayingScreen()
374 {
375 qDebug() << "Init playing screen";
376 playingWidget = new QWidget(this);
377
378
379 QFont playerFont;
380 playerFont.setPointSize(12);
381 playerFont.setBold(true);
382 playerNameLabel = new QLabel(":: PLAYER ::", this);
383 playerNameLabel->setAlignment(Qt::AlignRight | Qt::AlignTop);
384 playerNameLabel->setFont(playerFont);
385
386 QFont questionFont;
387 questionFont.setPointSize(20);
388 questionFont.setBold(true);
389 questionLabel = new QLabel(":: QUESTION ::", this);
390 questionLabel->setAlignment(Qt::AlignCenter);
391 questionLabel->setFont(questionFont);
392 questionLabel->setFrameStyle(QFrame::Raised | QFrame::StyledPanel);
393
394
395 QFont infoFont;
396 infoFont.setPointSize(14);
397 infoFont.setBold(true);
398 infoFont.setItalic(true);
399 infoLabel = new QLabel(":: INFO ::", this);
400 infoLabel->setAlignment(Qt::AlignCenter);
401 infoLabel->setWordWrap(true);
402 infoLabel->setFont(infoFont);
403
404
405 gameTimer = new QTimer(this);
406 gameTimer->setInterval(100); // every 100ms, so it moves fast
407 connect(gameTimer, SIGNAL(timeout()), this, SLOT(timerTick()));
408
409
410 // This will call newQuestion
411 preQuestionTimer = new QTimer(this);
412 preQuestionTimer->setSingleShot(true);
413 connect(preQuestionTimer, SIGNAL(timeout()), this, SLOT(newQuestion()));
414
415
416 // This will call preQuestion
417 postQuestionTimer = new QTimer(this);
418 postQuestionTimer->setSingleShot(true);
419 postQuestionTimer->setInterval(1500); // 1,5 seconds
420 connect(postQuestionTimer, SIGNAL(timeout()), this, SLOT(preQuestion()));
421
422
423
424 ///////////////////////////////////////////////////////////////////// Right
425 timeBar = new QProgressBar(this);
426 timeBar->setOrientation(Qt::Vertical);
427 timeBar->setFormat(tr("Time")); // "%v seconds"
428 // timeBar's range() and value() will be set upon game start, on toggleScreen()
429 timeBar->setToolTip("<b></b>" // HTMLized for wordwrap
430 + tr("Remaining time to answer this question"));
431
432 //////////////////////////////////////////////////////////////////// Bottom
433 gameProgressBar = new QProgressBar(this);
434 gameProgressBar->setFormat(tr("%v out of %m questions - %p%"));
435 // gameProgressBar's range() and value() will be set upon game start, on toggleScreen()
436 gameProgressBar->setToolTip("<b></b>"
437 + tr("How many questions you've answered"));
438
439 gameScoreLabel = new QLabel(tr("Score"), this);
440 gameScoreLabel->setAlignment(Qt::AlignRight | Qt::AlignVCenter);
441
442 gameScore = new QLCDNumber(5, this);
443 gameScore->setSegmentStyle(QLCDNumber::Flat);
444 gameScore->display(0);
445 gameScore->setToolTip("<b></b>"
446 + tr("Your current score"));
447
448
449 statusLabel = new QLabel(this);
450
451
452 endGameButton = new QPushButton(QIcon::fromTheme("media-playback-stop",
453 QIcon(":/images/button-cancel.png")),
454 tr("&End game"),
455 this);
456 endGameButton->setFlat(true);
457 connect(endGameButton, SIGNAL(clicked()),
458 this, SLOT(confirmEndGame()));
459
460
461
462 ///////////////////////////////////////////////////////////////// Left side
463 aniNoteMovie.setFileName(":/images/aninote.gif");
464
465 aniNoteLabel = new QLabel(this);
466 aniNoteLabel->setMovie(&aniNoteMovie);
467 aniNoteLabel->setFrameStyle(QFrame::StyledPanel | QFrame::Raised);
468 aniNoteLabel->setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Maximum);
469
470
471 QFont statsFont;
472 statsFont.setPointSize(12);
473
474 statisticsLabel = new QLabel("**STATS**", this);
475 statisticsLabel->setFrameStyle(QFrame::StyledPanel | QFrame::Sunken);
476 statisticsLabel->setFont(statsFont);
477
478
479 statisticsBoxLayout = new QVBoxLayout();
480 statisticsBoxLayout->addWidget(statisticsLabel);
481
482 statisticsBox = new QGroupBox(tr("Statistics"), this);
483 statisticsBox->setLayout(statisticsBoxLayout);
484
485
486
487 //////////////////////////////////////////////////////////////////// Center
488 //// Add the 4 answer buttons, and their font
489 answerButton[0] = new QPushButton(QIcon::fromTheme("arrow-right",
490 QIcon(":/images/button-arrow.png")),
491 "ANSWER 1 ----------------",
492 this);
493 connect(answerButton[0], SIGNAL(clicked()),
494 this, SLOT(answer1()));
495
496 answerButton[1] = new QPushButton(QIcon::fromTheme("arrow-right",
497 QIcon(":/images/button-arrow.png")),
498 "ANSWER 2 ----------------",
499 this);
500 connect(answerButton[1], SIGNAL(clicked()),
501 this, SLOT(answer2()));
502
503 answerButton[2] = new QPushButton(QIcon::fromTheme("arrow-right",
504 QIcon(":/images/button-arrow.png")),
505 "ANSWER 3 ----------------",
506 this);
507 connect(answerButton[2], SIGNAL(clicked()),
508 this, SLOT(answer3()));
509
510 answerButton[3] = new QPushButton(QIcon::fromTheme("arrow-right",
511 QIcon(":/images/button-arrow.png")),
512 "ANSWER 4 ----------------",
513 this);
514 connect(answerButton[3], SIGNAL(clicked()),
515 this, SLOT(answer4()));
516
517 QFont buttonFont;
518 buttonFont.setPointSize(13);
519 buttonFont.setBold(true);
520 // set font and minimum height in all 4 buttons
521 for (int counter = 0; counter != 4; ++counter)
522 {
523 answerButton[counter]->setFont(buttonFont);
524
525 // Set a minimum width, so it's ok for most titles/names
526 answerButton[counter]->setMinimumWidth(512);
527
528 // Make buttons use all space available
529 answerButton[counter]->setSizePolicy(QSizePolicy::MinimumExpanding,
530 QSizePolicy::MinimumExpanding);
531 }
532
533
534 /* Matching QActions to allow independent shortcuts.
535 *
536 * Using these independent shortcuts avoids a problem with autogenerated
537 * shortcuts under Plasma 5.
538 *
539 * Shortcut blocks are 1/2/3/4, Z/X/C/V, 7/8/9/0 and U/I/O/P
540 *
541 */
542 answerAction1 = new QAction(this);
543 answerAction1->setShortcuts(QList<QKeySequence>()
544 << Qt::Key_1 << Qt::Key_Z
545 << Qt::Key_7 << Qt::Key_U);
546 connect(answerAction1, SIGNAL(triggered()),
547 this, SLOT(answer1()));
548
549 answerAction2 = new QAction(this);
550 answerAction2->setShortcuts(QList<QKeySequence>()
551 << Qt::Key_2 << Qt::Key_X
552 << Qt::Key_8 << Qt::Key_I);
553 connect(answerAction2, SIGNAL(triggered()),
554 this, SLOT(answer2()));
555
556 answerAction3 = new QAction(this);
557 answerAction3->setShortcuts(QList<QKeySequence>()
558 << Qt::Key_3 << Qt::Key_C
559 << Qt::Key_9 << Qt::Key_O);
560 connect(answerAction3, SIGNAL(triggered()),
561 this, SLOT(answer3()));
562
563 answerAction4 = new QAction(this);
564 answerAction4->setShortcuts(QList<QKeySequence>()
565 << Qt::Key_4 << Qt::Key_V
566 << Qt::Key_0 << Qt::Key_P);
567 connect(answerAction4, SIGNAL(triggered()),
568 this, SLOT(answer4()));
569
570 this->addAction(answerAction1);
571 this->addAction(answerAction2);
572 this->addAction(answerAction3);
573 this->addAction(answerAction4);
574
575
576 // Add the AnswerBox, used in the highest difficulty mode, type-the-answer
577 answerBox = new AnswerBox(this);
578 answerBox->setFont(buttonFont);
579 answerBox->setMinimumWidth(512);
580 connect(answerBox, SIGNAL(answered(bool)),
581 this, SLOT(answerFromAnswerBox(bool)));
582
583 // Disable player input initially, to avoid problems in the title screen
584 this->enablePlayerInput(false);
585
586
587 /////////////////////////////////////////////////////////////////// Layouts
588
589 playingTopLayout = new QVBoxLayout();
590 playingTopLayout->setContentsMargins(0, 0, 0, 0);
591 playingTopLayout->addWidget(playerNameLabel);
592 playingTopLayout->addWidget(questionLabel, 1);
593 playingTopLayout->addWidget(infoLabel, 1);
594
595 statisticsLayout = new QVBoxLayout();
596 statisticsLayout->addWidget(aniNoteLabel, 1, Qt::AlignTop | Qt::AlignHCenter);
597 statisticsLayout->addSpacing(2);
598 statisticsLayout->addStretch(1);
599 statisticsLayout->addWidget(statisticsBox, 1);
600
601 answersLayout = new QVBoxLayout();
602 answersLayout->addWidget(answerButton[0], 4);
603 answersLayout->addSpacing(2);
604 answersLayout->addStretch(1);
605 answersLayout->addWidget(answerButton[1], 4);
606 answersLayout->addSpacing(2);
607 answersLayout->addStretch(1);
608 answersLayout->addWidget(answerButton[2], 4);
609 answersLayout->addSpacing(2);
610 answersLayout->addStretch(1);
611 answersLayout->addWidget(answerButton[3], 4);
612
613 // TMP FIXME: adding answerBox with great stretch factor to make it look almost centered
614 answersLayout->addWidget(answerBox, 22, Qt::AlignVCenter);
615
616
617 playingMiddleLayout = new QHBoxLayout();
618 playingMiddleLayout->addLayout(statisticsLayout, 2);
619 playingMiddleLayout->addSpacing(4);
620 playingMiddleLayout->addLayout(answersLayout, 9);
621 playingMiddleLayout->addSpacing(4);
622 playingMiddleLayout->addWidget(timeBar);
623
624
625 playingProgressLayout = new QHBoxLayout();
626 playingProgressLayout->addWidget(gameProgressBar);
627 playingProgressLayout->addSpacing(32);
628 playingProgressLayout->addWidget(gameScoreLabel);
629 playingProgressLayout->addSpacing(16);
630 playingProgressLayout->addWidget(gameScore);
631
632
633 playingBottomLayout = new QHBoxLayout();
634 playingBottomLayout->addWidget(statusLabel, 1);
635 playingBottomLayout->addWidget(endGameButton, 0, Qt::AlignRight);
636
637
638 playingLayout = new QVBoxLayout();
639 playingLayout->setAlignment(Qt::AlignRight);
640 playingLayout->addLayout(playingTopLayout);
641 playingLayout->addSpacing(2);
642 playingLayout->addStretch(0);
643 playingLayout->addLayout(playingMiddleLayout, 1);
644 playingLayout->addSpacing(8);
645 playingLayout->addStretch(0);
646 playingLayout->addLayout(playingProgressLayout);
647 playingLayout->addSpacing(4);
648 playingLayout->addStretch(0);
649 playingLayout->addLayout(playingBottomLayout);
650 playingWidget->setLayout(playingLayout);
651
652 mainLayout->addWidget(playingWidget);
653 if (useOwnColorTheme)
654 {
655 this->setThemedColors();
656 }
657
658
659 musicPlayer = Phonon::createPlayer(Phonon::MusicCategory,
660 Phonon::MediaSource());
661 qDebug() << "Phonon::createPlayer()->isValid()? " << musicPlayer->isValid();
662 if (!musicPlayer->isValid())
663 {
664 QMessageBox::warning(this, tr("Sound system error"),
665 tr("There seems to be a problem with "
666 "your sound system.")
667 + "<br><br>"
668 + tr("Maybe you don't have any Phonon backends "
669 "installed."));
670 }
671
672 connect(musicPlayer, SIGNAL(stateChanged(Phonon::State,Phonon::State)),
673 this, SLOT(playerStateChanged(Phonon::State,Phonon::State)));
674
675
676
677
678 playingWidget->hide();
679 }
680
681
682
683 /******************************************************************************
684 *
685 * Switch between the welcome screen and the playing screen, in either way
686 *
687 */
toggleScreen()688 void AuralWindow::toggleScreen()
689 {
690 if (!playing)
691 { // START game!
692 qDebug() << "Starting game";
693 welcomeWidget->hide();
694
695 shuffleMusicFiles();
696
697 // Reset everything...
698 currentMusicFile = 0;
699 this->gameProgressBar->setRange(0, this->numQuestions);
700 this->gameProgressBar->setValue(0);
701 this->gameScore->display(0); // reset score
702
703
704 maxTime = 500 / (difficultyLevel + 1); // In millisec/10
705 if (difficultyLevel == 5)
706 {
707 maxTime += 100; // Extra time in Hardcore/type-the-answer mode!
708 }
709 warningTime = maxTime / 4;
710 dangerTime = maxTime / 8;
711
712 //this->pieceDuration = maxTime - (maxTime / (difficultyLevel+1));
713 this->pieceDuration = maxTime - ( 90 / (difficultyLevel+1) );
714 // +1 becausec difficulty level can be 0
715 if (difficultyLevel == 5)
716 {
717 pieceDuration -= 5; // Extra duration in Hardcore level!
718 }
719
720 qDebug() << "Max/Warning/Danger times:" << maxTime/6 << warningTime/6
721 << dangerTime/6 << "secs";
722 qDebug() << "Piece duration:" << (maxTime - pieceDuration) / 6 << "secs";
723
724 timeBar->setRange(0, this->maxTime);
725 timeBar->setValue(this->maxTime);
726
727
728 if (difficultyLevel < 5) // Regular mode: show buttons, hide AnswerBox
729 {
730 answerButton[0]->show();
731 answerButton[1]->show();
732 answerButton[2]->show();
733 answerButton[3]->show();
734
735 answerBox->hide();
736 }
737 else // Type-the-answer mode: hide buttons and show AnswerBox
738 {
739 answerBox->show();
740
741 answerButton[0]->hide();
742 answerButton[1]->hide();
743 answerButton[2]->hide();
744 answerButton[3]->hide();
745 }
746
747 this->enablePlayerInput(true);
748
749 if (numPlayers > 1)
750 {
751 this->playerNameLabel->show();
752 }
753 else
754 {
755 this->playerNameLabel->hide();
756 }
757
758
759 // Reset stats for all players
760 this->score.clear();
761 this->goodAnswers.clear();
762 this->badAnswers.clear();
763 this->timedOutAnswers.clear();
764 for (int counter = 0; counter != numPlayers; ++counter)
765 {
766 score << 0;
767 goodAnswers << 0;
768 badAnswers << 0;
769 timedOutAnswers << 0;
770 }
771
772
773 this->currentPlayer = 0;
774
775
776 QStringList startStrings; // Randomly choose from a list of "get ready" strings
777 startStrings << tr("Starting!")
778 << tr("Let's go!")
779 << tr("GO!!")
780 << tr("Good luck!");
781 this->infoLabel->setStyleSheet("background: qradialgradient(spread:pad, "
782 "cx:0.5, cy:0.5, radius:0.5, fx:0.5, "
783 "fy:0.5, stop:0 rgba(0, 64, 64, 255) "
784 "stop:1 rgba(0, 255, 255, 255)); "
785 "color: white");
786 this->infoLabel->setText(startStrings.at(qrand() % startStrings.length()));
787
788 QString totalSongs = QLocale::system()
789 .toString(this->musicFiles[0].length());
790 this->statusLabel->setText(tr("%1 songs available").arg(totalSongs));
791
792
793 playingWidget->show();
794
795 playing = true;
796 this->updateStatistics();
797
798
799 newQuestion();
800 }
801 else
802 { // STOP game
803 qDebug() << "Stopping game";
804 musicPlayer->stop(); // stop music, if any
805
806 gameTimer->stop();
807
808 preQuestionTimer->stop(); // These 2 lines fix problems when clicking
809 postQuestionTimer->stop(); // "End Game" right after answering
810 // (Music in title screen)
811
812 this->enablePlayerInput(false);
813
814 playingWidget->hide();
815 welcomeWidget->show();
816 playing = false;
817 }
818 }
819
820
821 /*
822 * Set styles to app-specific, or set them empty for user/system-defined
823 *
824 */
setThemedColors()825 void AuralWindow::setThemedColors() // everything here is quite temporary
826 {
827 // set transparency
828 this->setWindowOpacity(0.98);
829
830 // set dark-blue style to the program in general
831 this->setStyleSheet("background: qlineargradient(spread:pad, "
832 "x1:0, y1:0, x2:1, y2:1, "
833 "stop:0.0 rgba(20, 40, 40, 255), "
834 "stop:1.0 rgba(20, 40, 120, 255) );"
835
836 "color: qlineargradient(spread:pad, "
837 "x1:0, y1:0, x2:1, y2:1, "
838 "stop:0.0 rgba(140, 140, 220, 255), "
839 "stop:0.5 rgba(160, 250, 160, 255), "
840 "stop:1.0 rgba(220, 140, 140, 255) );");
841
842 logoLabel->setStyleSheet("background: transparent");
843
844 aniNoteLabel->setStyleSheet("background: transparent");
845
846 // set colors on answer buttons and answer box
847 answerButton[0]->setStyleSheet("background: #20DD20; color: darkBlue");
848 answerButton[1]->setStyleSheet("background: #20CC20; color: darkBlue");
849 answerButton[2]->setStyleSheet("background: #20BB20; color: darkBlue");
850 answerButton[3]->setStyleSheet("background: #20AA20; color: darkBlue");
851
852
853 answerBox->setStyleSheet("background: #20AA20; color: darkBlue");
854
855
856 // set color of LCD score indicator
857 gameScoreLabel->setStyleSheet("background: transparent");
858
859 gameScore->setStyleSheet("background: black;"
860 "color: lightBlue");
861
862
863 statisticsBox->setStyleSheet("background: #400000");
864
865 statisticsLabel->setStyleSheet("background: darkRed;"
866 "color: white");
867
868 statusLabel->setStyleSheet("background: transparent");
869 }
870
871
enablePlayerInput(bool state)872 void AuralWindow::enablePlayerInput(bool state)
873 {
874 if (difficultyLevel < 5)
875 {
876 // Keyboard keys 1~4, Z~V, 7~0, U~P, and buttons
877 answerAction1->setEnabled(state);
878 answerAction2->setEnabled(state);
879 answerAction3->setEnabled(state);
880 answerAction4->setEnabled(state);
881
882 this->answerButton[0]->setEnabled(state);
883 this->answerButton[1]->setEnabled(state);
884 this->answerButton[2]->setEnabled(state);
885 this->answerButton[3]->setEnabled(state);
886 }
887 else
888 {
889 this->answerBox->setEnabled(state);
890 if (state)
891 {
892 this->answerBox->setFocus();
893 }
894 }
895
896
897 // Always disable the QActions when disabling input; otherwise, they can cause crashes
898 if (!state)
899 {
900 answerAction1->setDisabled(true);
901 answerAction2->setDisabled(true);
902 answerAction3->setDisabled(true);
903 answerAction4->setDisabled(true);
904 }
905 }
906
907
908
909
910 ///////////////////////////////////////////////////////////////////////////////
911 ////////////////////////////////// SLOTS //////////////////////////////////////
912 ///////////////////////////////////////////////////////////////////////////////
913
914
915
916
917 // Used from newQuestion(), via SIGNAL
playerStateChanged(Phonon::State newState,Phonon::State oldState)918 void AuralWindow::playerStateChanged(Phonon::State newState,
919 Phonon::State oldState)
920 {
921
922 qDebug() << "playerStateChanged() (5 means error) -> "
923 << oldState << ">" << newState;
924
925 //if (oldState != Phonon::PlayingState
926 if ((oldState == Phonon::LoadingState || oldState == Phonon::PausedState)
927 && newState == Phonon::StoppedState)
928 {
929 qDebug() << "Gone from LoadingState to StoppedState";
930 this->nextSongLoaded();
931 }
932
933 if (newState == Phonon::PausedState)
934 {
935 qDebug() << "Entered PausedState";
936 }
937
938 if (musicPlayer->errorType() != 0) // if some error
939 {
940 qDebug() << "playerStateChanged(), some error!! State:" << newState;
941 qDebug() << "Error string and type:" << musicPlayer->errorString()
942 << musicPlayer->errorType();
943 QMessageBox::warning(this,
944 tr("Error playing sound"),
945 tr("An error occurred while playing sound.\n"
946 "The error message was: %1\n\n"
947 "Maybe your Phonon-backend does not have "
948 "support for the MP3 file "
949 "format.").arg(musicPlayer->errorString()));
950 }
951 }
952
953
954
955 /*****************************************************************************/
956
957
958
959 /*
960 * Decrease time left to guess song. Called by gameTimer
961 *
962 */
timerTick()963 void AuralWindow::timerTick()
964 {
965 if (timeBar->value() == this->maxTime-1) // Just once upon start
966 {
967 // Seek to a random part of the music file
968 qint64 seekTime = 10000; // Start at least at 00:10
969 // add random ms avoiding last 50 sec, resulting in range 00:10 -> END -01:00
970 seekTime += qrand() % (musicPlayer->totalTime() - 50000);
971
972 qDebug() << "Seeking to:" << seekTime / 1000 << "sec /"
973 << "Seekable:" << musicPlayer->isSeekable()
974 << "/ Total time:" << musicPlayer->totalTime() / 1000;
975
976 musicPlayer->play();
977 //musicPlayer->pause();
978 /*
979 * play() BEFORE seek()
980 * because gstreamer-backend seeks to 0 again on play() apparently...
981 */
982 musicPlayer->seek(seekTime); // TMPFIX, add error control
983 //musicPlayer->play();
984 }
985
986
987 timeBar->setValue(timeBar->value() - 1);
988
989 // Stop music earlier than the time limit to answer the question
990 if (timeBar->value() == this->pieceDuration)
991 {
992 this->musicPlayer->stop();
993 this->aniNoteLabel->movie()->stop(); // TMPFIX
994 qDebug() << "totalTime, again:" << musicPlayer->totalTime() / 1000; // TMP tests
995 }
996
997
998 // Clear colored info label (good/bad), soon after new question
999 if (timeBar->value() == this->maxTime - 10) // very very TMPFIX
1000 {
1001 this->infoLabel->setText("");
1002 this->infoLabel->setStyleSheet("background: transparent");
1003 }
1004
1005
1006 // if remaining time is low, change timeBar colors
1007
1008 if (timeBar->value() == this->warningTime) // warning level
1009 {
1010 timeBar->setStyleSheet("background-color: yellow"); // orange?
1011 }
1012
1013 if (timeBar->value() == this->dangerTime) // danger level
1014 {
1015 timeBar->setStyleSheet("background-color: red"); // darkRed?
1016 }
1017
1018
1019 // time to answer ended
1020 if (timeBar->value() == 0)
1021 {
1022 qDebug() << "Time's up!";
1023 musicPlayer->stop();
1024 this->infoLabel->setStyleSheet("background: "
1025 "qradialgradient(spread:pad, "
1026 "cx:0.5, cy:0.5, radius:0.5, "
1027 "fx:0.5, fy:0.5, stop:0 "
1028 "rgba(64, 0, 0, 255) stop:1 "
1029 "rgba(128, 0, 0, 255)); color: white");
1030 this->infoLabel->setText(tr("Time's up!!") + " "
1031 + tr("The answer was:") + " "
1032 + musicFiles[questionType].at(currentMusicFile));
1033
1034
1035
1036 answerQuestion(0); // 0 means it's timeout, not a button
1037 }
1038 }
1039
1040
1041
1042 /*
1043 * Update configuration from optionsDialog SIGNAL
1044 *
1045 */
updateConfig(bool startGame,QString directory,bool forceReload,int difficulty,int questions,int players,QStringList playerNameList,bool ownColors)1046 void AuralWindow::updateConfig(bool startGame, QString directory, bool forceReload,
1047 int difficulty, int questions, int players,
1048 QStringList playerNameList, bool ownColors)
1049 {
1050 bool mustReload = false;
1051 if (this->musicDirectory != directory)
1052 {
1053 qDebug() << "musicDirectory has changed, mustReload = true";
1054 qDebug() << "musicDirectory:" << musicDirectory << "; directory:" << directory;
1055 mustReload = true; // if music directory's been changed, reload collection info
1056 }
1057
1058 this->musicDirectory = directory;
1059 this->musicAnalyzer->setMusicDirectory(musicDirectory); // inform M.A. about it
1060
1061 this->difficultyLevel = difficulty;
1062 this->numQuestions = questions;
1063 this->numPlayers = players;
1064 this->playerNames.clear();
1065 playerNames = playerNameList;
1066 this->useOwnColorTheme = ownColors;
1067
1068 qDebug() << "Updated config with: " << startGame << directory << forceReload
1069 << difficulty << questions << players
1070 << playerNameList << ownColors;
1071
1072
1073 // reload music collection information here if needed
1074 if (mustReload || forceReload)
1075 {
1076 qDebug() << "Reloading music collection information";
1077 // this->createSongList(); // replaced by a call from a timer
1078 postConfigUpdatedTimer->start(); // will call createSongList() in 500ms
1079 }
1080
1081
1082 // if optionsDialog was called in PlayMode, start game now
1083 if (startGame)
1084 {
1085 if (numPlayers > 1) // in multiplayer, give extra time to prepare
1086 {
1087 this->preQuestionTimer->setInterval(2000); // 2 sec
1088 // FIXME: make configurable?
1089 }
1090 else
1091 {
1092 this->preQuestionTimer->setInterval(500); // half a sec
1093 }
1094
1095 this->toggleScreen(); // Start game!
1096 }
1097 }
1098
1099
1100
1101 /*
1102 * About... box
1103 */
showAbout()1104 void AuralWindow::showAbout()
1105 {
1106 QMessageBox::about(this, tr("About Auralquiz"),
1107 QString("<big><b>Auralquiz v%1</b></big>")
1108 .arg(qApp->applicationVersion())
1109 + "<br />"
1110 "Copyright 2011-2017 JanKusanagi"
1111 "<br />"
1112 "<br />"
1113 "<a href=\"https://jancoding.wordpress.com/auralquiz\">"
1114 "https://jancoding.wordpress.com/auralquiz</a>"
1115 "<br />"
1116 "<br />"
1117 + tr("Auralquiz loads all your music from a specified "
1118 "folder and asks questions about it, playing a "
1119 "short piece of each music file as clue.")
1120 + "<br />"
1121 "<hr />"
1122 + tr("English translation by JanKusanagi.",
1123 "TRANSLATORS: Change this with your language and name. "
1124 "Feel free to add your contact information, such as e-mail. "
1125 "If there was another translator before you, add your "
1126 "name after theirs ;)")
1127 + "<br />"
1128 "<br />"
1129 + tr("Thanks to all the testers, translators and packagers, "
1130 "who help make Auralquiz better!")
1131 + "<br />"
1132 "<hr />"
1133 + tr("Auralquiz is licensed under the GNU GPL license.")
1134 + "<br />"
1135 + tr("Main screen image and some icons are based on "
1136 "Oxygen icons, under LGPL license.")
1137 + "<br />"
1138 "<br />"
1139 "<a href=\"http://www.gnu.org/licenses/gpl-2.0.html\">"
1140 "GNU GPL v2</a> - "
1141 "<a href=\"http://www.gnu.org/licenses/lgpl-2.1.html\">"
1142 "GNU LGPL v2.1</a>"
1143 "<br />"
1144 "<br />"
1145 "<a href=\"https://techbase.kde.org/Projects/Oxygen\">"
1146 "techbase.kde.org/Projects/Oxygen</a>"
1147 "<br />"
1148 "<br />"
1149
1150 + QString("<b>Qt</b> v%1 ~ <b>Phonon</b> v%2")
1151 .arg(qVersion())
1152 .arg(Phonon::phononVersion()));
1153 }
1154
1155
1156
1157
modifyStartGameButton(bool enabledState,QString text,QString tooltip)1158 void AuralWindow::modifyStartGameButton(bool enabledState,
1159 QString text,
1160 QString tooltip)
1161 {
1162 this->startGameButton->setEnabled(enabledState);
1163 if (!text.isEmpty())
1164 {
1165 this->startGameButton->setText("\n" + text + "\n");
1166 this->startGameButton->setToolTip("<b></b>" // HTMLize, so it gets wordwrap
1167 + tooltip);
1168 }
1169
1170 this->startGameButton->setFocus();
1171 }
1172
1173
1174
1175
1176
1177
1178
1179 /******************************************************************************
1180 *
1181 * Prepare a new question, its answers, and play the new song file
1182 */
newQuestion()1183 void AuralWindow::newQuestion()
1184 {
1185 qDebug() << "newQuestion()";
1186
1187 timeBar->setValue(this->maxTime);
1188 timeBar->setStyleSheet("");
1189
1190 this->correctAnswer = (qrand() % 4) + 1; // 1, 2, 3, 4; 0 would mean TIMED UP
1191 //qDebug() << "correctAnswer:" << this->correctAnswer;
1192
1193 questionType = (currentMusicFile & 1) + 1; // quite TMPFIX, odd=artist, even=title
1194 //qDebug() << "questionType: " << questionType;
1195
1196 if (questionType == 1)
1197 {
1198 questionLabel->setText(tr("Who plays this song?"));
1199 }
1200 else
1201 {
1202 questionLabel->setText(tr("What's the title of this song?"));
1203 }
1204
1205 // show current player name in multiplayer mode // FIXME: and avatar!
1206 if (numPlayers > 1)
1207 {
1208 playerNameLabel->setText(tr("Player %1 of %2").arg(currentPlayer + 1)
1209 .arg(numPlayers)
1210 + " -- "
1211 + QString("%1").arg(playerNames.at(currentPlayer)));
1212 gameScore->display(score.at(currentPlayer));
1213 }
1214
1215
1216 QString buttonTexts[4];
1217 int answerCount = 0;
1218
1219 // FIXME: These checks should be moved to the analyzer, in createSongList()
1220 int tries = 0;
1221 while (answerCount < 4)
1222 {
1223 buttonTexts[answerCount] = musicFiles[questionType]
1224 .at(qrand() % this->musicFiles[0].length());
1225
1226
1227 //qDebug() << "answerCount" << answerCount;
1228
1229 // If text in current button is NOT the correct answer (lowercase comparison!)
1230 if (buttonTexts[answerCount].toLower() != musicFiles[questionType]
1231 .at(currentMusicFile).toLower())
1232 {
1233 bool isGood = true;
1234 for (int previousAnswer = 0; previousAnswer != answerCount; ++previousAnswer)
1235 {
1236 // Compare strings in LOWERCASE (Metallica ~= metallica)
1237 if (buttonTexts[answerCount].toLower()
1238 != buttonTexts[previousAnswer].toLower())
1239 {
1240 isGood = true;
1241 //qDebug() << "is GOOD" << previousAnswer;
1242 //qDebug() << "is GOOD" << buttonTexts[answerCount]
1243 // << buttonTexts[previousAnswer];
1244 }
1245 else
1246 {
1247 isGood = false;
1248 //qDebug() << "is NOT good; duplicated, break" << previousAnswer;
1249 //qDebug() << "is NOT good" << buttonTexts[answerCount]
1250 // << buttonTexts[previousAnswer];
1251 break;
1252 }
1253 }
1254
1255 if (isGood)
1256 {
1257 // Set random artists/titles as labels on buttons
1258 answerButton[answerCount]->setText(buttonTexts[answerCount]);
1259
1260 ++answerCount;
1261 tries = 0;
1262 }
1263
1264 //qDebug() << "buttonTexts[answerCount]:" << buttonTexts[answerCount];
1265 }
1266 ++tries; /* TMPFIX
1267 Temporary way of catching infinite loops
1268 if there are not enough different artists or titles */
1269
1270 if (tries > 50)
1271 {
1272 // FIXME: these checks should be done after createSongList()
1273 qDebug() << "Not enough different titles or artists to create question!";
1274 answerCount = 999; // "break"
1275 }
1276 }
1277
1278 // quite hacky, TMPFIX
1279 if (tries < 50) // if no problem with duplicates, meaning we have enough valid files
1280 {
1281 // Set correct artist/title on one button
1282 answerButton[correctAnswer-1]->setText(musicFiles[questionType].at(currentMusicFile));
1283
1284 // replace "&" by "&&" in buttons, so it doesn't turn into an accelerator
1285 // Also truncate if answer is too long
1286 for (int counter=0; counter != 4; ++counter)
1287 {
1288 QString fixedAnswer = answerButton[counter]->text();
1289 fixedAnswer.replace(QRegExp("&"), "&&"); // fix non-accelerator
1290 fixedAnswer.truncate(64); // avoid wiiiiide buttons
1291 // FIXME 1.1.x: truncate position should be calculated, not hardcoded
1292 // For now, 64 seems sane
1293
1294 answerButton[counter]->setText(fixedAnswer);
1295 }
1296
1297
1298 // Set answer for AnswerBox too
1299 answerBox->setAnswer(musicFiles[questionType].at(currentMusicFile));
1300
1301
1302 // Re-enable stuff to answer
1303 this->enablePlayerInput(true);
1304
1305
1306 musicPlayer->stop();
1307 musicPlayer->clear(); // Stop playing file and clear queues
1308 qDebug() << "musicPlayer stopped and cleared";
1309
1310 musicPlayer->setCurrentSource(QUrl("file://" + musicFiles[0].at(currentMusicFile)));
1311 qDebug() << "newQuestion(): musicPlayer->setCurrentSource() done";
1312
1313 qDebug() << "seekable?" << this->musicPlayer->isSeekable(); // checks needed for gstreamer
1314 qDebug() << "PhononState should change now; "
1315 "with Gstreamer-backend it might stop here (BUG)";
1316 }
1317 else
1318 {
1319 this->toggleScreen(); // Recursive!! :P
1320
1321 QMessageBox::critical(this, tr("Not enough music files"),
1322 tr("You don't have enough music files, "
1323 "or from enough different artists.\n"
1324 "You need music from at least 5 different artists "
1325 "to be able to generate questions."));
1326 }
1327 }
1328
1329
1330
1331 /*
1332 * Called from PlayerStateChanged()
1333 */
nextSongLoaded()1334 void AuralWindow::nextSongLoaded()
1335 {
1336 qDebug() << "(playerStateChanged) > nextSongLoaded()";
1337
1338 /* Put into PlayingState so seeking and getting total time works correctly later
1339 *
1340 * A little hacky and maybe unreliable with some backends (TMP / FIXME)
1341 *
1342 */
1343 musicPlayer->play();
1344 musicPlayer->pause(); // Pause immediately, so the user can't hear anything
1345
1346 this->aniNoteLabel->movie()->start();
1347 gameTimer->start(); // will call "timerTick()"
1348 }
1349
1350
1351
preQuestion()1352 void AuralWindow::preQuestion()
1353 {
1354 if (this->gameProgressBar->value() < this->numQuestions)
1355 {
1356 // There are more questions!
1357
1358 ++currentMusicFile;
1359 if (currentMusicFile == this->musicFiles[0].length())
1360 {
1361 currentMusicFile = 0;
1362 qDebug() << "Not enough music files to avoid repeating; back to 1st";
1363 }
1364
1365
1366 this->updateStatistics();
1367
1368
1369 infoLabel->setStyleSheet("background: qradialgradient(spread:pad, "
1370 "cx:0.5, cy:0.5, radius:0.5, fx:0.5, fy:0.5, "
1371 "stop:0 rgba(0, 0, 128, 255) stop:1 "
1372 "rgba(0, 128, 255, 127)); color: white");
1373 if (numPlayers > 1)
1374 {
1375 infoLabel->setText(tr("Go, %1!",
1376 "%1=player's name")
1377 .arg(playerNames.at(currentPlayer)));
1378 }
1379 else
1380 {
1381 infoLabel->setText(tr("Next!"));
1382 }
1383
1384
1385 preQuestionTimer->start(); // this will call newQuestion()
1386 }
1387 else // No more questions!
1388 {
1389 qDebug() << "End of questions";
1390
1391 // Show the ranking window
1392 ranking = new Ranking(numPlayers, playerNames, score,
1393 goodAnswers, badAnswers, timedOutAnswers,
1394 this);
1395 connect(ranking, SIGNAL(closed()),
1396 this, SLOT(killRanking()));
1397
1398 rankingTimer->setSingleShot(true);
1399 connect(rankingTimer, SIGNAL(timeout()),
1400 ranking, SLOT(show()));
1401
1402 rankingTimer->start(2000); // Wait 2 seconds before showing the ranking
1403
1404
1405 this->infoLabel->setStyleSheet("background: qradialgradient(spread:pad, "
1406 "cx:0.5, cy:0.5, radius:0.5, fx:0.5, "
1407 "fy:0.5, stop:0 rgba(0, 0, 128, 255) "
1408 "stop:1 rgba(0, 128, 255, 255)); "
1409 "color: white");
1410 this->infoLabel->setText(tr("All done!"));
1411 }
1412 }
1413
1414
1415 /**************************************************************************************
1416 *
1417 * Check if answer is correct, give score, and get new question
1418 */
answerQuestion(int numAnswer)1419 void AuralWindow::answerQuestion(int numAnswer)
1420 {
1421 qDebug() << "Answered question with button" << numAnswer;
1422
1423 this->enablePlayerInput(false);
1424
1425 this->aniNoteLabel->movie()->stop();
1426
1427 musicPlayer->stop(); // stop music
1428 // This helps Phonon's stateChanged(), avoids silent songs
1429 gameTimer->stop();
1430
1431
1432
1433 int questionPoints;
1434 if (timeBar->value() != 0)
1435 {
1436 //questionPoints = (100 * difficultyLevel) + (timeBar->value() / 2); // OLD
1437
1438 questionPoints = 50 * difficultyLevel+1;
1439 questionPoints += timeBar->value() / 2;
1440 questionPoints += 100;
1441 }
1442 else
1443 {
1444 questionPoints = 0;
1445 this->timedOutAnswers[currentPlayer]++;
1446 qDebug() << "Answered by timeout";
1447 }
1448
1449
1450
1451 if (questionPoints != 0)
1452 {
1453 if (numAnswer == correctAnswer)
1454 {
1455 QStringList rightAnswerStrings;
1456 rightAnswerStrings << tr("Correct!!")
1457 << tr("Yeah!")
1458 << tr("That's right!")
1459 << tr("Good!")
1460 << tr("Great!");
1461
1462 this->infoLabel->setStyleSheet("background: "
1463 "qradialgradient(spread:pad, "
1464 "cx:0.5, cy:0.5, radius:0.5, "
1465 "fx:0.5, fy:0.5, stop:0 "
1466 "rgba(0, 192, 0, 255) "
1467 "stop:1 rgba(0, 255, 0, 0)); "
1468 "color: white");
1469 this->infoLabel->setText(rightAnswerStrings
1470 .at(qrand() % rightAnswerStrings.length())
1471 + " ~ "
1472 + tr("%1 by %2",
1473 "1=song title, 2=author name")
1474 .arg("\"" + musicFiles[2].at(currentMusicFile) + "\"")
1475 .arg("\"" + musicFiles[1].at(currentMusicFile) + "\"")
1476 + " ~ "
1477 + tr("+%1 points!").arg(questionPoints));
1478
1479 this->score[currentPlayer] += questionPoints;
1480 gameScore->display(score.at(currentPlayer));
1481 this->goodAnswers[currentPlayer]++;
1482
1483 qDebug() << "correct!!" << questionPoints;
1484 }
1485 else
1486 {
1487 QStringList wrongAnswerStrings;
1488 wrongAnswerStrings << tr("Wrong!")
1489 << tr("No!")
1490 << tr("That's not it.")
1491 << tr("Ooops!")
1492 << tr("FAIL!");
1493
1494 this->infoLabel->setStyleSheet("background: "
1495 "qradialgradient(spread:pad, "
1496 "cx:0.5, cy:0.5, radius:0.5, "
1497 "fx:0.5, fy:0.5, stop:0 "
1498 "rgba(192, 0, 0, 255) "
1499 "stop:1 rgba(255, 0, 0, 0)); "
1500 "color: white");
1501 this->infoLabel->setText(wrongAnswerStrings
1502 .at(qrand() % wrongAnswerStrings.length())
1503 + " " + tr("Correct answer was:") + " "
1504 + musicFiles[questionType].at(currentMusicFile));
1505 // quite TMPFIX
1506
1507 this->badAnswers[currentPlayer]++;
1508
1509 qDebug() << "wrong!";
1510 }
1511
1512 }
1513
1514
1515 this->updateStatistics();
1516
1517
1518 if (numPlayers > 1)
1519 {
1520 this->currentPlayer++;
1521 if (currentPlayer == numPlayers)
1522 {
1523 this->currentPlayer = 0; // back to first player
1524 qDebug() << "Next question, back to Player 1";
1525 }
1526 }
1527
1528
1529
1530 if (currentPlayer == 0)
1531 { // Always on single-player, and after all players answer, in multi
1532 gameProgressBar->setValue(gameProgressBar->value() + 1);
1533 }
1534
1535
1536 postQuestionTimer->start(); // will call preQuestion()
1537 }
1538
1539
1540 // These below... suck
answer1()1541 void AuralWindow::answer1()
1542 {
1543 answerQuestion(1);
1544 }
answer2()1545 void AuralWindow::answer2()
1546 {
1547 answerQuestion(2);
1548 }
answer3()1549 void AuralWindow::answer3()
1550 {
1551 answerQuestion(3);
1552 }
answer4()1553 void AuralWindow::answer4()
1554 {
1555 answerQuestion(4);
1556 }
1557
1558 // This one doesn't suck, actually!
answerFromAnswerBox(bool correct)1559 void AuralWindow::answerFromAnswerBox(bool correct)
1560 {
1561 if (correct)
1562 {
1563 answerQuestion(this->correctAnswer);
1564 }
1565 else
1566 {
1567 answerQuestion(-1); // Answer incorrectly
1568 }
1569 }
1570
confirmEndGame()1571 void AuralWindow::confirmEndGame()
1572 {
1573 int confirm = QMessageBox::question(this,
1574 tr("End game?"),
1575 tr("Are you sure you want to end "
1576 "this game?"),
1577 tr("&Yes, end it"), tr("&No"), "",
1578 1, 1);
1579
1580 if (confirm == 0 && this->playing) // Ignore if game ended by timeout
1581 { // while the dialog was present
1582 qDebug() << "Ending game...";
1583 this->toggleScreen();
1584 }
1585 }
1586
killRanking()1587 void AuralWindow::killRanking()
1588 {
1589 qDebug() << "deleting Ranking object";
1590 ranking->deleteLater();
1591
1592 this->toggleScreen();
1593 }
1594
1595
1596
1597 ///////////////////////////////////////////////////////////////////////////////
1598 //////////////////////////////// PROTECTED ////////////////////////////////////
1599 ///////////////////////////////////////////////////////////////////////////////
1600
1601
1602
1603
1604 /******************************************************************************
1605 *
1606 * Store app config upon exit
1607 */
closeEvent(QCloseEvent * event)1608 void AuralWindow::closeEvent(QCloseEvent *event)
1609 {
1610 QSettings settings;
1611 settings.setValue("firstRun", false);
1612 settings.setValue("size", this->size());
1613
1614 settings.setValue("useOwnColorTheme", this->useOwnColorTheme);
1615
1616
1617 settings.setValue("musicDirectory", this->musicDirectory);
1618 settings.setValue("numQuestions", this->numQuestions);
1619 settings.setValue("difficultyLevel", this->difficultyLevel);
1620 settings.setValue("numPlayers", this->numPlayers);
1621
1622 settings.setValue("playerNames", this->playerNames);
1623
1624 qDebug("closeEvent: config saved");
1625 event->accept();
1626 }
1627
1628