1 | /*************************************************************************** |
2 | * Copyright (C) 1999-2006 by Éric Bischoff <ebischoff@nerim.net> * |
3 | * Copyright (C) 2007-2008 by Albert Astals Cid <aacid@kde.org> * |
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 | |
11 | /* Play ground widget */ |
12 | |
13 | #include "playground.h" |
14 | |
15 | #include <klocale.h> |
16 | #include <kstandarddirs.h> |
17 | #include <kconfiggroup.h> |
18 | #include <kdebug.h> |
19 | |
20 | #include <QCursor> |
21 | #include <QDataStream> |
22 | #include <QDomDocument> |
23 | #include <QFile> |
24 | #include <QGraphicsSvgItem> |
25 | #include <QMouseEvent> |
26 | #include <QPainter> |
27 | #include <QPrinter> |
28 | #include <QFileInfo> |
29 | |
30 | #include <kstandardaction.h> |
31 | #include <kactioncollection.h> |
32 | #include <kstandardshortcut.h> |
33 | #include <kicon.h> |
34 | #include <QAction> |
35 | |
36 | #include "action.h" |
37 | #include "toplevel.h" |
38 | #include "todraw.h" |
39 | |
40 | static const char *saveGameTextScaleTextMode = "KTuberlingSaveGameV2" ; |
41 | static const char *saveGameTextTextMode = "KTuberlingSaveGameV3" ; |
42 | static const char *saveGameText = "KTuberlingSaveGameV4" ; |
43 | |
44 | // Constructor |
45 | PlayGround::PlayGround(TopLevel *parent) |
46 | : QGraphicsView(parent), m_dragItem(0), m_nextZValue(1), m_lockAspect(false) |
47 | { |
48 | m_topLevel = parent; |
49 | setFrameStyle(QFrame::NoFrame); |
50 | setOptimizationFlag(QGraphicsView::DontSavePainterState, true); // all items here save the painter state |
51 | setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
52 | setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); |
53 | } |
54 | |
55 | // Destructor |
56 | PlayGround::~PlayGround() |
57 | { |
58 | foreach (const SceneData &data, m_scenes) |
59 | { |
60 | delete data.scene; |
61 | delete data.undoStack; |
62 | } |
63 | } |
64 | |
65 | // Reset the play ground |
66 | void PlayGround::reset() |
67 | { |
68 | |
69 | foreach(QGraphicsItem *item, scene()->items()) |
70 | { |
71 | ToDraw *currentObject = qgraphicsitem_cast<ToDraw *>(item); |
72 | delete currentObject; |
73 | } |
74 | |
75 | undoStack()->clear(); |
76 | } |
77 | |
78 | // Save objects laid down on the editable area |
79 | bool PlayGround::saveAs(const QString & name) |
80 | { |
81 | QFile f(name); |
82 | if (!f.open( QIODevice::WriteOnly ) ) |
83 | return false; |
84 | |
85 | QFileInfo gameBoard(m_gameboardFile); |
86 | QDataStream out(&f); |
87 | out.setVersion(QDataStream::Qt_4_5); |
88 | out << QString::fromLatin1(saveGameText); |
89 | out << gameBoard.fileName(); |
90 | foreach(QGraphicsItem *item, scene()->items()) |
91 | { |
92 | ToDraw *currentObject = qgraphicsitem_cast<ToDraw *>(item); |
93 | if (currentObject != NULL) currentObject->save(out); |
94 | } |
95 | |
96 | return (f.error() == QFile::NoError); |
97 | } |
98 | |
99 | // Print gameboard's picture |
100 | bool PlayGround::printPicture(QPrinter &printer) |
101 | { |
102 | QPainter artist; |
103 | QPixmap picture(getPicture()); |
104 | |
105 | if (!artist.begin(&printer)) return false; |
106 | artist.drawPixmap(QPoint(32, 32), picture); |
107 | if (!artist.end()) return false; |
108 | return true; |
109 | } |
110 | |
111 | // Get a pixmap containing the current picture |
112 | QPixmap PlayGround::getPicture() |
113 | { |
114 | QPixmap result(mapFromScene(backgroundRect()).boundingRect().size()); |
115 | QPainter artist(&result); |
116 | scene()->render(&artist, QRectF(), backgroundRect(), Qt::IgnoreAspectRatio); |
117 | artist.end(); |
118 | return result; |
119 | } |
120 | |
121 | void PlayGround::connectRedoAction(QAction *action) |
122 | { |
123 | connect(action, SIGNAL(triggered()), &m_undoGroup, SLOT(redo())); |
124 | connect(&m_undoGroup, SIGNAL(canRedoChanged(bool)), action, SLOT(setEnabled(bool))); |
125 | } |
126 | |
127 | void PlayGround::connectUndoAction(QAction *action) |
128 | { |
129 | connect(action, SIGNAL(triggered()), &m_undoGroup, SLOT(undo())); |
130 | connect(&m_undoGroup, SIGNAL(canUndoChanged(bool)), action, SLOT(setEnabled(bool))); |
131 | } |
132 | |
133 | // Mouse pressed event |
134 | void PlayGround::mousePressEvent(QMouseEvent *event) |
135 | { |
136 | if (m_gameboardFile.isEmpty()) return; |
137 | |
138 | if (event->button() != Qt::LeftButton) return; |
139 | |
140 | if (m_dragItem) placeDraggedItem(event->pos()); |
141 | else if (!m_pickedElement.isNull()) placeNewItem(event->pos()); |
142 | else |
143 | { |
144 | // see if the user clicked on the warehouse of items |
145 | QPointF scenePos = mapToScene(event->pos()); |
146 | QMap<QString, QString>::const_iterator it, itEnd; |
147 | it = m_objectsNameSound.constBegin(); |
148 | itEnd = m_objectsNameSound.constEnd(); |
149 | QString foundElem; |
150 | QRectF bounds; |
151 | for( ; foundElem.isNull() && it != itEnd; ++it) |
152 | { |
153 | bounds = m_SvgRenderer.boundsOnElement(it.key()); |
154 | if (bounds.contains(scenePos)) foundElem = it.key(); |
155 | } |
156 | |
157 | if (!foundElem.isNull()) |
158 | { |
159 | bounds = mapFromScene(bounds).boundingRect(); |
160 | double objectScale = m_objectsNameRatio.value(foundElem); |
161 | int width = qRound(bounds.width() * objectScale); |
162 | int height = qRound(bounds.height() * objectScale); |
163 | |
164 | m_topLevel->playSound(m_objectsNameSound.value(foundElem)); |
165 | setCursor(QCursor(toPixmap(foundElem, width, height, &m_SvgRenderer))); |
166 | m_pickedElement = foundElem; |
167 | } |
168 | else |
169 | { |
170 | // see if the user clicked on an already existent item |
171 | QGraphicsItem *dragItem = scene()->itemAt(mapToScene(event->pos())); |
172 | m_dragItem = qgraphicsitem_cast<ToDraw*>(dragItem); |
173 | if (m_dragItem) |
174 | { |
175 | QRectF rect = m_dragItem->unclippedRect(); |
176 | rect = m_dragItem->transform().mapRect(rect); |
177 | QPolygon poly = mapFromScene(rect); |
178 | QSize size = poly.boundingRect().size(); // the polygon should be a rect... |
179 | QString elem = m_dragItem->elementId(); |
180 | setCursor(QCursor(toPixmap(elem, size.width(), size.height(), &m_SvgRenderer))); |
181 | |
182 | scene()->removeItem(m_dragItem); |
183 | m_topLevel->playSound(m_objectsNameSound.value(elem)); |
184 | } |
185 | } |
186 | } |
187 | } |
188 | |
189 | bool PlayGround::insideBackground(const QSizeF &size, const QPointF &pos) const |
190 | { |
191 | return backgroundRect().intersects(QRectF(pos, size)); |
192 | } |
193 | |
194 | QRectF PlayGround::backgroundRect() const |
195 | { |
196 | return m_SvgRenderer.boundsOnElement(QLatin1String( "background" )); |
197 | } |
198 | |
199 | void PlayGround::placeDraggedItem(const QPoint &pos) |
200 | { |
201 | QPointF itemPos = mapToScene(pos); |
202 | const QSizeF &elementSize = m_dragItem->transform().mapRect(m_dragItem->unclippedRect()).size(); |
203 | itemPos -= QPointF(elementSize.width()/2, elementSize.height()/2); |
204 | |
205 | if (insideBackground(elementSize, itemPos)) |
206 | { |
207 | scene()->addItem(m_dragItem); |
208 | undoStack()->push(new ActionMove(m_dragItem, itemPos, m_nextZValue, scene())); |
209 | m_nextZValue++; |
210 | } |
211 | else |
212 | { |
213 | undoStack()->push(new ActionRemove(m_dragItem, scene())); |
214 | } |
215 | |
216 | setCursor(QCursor()); |
217 | m_dragItem = 0; |
218 | } |
219 | |
220 | void PlayGround::placeNewItem(const QPoint &pos) |
221 | { |
222 | double objectScale = m_objectsNameRatio.value(m_pickedElement); |
223 | QSizeF elementSize = m_SvgRenderer.boundsOnElement(m_pickedElement).size() * objectScale; |
224 | QPointF itemPos = mapToScene(pos); |
225 | |
226 | itemPos -= QPointF(elementSize.width()/2, elementSize.height()/2); |
227 | |
228 | if (insideBackground(elementSize, itemPos)) |
229 | { |
230 | ToDraw *item = new ToDraw; |
231 | item->setElementId(m_pickedElement); |
232 | item->setPos(itemPos); |
233 | item->setSharedRenderer(&m_SvgRenderer); |
234 | item->setZValue(m_nextZValue); |
235 | m_nextZValue++; |
236 | item->scale(objectScale, objectScale); |
237 | |
238 | undoStack()->push(new ActionAdd(item, scene())); |
239 | } |
240 | |
241 | setCursor(QCursor()); |
242 | m_pickedElement.clear(); |
243 | } |
244 | |
245 | void PlayGround::recenterView() |
246 | { |
247 | // Cannot use sceneRect() because sometimes items get placed |
248 | // with pos() outside rect (e.g. pizza theme) |
249 | fitInView(QRect(QPoint(0,0), m_SvgRenderer.defaultSize()), |
250 | m_lockAspect ? Qt::KeepAspectRatio : Qt::IgnoreAspectRatio); |
251 | } |
252 | |
253 | QGraphicsScene *PlayGround::scene() const |
254 | { |
255 | return m_scenes[m_gameboardFile].scene; |
256 | } |
257 | |
258 | QUndoStack *PlayGround::undoStack() const |
259 | { |
260 | return m_scenes[m_gameboardFile].undoStack; |
261 | } |
262 | |
263 | void PlayGround::resizeEvent(QResizeEvent *) |
264 | { |
265 | recenterView(); |
266 | } |
267 | |
268 | void PlayGround::lockAspectRatio(bool lock) |
269 | { |
270 | if (m_lockAspect != lock) |
271 | { |
272 | m_lockAspect = lock; |
273 | recenterView(); |
274 | } |
275 | } |
276 | |
277 | bool PlayGround::isAspectRatioLocked() const |
278 | { |
279 | return m_lockAspect; |
280 | } |
281 | |
282 | // Register the various playgrounds |
283 | void PlayGround::registerPlayGrounds() |
284 | { |
285 | const QStringList list = KGlobal::dirs()->findAllResources("appdata" , QLatin1String( "pics/*.theme" )); |
286 | |
287 | foreach(const QString &theme, list) |
288 | { |
289 | QFile layoutFile(theme); |
290 | if (layoutFile.open(QIODevice::ReadOnly)) |
291 | { |
292 | QDomDocument layoutDocument; |
293 | if (layoutDocument.setContent(&layoutFile)) |
294 | { |
295 | QString desktop = layoutDocument.documentElement().attribute(QLatin1String( "desktop" )); |
296 | KConfig c( KStandardDirs::locate("appdata" , QLatin1String( "pics/" ) ) + desktop); |
297 | KConfigGroup cg = c.group("KTuberlingTheme" ); |
298 | QString gameboard = layoutDocument.documentElement().attribute(QLatin1String( "gameboard" )); |
299 | QPixmap pixmap(200,100); |
300 | pixmap.fill(Qt::transparent); |
301 | playGroundPixmap(gameboard,pixmap); |
302 | m_topLevel->registerGameboard(cg.readEntry("Name" ), theme, pixmap); |
303 | } |
304 | } |
305 | } |
306 | } |
307 | |
308 | void PlayGround::playGroundPixmap(const QString &playgroundName, QPixmap &pixmap) |
309 | { |
310 | m_SvgRenderer.load(KStandardDirs::locate("appdata" , QLatin1String( "pics/" ) + playgroundName)); |
311 | QPainter painter(&pixmap); |
312 | m_SvgRenderer.render(&painter,QLatin1String( "background" )); |
313 | } |
314 | |
315 | // Load background and draggable objects masks |
316 | bool PlayGround::loadPlayGround(const QString &gameboardFile) |
317 | { |
318 | QDomNodeList playGroundsList, |
319 | editableAreasList, objectsList, |
320 | gameAreasList, maskAreasList, soundNamesList, labelsList; |
321 | QDomElement playGroundElement, |
322 | editableAreaElement, objectElement, |
323 | gameAreaElement, maskAreaElement, soundNameElement, labelElement; |
324 | QDomAttr gameboardAttribute, masksAttribute, |
325 | leftAttribute, topAttribute, rightAttribute, bottomAttribute, |
326 | refAttribute; |
327 | |
328 | QFile layoutFile(gameboardFile); |
329 | if (!layoutFile.open(QIODevice::ReadOnly)) return false; |
330 | |
331 | QDomDocument layoutDocument; |
332 | if (!layoutDocument.setContent(&layoutFile)) return false; |
333 | |
334 | playGroundElement = layoutDocument.documentElement(); |
335 | |
336 | QString gameboardName = playGroundElement.attribute(QLatin1String( "gameboard" )); |
337 | |
338 | QColor bgColor = QColor(playGroundElement.attribute(QLatin1String( "bgcolor" ), QLatin1String( "#fff" ) ) ); |
339 | if (!bgColor.isValid()) |
340 | bgColor = Qt::white; |
341 | |
342 | if (!m_SvgRenderer.load(KStandardDirs::locate("appdata" , QLatin1String( "pics/" ) + gameboardName))) |
343 | return false; |
344 | |
345 | objectsList = playGroundElement.elementsByTagName(QLatin1String( "object" )); |
346 | if (objectsList.count() < 1) |
347 | return false; |
348 | |
349 | m_objectsNameSound.clear(); |
350 | |
351 | // create scene data if needed |
352 | if(!m_scenes.contains(gameboardFile)) |
353 | { |
354 | SceneData &data = m_scenes[gameboardFile]; |
355 | data.scene = new QGraphicsScene(); |
356 | data.undoStack = new QUndoStack(); |
357 | |
358 | QGraphicsSvgItem *background = new QGraphicsSvgItem(); |
359 | background->setPos(QPoint(0,0)); |
360 | background->setSharedRenderer(&m_SvgRenderer); |
361 | background->setZValue(0); |
362 | data.scene->addItem(background); |
363 | |
364 | m_undoGroup.addStack(data.undoStack); |
365 | } |
366 | |
367 | for (int decoration = 0; decoration < objectsList.count(); decoration++) |
368 | { |
369 | objectElement = (const QDomElement &) objectsList.item(decoration).toElement(); |
370 | |
371 | const QString &objectName = objectElement.attribute(QLatin1String( "name" )); |
372 | if (m_SvgRenderer.elementExists(objectName)) |
373 | { |
374 | m_objectsNameSound.insert(objectName, objectElement.attribute(QLatin1String( "sound" ))); |
375 | m_objectsNameRatio.insert(objectName, objectElement.attribute(QLatin1String( "scale" ), QLatin1String( "1" )).toDouble()); |
376 | } |
377 | else |
378 | { |
379 | kWarning() << objectName << "does not exist. Check" << gameboardFile; |
380 | } |
381 | } |
382 | |
383 | setBackgroundBrush(bgColor); |
384 | m_gameboardFile = gameboardFile; |
385 | setScene(scene()); |
386 | |
387 | recenterView(); |
388 | |
389 | m_undoGroup.setActiveStack(undoStack()); |
390 | |
391 | return true; |
392 | } |
393 | |
394 | QString PlayGround::currentGameboard() const |
395 | { |
396 | return m_gameboardFile; |
397 | } |
398 | |
399 | // Load objects and lay them down on the editable area |
400 | PlayGround::LoadError PlayGround::loadFrom(const QString &name) |
401 | { |
402 | QFile f(name); |
403 | if (!f.open(QIODevice::ReadOnly)) |
404 | return OtherError; |
405 | |
406 | QDataStream in(&f); |
407 | in.setVersion(QDataStream::Qt_4_5); |
408 | |
409 | bool scale = false; |
410 | bool reopenInTextMode = false; |
411 | QString magicText; |
412 | in >> magicText; |
413 | if ( QLatin1String( saveGameTextScaleTextMode ) == magicText) { |
414 | scale = true; |
415 | reopenInTextMode = true; |
416 | } else if (QLatin1String( saveGameTextTextMode ) == magicText) { |
417 | reopenInTextMode = true; |
418 | } else if ( QLatin1String( saveGameText ) != magicText) { |
419 | return OldFileVersionError; |
420 | } |
421 | |
422 | if (reopenInTextMode) { |
423 | f.close(); |
424 | if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) |
425 | return OtherError; |
426 | in.setDevice(&f); |
427 | in.setVersion(QDataStream::Qt_4_5); |
428 | in >> magicText; |
429 | } |
430 | |
431 | sceneRect(); |
432 | |
433 | if (in.atEnd()) |
434 | return OtherError; |
435 | |
436 | QString board; |
437 | in >> board; |
438 | |
439 | qreal xFactor = 1.0; |
440 | qreal yFactor = 1.0; |
441 | m_topLevel->changeGameboard(board); |
442 | |
443 | reset(); |
444 | |
445 | if (scale) { |
446 | QSize defaultSize = m_SvgRenderer.defaultSize(); |
447 | QSize currentSize = size(); |
448 | xFactor = (qreal)defaultSize.width() / (qreal)currentSize.width(); |
449 | yFactor = (qreal)defaultSize.height() / (qreal)currentSize.height(); |
450 | } |
451 | |
452 | while ( !in.atEnd() ) |
453 | { |
454 | ToDraw *obj = new ToDraw; |
455 | if (!obj->load(in)) |
456 | { |
457 | delete obj; |
458 | return OtherError; |
459 | } |
460 | obj->setSharedRenderer(&m_SvgRenderer); |
461 | double objectScale = m_objectsNameRatio.value(obj->elementId()); |
462 | obj->scale(objectScale, objectScale); |
463 | if (scale) { // Mimic old behavior |
464 | QPointF storedPos = obj->pos(); |
465 | storedPos.setX(storedPos.x() * xFactor); |
466 | storedPos.setY(storedPos.y() * yFactor); |
467 | obj->setPos(storedPos); |
468 | } |
469 | undoStack()->push(new ActionAdd(obj, scene())); |
470 | } |
471 | if (f.error() == QFile::NoError) return NoError; |
472 | else return OtherError; |
473 | } |
474 | |
475 | /* kate: replace-tabs on; indent-width 2; */ |
476 | |