scene.cpp 9.93 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
/***************************************************************************
 *   Copyright 2009 Stefan Majewsky <majewsky@gmx.net>
 *
 *   This program is free software; you can redistribute it and/or
 *   modify it under the terms of the GNU General Public
 *   License as published by the Free Software Foundation; either
 *   version 2 of the License, or (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
***************************************************************************/

#include "scene.h"
#include "part.h"
#include "piece.h"
22
#include "../file-io/collection.h"
23
#include "../file-io/puzzle.h"
24
#include "settings.h"
25

26
#include <cmath>
Stefan Majewsky's avatar
Stefan Majewsky committed
27
#include <QFile>
28
#include <QGraphicsView>
29
#include <QPropertyAnimation>
30 31
#include <QTimer>
#include <QtConcurrentRun>
Stefan Majewsky's avatar
Stefan Majewsky committed
32 33
#include <KConfig>
#include <KConfigGroup>
34 35
#include <KMessageBox>
#include <KLocalizedString>
Stefan Majewsky's avatar
Stefan Majewsky committed
36 37
#include <KStandardDirs>

38 39
typedef QPair<int, int> DoubleIntPair; //comma in type is not possible in foreach macro

40 41
Palapeli::Scene::Scene(QObject* parent)
	: QGraphicsScene(parent)
42
	, m_constrained(false)
43
	, m_partGroup(new Palapeli::EmptyGraphicsObject)
44
	, m_loadingPuzzle(false)
45
{
46
	addItem(m_partGroup);
47
	connect(&m_metadataLoader, SIGNAL(finished()), this, SLOT(continueLoading()));
48 49
}

50 51 52 53 54 55 56 57
QRectF Palapeli::Scene::partsBoundingRect() const
{
	QRectF result;
	foreach (Palapeli::Part* part, m_parts)
		result |= part->mapToScene(part->piecesBoundingRect()).boundingRect();
	return result;
}

58 59 60 61 62 63 64 65 66 67 68 69 70
bool Palapeli::Scene::isConstrained() const
{
	return m_constrained;
}

void Palapeli::Scene::setConstrained(bool constrained)
{
	if (m_constrained == constrained)
		return;
	m_constrained = constrained;
	emit constrainedChanged(constrained);
}

71
void Palapeli::Scene::loadPuzzle(const QModelIndex& index)
72
{
73 74
	if (m_loadingPuzzle)
		return;
75 76 77
	//load puzzle
	QObject* puzzlePayload = index.data(Palapeli::Collection::PuzzleObjectRole).value<QObject*>();
	Palapeli::Puzzle* puzzle = qobject_cast<Palapeli::Puzzle*>(puzzlePayload);
78 79 80 81 82 83
	if (puzzle && m_puzzle != puzzle)
	{
		m_puzzle = puzzle;
		m_identifier = index.data(Palapeli::Collection::IdentifierRole).toString();
		loadPuzzleInternal();
	}
84 85
}

86
void Palapeli::Scene::loadPuzzleInternal()
87
{
88
	m_loadingPuzzle = true;
89 90
	//reset behavioral parameters
	setConstrained(false);
91
	//clear scene
92 93
	qDeleteAll(m_pieces); m_pieces.clear();
	qDeleteAll(m_parts); m_parts.clear();
94 95 96 97 98 99 100
	emit reportProgress(0, 0);
	//begin to load puzzle
	startLoading();
}

void Palapeli::Scene::startLoading()
{
Stefan Majewsky's avatar
Stefan Majewsky committed
101
	if (m_puzzle)
102 103 104 105 106 107 108
	{
		if (m_puzzle->metadata())
			continueLoading();
		else
			m_metadataLoader.setFuture(QtConcurrent::run(m_puzzle.data(), &Palapeli::Puzzle::readMetadata, false));
			//will call continueLoading() when done reading metadata
	}
109 110 111 112
}

void Palapeli::Scene::continueLoading()
{
Stefan Majewsky's avatar
Stefan Majewsky committed
113 114
	if (!m_puzzle)
		return;
115
	//continue to read puzzle
116
	if (!m_puzzle->metadata()) //reading the archive has failed
117 118 119 120
		return;
	if (!m_puzzle->readContents()) //this cannot be done in a separate thread
		return;
	//delay piece loading for UI responsibility
121
	if (!m_puzzle->contents()->pieces.isEmpty())
122 123 124 125 126
		QTimer::singleShot(0, this, SLOT(loadNextPart()));
}

