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>&nbsp;</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>&nbsp;</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>&nbsp;</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