123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462 |
- /*
- * Copyright (c) 2011 Nokia Corporation.
- */
- #include <QFile>
- #include <QXmlStreamReader>
- #include <QVariant>
- #include <QPainter>
- #include <QEvent>
- #include <QDebug>
- #include <QAudioOutput>
- #include <QThread>
- #include <QtDeclarative/QDeclarativeContext>
- #include "level.h"
- Level::Level() : m_rowCount(0), m_columnCount(0)
- {
- /***********************************************************************************************
- *Setting up the Sound player object, this object will live in a separate thread to ensure that *
- *sound playback does not interfere with the game itself. *
- * *
- *Connection type set to Qt::QueuedConnection, this is the connection type required for objects *
- *living in different threads. This would also get handled by the default Qt::AutoConnection, *
- *but this would make the code less readable. *
- ***********************************************************************************************/
- m_player = new SoundPlayer;
- connect(this, SIGNAL(bgSoundRequest()), m_player, SLOT(startBGSound()), Qt::QueuedConnection);
- connect(this, SIGNAL(boingSoundRequest()) ,m_player, SLOT(playBoing()), Qt::QueuedConnection);
- connect(this, SIGNAL(groanSoundRequest()) ,m_player, SLOT(playGroan()), Qt::QueuedConnection);
- connect(this, SIGNAL(stopSounds()), m_player, SLOT(stopSounds()), Qt::QueuedConnection);
- connect(m_player, SIGNAL(soundsStopped()), this, SIGNAL(readyToQuit()), Qt::QueuedConnection);
- }
- Level::~Level()
- {
- /***********************************************************************************************
- *Because the SoundPlayer object lives in another thread, it can not be a child of the Level *
- *Object and it needs to be deleted by hand. *
- ***********************************************************************************************/
- delete m_player;
- }
- /***************************************************************************************************
- *All sound playback is handled by an object living in another thread, playback needs to be *
- *invoked by means of a signal and queued connection. *
- ***************************************************************************************************/
- void Level::startBGSound()
- {
- #ifndef NO_SOUND
- emit bgSoundRequest();
- #endif
- }
- void Level::playBoing()
- {
- #ifndef NO_SOUND
- emit boingSoundRequest();
- #endif
- }
- void Level::playGroan()
- {
- #ifndef NO_SOUND
- emit groanSoundRequest();
- #endif
- }
- /**************************************************************************************************/
- QString Level::item(int row, int column) const
- {
- /***********************************************************************************************
- *Verifying that the row and column are in bounds. *
- *Extracting the string representing the item in that row and column. *
- ***********************************************************************************************/
- if ( row < 0 || row >= m_rowCount || column < 0 || column >= m_columnCount )
- return QString();
- const QString & text = m_map.at(row);
- if ( column >= text.count())
- return QString(" ");
- return text.at(column);
- }
- bool Level::load(QString fileName)
- {
- /***********************************************************************************************
- *Clearing the levels old data. *
- ***********************************************************************************************/
- m_rowCount = m_columnCount = 0;
- m_map.clear();
- /***********************************************************************************************
- *Opening the level file in read only mode. *
- ***********************************************************************************************/
- if(fileName.isEmpty() || !QFile::exists(fileName))
- fileName = ":/level/level.xml";
- QFile file(fileName);
- if (!file.open(QFile::ReadOnly | QFile::Text)) {
- qDebug() << Q_FUNC_INFO << file.errorString();
- return false;
- }
- /***********************************************************************************************
- *Using a QXmlStreamReader to parse the level file. *
- ***********************************************************************************************/
- QXmlStreamReader xml(&file);
- if (xml.readNextStartElement()) {
- if (xml.name() == "level") {
- while (xml.readNextStartElement()) {
- if (xml.name() == "layer") {
- const QString text = xml.readElementText();
- m_map.append(text);
- ++m_rowCount;
- m_columnCount = qMax(m_columnCount, text.count());
- }
- }
- }
- }
- /***********************************************************************************************
- *Checking xml parsing errors *
- ***********************************************************************************************/
- if (xml.hasError()) {
- qDebug() << Q_FUNC_INFO << xml.errorString();
- return false;
- }
- return true;
- }
- bool Level::intersects(QDeclarativeItem *item1, QDeclarativeItem *item2)
- {
- /***********************************************************************************************
- *Using QRect to check if the two items intersect. *
- ***********************************************************************************************/
- if ( !(item1&&item2))
- return false;
- const QRect r1 = QRect(item1->x(),item1->y(),item1->width(),item1->height());
- const QRect r2 = QRect(item2->x(),item2->y(),item2->width(),item2->height());
- return r1.intersects(r2);
- }
- QVariant Level::collidingItems(QDeclarativeItem *item) const
- {
- if (!item)
- return QVariant();
- QList<QObject*> dataList;
- /***********************************************************************************************
- *Extracting the list of items that collide with the item. *
- *Casting the item pointer to a EllipseItem pointer, *
- *if successful clearing the ellipse items m_rect vector. *
- ***********************************************************************************************/
- QList<QGraphicsItem *> list = item->collidingItems();
- EllipseItem *el = qobject_cast<EllipseItem*>(item);
- if (el)
- el->m_rect.clear();
- for ( int i=0; i<list.count();++i ) {
- /*******************************************************************************************
- *Ignore the collision if the items happen to be in a parent-child relation. *
- *Casting the colliding item pointer to a QDeclarativeItem pointer. *
- *******************************************************************************************/
- if ( item->isAncestorOf(list.at(i)) || item->parentItem() != list.at(i)->parentItem())
- continue;
- QDeclarativeItem * collidedItem = qobject_cast<QDeclarativeItem*>(list.at(i));
- if (collidedItem) {
- /***************************************************************************************
- *Ignore the collision if the colliding item happens to be the background *
- *or one of the ui controls. *
- *If an EllipseItem append the collision rect to the items m_rect vector. *
- *Append the item pointer to the result list. *
- ***************************************************************************************/
- if ( collidedItem->objectName() == QLatin1String("arrow") ||
- collidedItem->objectName() == QLatin1String("background") ||
- collidedItem->objectName() == QLatin1String("quit"))
- continue;
- if (el)
- el->m_rect.append( intersected(item,collidedItem) );
- dataList << collidedItem;
- }
- }
- /***********************************************************************************************
- *Return a QVariant containing the colliding object pointers. *
- ***********************************************************************************************/
- return QVariant::fromValue(dataList);
- }
- QRect Level::intersected(QDeclarativeItem *item1, QDeclarativeItem *item2) const
- {
- if (!(item1&&item2))
- return QRect();
- /***********************************************************************************************
- *Extracting the items shapes. *
- *Translate the second items shapes position to be relative to the first item. *
- *Calculate the items intersection. *
- *Return the intersections bounding rectangle. *
- ***********************************************************************************************/
- QPainterPath p1 = item1->shape();
- QPainterPath p2 = item2->shape().translated(item2->pos()-item1->pos());
- QPainterPath intersection = p2.intersected(p1);
- const QRect bounder = intersection.boundingRect().toRect();
- return bounder;
- }
- SoundPlayer::SoundPlayer() :m_musicBGOutput(0),m_boingOutput(0),m_groanOutput(0),
- m_musicBGPlaying(false),m_boingPlaying(false),m_groanPlaying(false)
- {
- /***********************************************************************************************
- *Creating a dedicated QThread for the SoundPlayer to live in, and moving the object to the *
- *new thread. *
- ***********************************************************************************************/
- m_soundThread = new QThread;
- this->moveToThread(m_soundThread);
- m_soundThread->start();
- /***********************************************************************************************
- *Setting up the audio format *
- ***********************************************************************************************/
- m_format.setSampleRate(22050);
- m_format.setChannelCount(1);
- m_format.setSampleSize(16);
- m_format.setCodec("audio/pcm");
- m_format.setByteOrder(QAudioFormat::LittleEndian);
- m_format.setSampleType(QAudioFormat::SignedInt);
- /***********************************************************************************************
- *Setting a flag indicating if the sound backend supports the given format, *
- *and loading the sound files. *
- * *
- *The QAudioOutput object creation is differed till the first usage, this is because the Sound *
- *players constructor is called from the main thread, and constructing them here would prevent *
- *them form working properly. *
- ***********************************************************************************************/
- QAudioDeviceInfo info(QAudioDeviceInfo::defaultOutputDevice());
- if (!info.isFormatSupported(m_format)) {
- m_soundSupport = false;
- qWarning()<<"raw audio format not supported by backend, cannot play audio.";
- return;
- } else {
- m_soundSupport = true;
- m_musicBGFile.setFileName(":/sounds/grieg4f.raw");
- m_boingtFile.setFileName(":/sounds/boink.raw");
- m_groanFile.setFileName(":/sounds/groan.raw");
- }
- }
- SoundPlayer::~SoundPlayer()
- {
- /***********************************************************************************************
- *Calling the sound thread to exit its event loop, waiting for the thread do finish and deleting*
- *the instance. *
- ***********************************************************************************************/
- m_soundThread->exit(0);
- m_soundThread->wait();
- delete m_soundThread;
- }
- /***************************************************************************************************
- *Sound playback slots, called by signals emitted by the Level object. *
- *The audio output objects get constructed the first time the slot is called. *
- ***************************************************************************************************/
- void SoundPlayer::startBGSound()
- {
- if(m_musicBGPlaying) return;
- if(!m_soundSupport) return;
- if (m_musicBGOutput) {
- m_musicBGOutput->start(&m_musicBGFile);
- m_musicBGPlaying = true;
- } else{
- if (m_musicBGFile.open(QIODevice::ReadOnly)) {
- m_musicBGOutput = new QAudioOutput(m_format,this);
- connect(m_musicBGOutput,SIGNAL(stateChanged(QAudio::State)),
- SLOT(outputStateChanged(QAudio::State)));
- m_musicBGOutput->start(&m_musicBGFile);
- m_musicBGPlaying = true;
- }
- }
- }
- void SoundPlayer::playBoing()
- {
- if(!m_soundSupport) return;
- if (m_boingOutput) {
- if(m_boingPlaying) return;
- m_boingOutput->start(&m_boingtFile);
- m_boingPlaying = true;
- } else{
- if (m_boingtFile.open(QIODevice::ReadOnly)) {
- m_boingOutput = new QAudioOutput(m_format,this);
- connect(m_boingOutput,SIGNAL(stateChanged(QAudio::State)),
- SLOT(outputStateChanged(QAudio::State)));
- m_boingOutput->start(&m_boingtFile);
- }
- }
- }
- void SoundPlayer::playGroan()
- {
- if(!m_soundSupport) return;
- if (m_groanOutput) {
- if(m_groanPlaying) return;
- m_groanOutput->start(&m_groanFile);
- m_groanPlaying = true;
- } else{
- if (m_groanFile.open(QIODevice::ReadOnly)) {
- m_groanOutput = new QAudioOutput(m_format,this);
- connect(m_groanOutput,SIGNAL(stateChanged(QAudio::State)),
- SLOT(outputStateChanged(QAudio::State)));
- m_groanOutput->start(&m_groanFile);
- }
- }
- }
- /**************************************************************************************************/
- void SoundPlayer::stopSounds()
- {
- /***********************************************************************************************
- *Stop all sound playback and emit the signal indicating it is now safe to exit the application *
- ***********************************************************************************************/
- if(m_soundSupport)
- {
- if(m_musicBGOutput)
- m_musicBGOutput->stop();
- if(m_boingOutput)
- m_boingOutput->stop();
- if(m_groanOutput)
- m_groanOutput->stop();
- }
- emit soundsStopped();
- }
- void SoundPlayer::outputStateChanged(QAudio::State state)
- {
- /***********************************************************************************************
- *If signal sender == m_musicBGOutput resume background music playback. *
- *If signal sender == m_boingOutput drop the boing playing flag and prepare for next playback. *
- *If signal sender == m_groanOutput drop the groan playing flag and prepare for next playback. *
- ***********************************************************************************************/
- if (state == QAudio::IdleState) {
- if (sender() == m_musicBGOutput ) {
- m_musicBGOutput->stop();
- m_musicBGFile.seek(0);
- m_musicBGOutput->start(&m_musicBGFile);
- } else if (sender() == m_boingOutput ) {
- m_boingPlaying = false;
- m_boingOutput->stop();
- m_boingtFile.seek(0);
- } else if (sender() == m_groanOutput ) {
- m_groanPlaying = false;
- m_groanOutput->stop();
- m_groanFile.seek(0);
- }
- }
- }
- EllipseItem::EllipseItem(QDeclarativeItem *parent)
- : QDeclarativeItem(parent), m_margin(0),m_burn(false)
- {
- /***********************************************************************************************
- *Set the items flag to indicate that the item has some contents. *
- ***********************************************************************************************/
- setFlag(QGraphicsItem::ItemHasNoContents, false);
- }
- QPainterPath EllipseItem::shape () const
- {
- /***********************************************************************************************
- *Return the items shape as an ellipse taking in to account the items size and margins. *
- ***********************************************************************************************/
- QPainterPath path;
- path.addEllipse( m_margin, m_margin,width()-m_margin*2,height()-m_margin*2);
- return path;
- }
- void EllipseItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
- QWidget *widget)
- {
- Q_UNUSED(option)
- Q_UNUSED(widget)
- /***********************************************************************************************
- *Draw the items shape. *
- ***********************************************************************************************/
- painter->save();
- painter->setPen(Qt::red);
- if (m_burn)
- painter->setBrush(Qt::black);
- else
- painter->setBrush(Qt::green);
- painter->drawPath(shape());
- painter->restore();
- }
- MultiTouchItem::MultiTouchItem(QDeclarativeItem *parent) : QDeclarativeItem(parent)
- {
- setAcceptTouchEvents(true);
- setAcceptedMouseButtons(Qt::LeftButton);
- setFlag(QGraphicsItem::ItemHasNoContents, false);
- }
- void MultiTouchItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
- QWidget *widget)
- {
- Q_UNUSED(option)
- Q_UNUSED(widget)
- /***********************************************************************************************
- *Draw the items pixmap with 0.5 opacity. *
- ***********************************************************************************************/
- painter->save();
- painter->setOpacity(0.5);
- painter->drawPixmap(0,0,width(),height(), m_pixmap);
- painter->restore();
- }
- bool MultiTouchItem::sceneEvent(QEvent *event)
- {
- /***********************************************************************************************
- *Overloaded items scene event, to handle touch and mouse press events. *
- *Handle TouchBegin, TouchUpdate, TouchEnd and mouse press/release events or revert to *
- *the QDeclarativeItems default implementation. *
- ***********************************************************************************************/
- switch (event->type()) {
- case QEvent::TouchBegin:
- emit pressed();
- break;
- case QEvent::TouchUpdate:
- break;
- case QEvent::TouchEnd:
- emit released();
- break;
- case QEvent::GraphicsSceneMousePress:
- emit pressed();
- break;
- case QEvent::GraphicsSceneMouseRelease:
- emit released();
- break;
- default:
- return QDeclarativeItem::sceneEvent(event);
- }
- return true;
- }
- void MultiTouchItem::setSource(const QString &s)
- {
- /***********************************************************************************************
- *Set the items pixmap and schedule a redraw. *
- ***********************************************************************************************/
- m_source = s;
- m_pixmap = QPixmap(s);
- update();
- }
|