void Palapeli::Scene::loadNextPart()
{
Stefan Majewsky's avatar
Stefan Majewsky committed
127 128
	if (!m_puzzle)
		return;
129 130
	//add pieces and parts, but only one piece at a time
	const Palapeli::PuzzleContents* contents = m_puzzle->contents();
131 132 133
	QMap<int, QPixmap>::const_iterator iterPieces = contents->pieces.begin();
	const QMap<int, QPixmap>::const_iterator iterPiecesEnd = contents->pieces.end();
	for (int pieceID = iterPieces.key(); iterPieces != iterPiecesEnd; pieceID = (++iterPieces).key())
134
	{
135 136
		if (m_pieces.contains(pieceID))
			continue; //already loaded
137
		Palapeli::Piece* piece = new Palapeli::Piece(iterPieces.value(), contents->pieceOffsets[pieceID]);
138
		m_pieces[pieceID] = piece;
139 140 141
		Palapeli::Part* part = new Palapeli::Part(piece);
		connect(part, SIGNAL(destroyed(QObject*)), this, SLOT(partDestroyed(QObject*)));
		connect(part, SIGNAL(partMoved()), this, SLOT(partMoved()));
Stefan Majewsky's avatar
Stefan Majewsky committed
142
		connect(part, SIGNAL(partMoving()), this, SLOT(partMoving()));
143
		part->setParentItem(m_partGroup);
144
		m_parts << part;
145 146 147 148 149 150
		//continue with next part after eventloop run
		if (contents->pieces.size() > m_pieces.size())
			QTimer::singleShot(0, this, SLOT(loadNextPart()));
		else
			QTimer::singleShot(0, this, SLOT(finishLoading()));
		return;
151
	}
152 153 154 155
}

void Palapeli::Scene::finishLoading()
{
Stefan Majewsky's avatar
Stefan Majewsky committed
156 157
	if (!m_puzzle)
		return;
158
	const Palapeli::PuzzleContents* contents = m_puzzle->contents();
159
	//add piece relations
Stefan Majewsky's avatar
Stefan Majewsky committed
160
	foreach (const DoubleIntPair& relation, contents->relations)
161
	{
162 163
		Palapeli::Piece* firstPiece = m_pieces[relation.first];
		Palapeli::Piece* secondPiece = m_pieces[relation.second];
164 165 166
		firstPiece->addNeighbor(secondPiece);
		secondPiece->addNeighbor(firstPiece);
	}
Stefan Majewsky's avatar
Stefan Majewsky committed
167
	//Is "savegame" available?
168 169
	static const QString pathTemplate = QString::fromLatin1("collection/%1.save");
	KConfig saveConfig(KStandardDirs::locateLocal("appdata", pathTemplate.arg(m_identifier)));
Stefan Majewsky's avatar
Stefan Majewsky committed
170 171 172 173
	if (saveConfig.hasGroup("SaveGame"))
	{
		//read piece positions from savegame
		KConfigGroup saveGroup(&saveConfig, "SaveGame");
174 175
		QMap<int, Palapeli::Piece*>::const_iterator iterPieces = m_pieces.constBegin();
		const QMap<int, Palapeli::Piece*>::const_iterator iterPiecesEnd = m_pieces.constEnd();
176
		for (int pieceID = iterPieces.key(); iterPieces != iterPiecesEnd; pieceID = (++iterPieces).key())
Stefan Majewsky's avatar
Stefan Majewsky committed
177
		{
178
			Palapeli::Part* part = iterPieces.value()->part();
179
			part->setPos(saveGroup.readEntry(QString::number(pieceID), QPointF()));
Stefan Majewsky's avatar
Stefan Majewsky committed
180 181 182 183
		}
		for (iterPieces = m_pieces.constBegin(); iterPieces != iterPiecesEnd; ++iterPieces)
		{
			Palapeli::Part* part = iterPieces.value()->part();
Stefan Majewsky's avatar
Stefan Majewsky committed
184
			part->searchConnections();
185 186
			part->validatePosition();
		}
Stefan Majewsky's avatar
Stefan Majewsky committed
187 188
	}
	else
189
	{
190 191 192
		//place parts at nice positions
		//step 1: determine maximum part size
		QSizeF partAreaSize;
Stefan Majewsky's avatar
Stefan Majewsky committed
193
		foreach (Palapeli::Part* part, m_parts)
194 195 196 197
			partAreaSize = partAreaSize.expandedTo(part->scenePiecesBoundingRect().size());
		partAreaSize *= 1.3; //more space for each part
		//step 2: place parts in a grid in random order
		QList<Palapeli::Part*> partPool(m_parts);
198
		const int xCount = floor(sqrt((float)partPool.count()));
199
		for (int y = 0; !partPool.isEmpty(); ++y)
Stefan Majewsky's avatar
Stefan Majewsky committed
200
		{
201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218
			for (int x = 0; x < xCount && !partPool.isEmpty(); ++x)
			{
				//select random part
				Palapeli::Part* part = partPool.takeAt(qrand() % partPool.count());
				//determine part offset
				part->setPos(QPointF());
				const QRectF br = part->scenePiecesBoundingRect();
				const QPointF partOffset = br.topLeft();
				const QSizeF partSize = br.size();
				//determine random position inside part area
				const QPointF areaOffset(
					qrand() % (int)(partAreaSize.width() - partSize.width()),
					qrand() % (int)(partAreaSize.height() - partSize.height())
				);
				//move to desired position in (x,y) grid
				const QPointF gridBasePosition(x * partAreaSize.width(), y * partAreaSize.height());
				part->setPos(gridBasePosition + areaOffset - partOffset);
			}
Stefan Majewsky's avatar
Stefan Majewsky committed
219
		}
220
	}
221
	//determine scene rect
222
	setSceneRect(partsBoundingRect());
223 224
	//initialize external progress display
	emit reportProgress(m_pieces.count(), m_parts.count());
Stefan Majewsky's avatar
Stefan Majewsky committed
225
	emit puzzleStarted();
226
	m_loadingPuzzle = false;
227 228 229
	//check if puzzle has been completed
	if (m_parts.count() == 1)
	{
230
		int result = KMessageBox::questionYesNo(views()[0], i18n("You have finished the puzzle the last time. Do you want to restart it now?"));
231 232 233
		if (result == KMessageBox::Yes)
			restartPuzzle();
	}
234 235 236 237
}

void Palapeli::Scene::partDestroyed(QObject* object)
{
238
	int oldCount = m_parts.count();
239
	m_parts.removeAll(reinterpret_cast<Palapeli::Part*>(object));
240 241 242
	//victory animation
	if (m_parts.count() == 1 && oldCount > 1 && !m_loadingPuzzle)
		QTimer::singleShot(0, this, SLOT(playVictoryAnimation()));
243 244
}

Stefan Majewsky's avatar
Stefan Majewsky committed
245 246 247 248
void Palapeli::Scene::partMoving()
{
	//if scene size constraint is not active, enlarge scene rect as needed
	if (!m_constrained)
249
		setSceneRect(partsBoundingRect() | sceneRect());
Stefan Majewsky's avatar
Stefan Majewsky committed
250 251
}

252 253
void Palapeli::Scene::partMoved()
{
Stefan Majewsky's avatar
Stefan Majewsky committed
254
	partMoving();
255
	emit reportProgress(m_pieces.count(), m_parts.count());
Stefan Majewsky's avatar
Stefan Majewsky committed
256
	//save piece positions
257
	static const QString pathTemplate = QString::fromLatin1("collection/%1.save");
Stefan Majewsky's avatar
Stefan Majewsky committed
258
	KConfig saveConfig(KStandardDirs::locateLocal("appdata", pathTemplate.arg(m_identifier)));
Stefan Majewsky's avatar
Stefan Majewsky committed
259
	KConfigGroup saveGroup(&saveConfig, "SaveGame");
260 261
	QMap<int, Palapeli::Piece*>::const_iterator iterPieces = m_pieces.constBegin();
	const QMap<int, Palapeli::Piece*>::const_iterator iterPiecesEnd = m_pieces.constEnd();
262 263
	for (int pieceID = iterPieces.key(); iterPieces != iterPiecesEnd; pieceID = (++iterPieces).key())
		saveGroup.writeEntry(QString::number(pieceID), iterPieces.value()->part()->pos());
264
}
Stefan Majewsky's avatar
Stefan Majewsky committed
265

266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
void Palapeli::Scene::playVictoryAnimation()
{
	setConstrained(true);
	QPropertyAnimation* animation = new QPropertyAnimation(this, "sceneRect", this);
	animation->setStartValue(sceneRect());
	animation->setEndValue(partsBoundingRect());
	animation->setDuration(1000);
	connect(animation, SIGNAL(finished()), this, SLOT(playVictoryAnimation2()));
	animation->start(QAbstractAnimation::DeleteWhenStopped);
}

void Palapeli::Scene::playVictoryAnimation2()
{
	setSceneRect(partsBoundingRect());
	QTimer::singleShot(100, this, SIGNAL(victoryAnimationFinished()));
	QTimer::singleShot(1500, this, SLOT(playVictoryAnimation3())); //give the View some time to play its part of the victory animation
}

void Palapeli::Scene::playVictoryAnimation3()
{
	KMessageBox::information(views()[0], i18n("Great! You have finished the puzzle."));
}

Stefan Majewsky's avatar
Stefan Majewsky committed
289 290
void Palapeli::Scene::restartPuzzle()
{
291
	static const QString pathTemplate = QString::fromLatin1("collection/%1.save");
Stefan Majewsky's avatar
Stefan Majewsky committed
292 293
	QFile(KStandardDirs::locateLocal("appdata", pathTemplate.arg(m_identifier))).remove();
	//reload puzzle
294
	loadPuzzleInternal();
Stefan Majewsky's avatar
Stefan Majewsky committed
295 296
}

Stefan Majewsky's avatar
Stefan Majewsky committed
297
#include "scene.moc"