File indexing completed on 2024-09-08 03:46:55
0001 /* 0002 SPDX-FileCopyrightText: 1999-2006 Éric Bischoff <ebischoff@nerim.net> 0003 SPDX-FileCopyrightText: 2007-2008 Albert Astals Cid <aacid@kde.org> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 /* Play ground widget */ 0009 0010 #include "playground.h" 0011 0012 #include <KConfig> 0013 #include <KConfigGroup> 0014 #include "ktuberling_debug.h" 0015 0016 #include <QAction> 0017 #include <QApplication> 0018 #include <QCursor> 0019 #include <QDataStream> 0020 #include <QDir> 0021 #include <QDomDocument> 0022 #include <QFile> 0023 #include <QFileInfo> 0024 #include <QGraphicsSvgItem> 0025 #include <QMouseEvent> 0026 #include <QPainter> 0027 #include <QPagedPaintDevice> 0028 0029 #include "action.h" 0030 #include "filefactory.h" 0031 #include "todraw.h" 0032 0033 static const char *saveGameTextScaleTextMode = "KTuberlingSaveGameV2"; 0034 static const char *saveGameTextTextMode = "KTuberlingSaveGameV3"; 0035 static const char *saveGameText = "KTuberlingSaveGameV4"; 0036 0037 // Constructor 0038 PlayGround::PlayGround(PlayGroundCallbacks *callbacks, QWidget *parent) 0039 : QGraphicsView(parent), m_callbacks(callbacks), m_newItem(nullptr), m_dragItem(nullptr), m_nextZValue(1), m_lockAspect(false), m_allowOnlyDrag(false) 0040 { 0041 setFrameStyle(QFrame::NoFrame); 0042 setOptimizationFlag(QGraphicsView::DontSavePainterState, true); // all items here save the painter state 0043 setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0044 setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 0045 setMouseTracking(true); 0046 } 0047 0048 // Destructor 0049 PlayGround::~PlayGround() 0050 { 0051 for (const SceneData &data : std::as_const(m_scenes)) 0052 { 0053 delete data.scene; 0054 delete data.undoStack; 0055 } 0056 } 0057 0058 // Reset the play ground 0059 void PlayGround::reset() 0060 { 0061 0062 const QList<QGraphicsItem*> items = scene()->items(); 0063 for(QGraphicsItem *item : items) 0064 { 0065 ToDraw *currentObject = qgraphicsitem_cast<ToDraw *>(item); 0066 delete currentObject; 0067 } 0068 0069 undoStack()->clear(); 0070 } 0071 0072 // Save objects laid down on the editable area 0073 bool PlayGround::saveAs(const QString & name) 0074 { 0075 QFile f(name); 0076 if (!f.open( QIODevice::WriteOnly ) ) 0077 return false; 0078 0079 QFileInfo gameBoard(m_gameboardFile); 0080 QDataStream out(&f); 0081 out.setVersion(QDataStream::Qt_4_5); 0082 out << QString::fromLatin1(saveGameText); 0083 out << gameBoard.fileName(); 0084 const QList<QGraphicsItem*> items = scene()->items(); 0085 for(QGraphicsItem *item : items) 0086 { 0087 ToDraw *currentObject = qgraphicsitem_cast<ToDraw *>(item); 0088 if (currentObject != nullptr) currentObject->save(out); 0089 } 0090 0091 return (f.error() == QFile::NoError); 0092 } 0093 0094 // Print gameboard's picture 0095 bool PlayGround::printPicture(QPagedPaintDevice &printer) 0096 { 0097 QPainter artist; 0098 QPixmap picture(getPicture()); 0099 0100 if (!artist.begin(&printer)) return false; 0101 artist.drawPixmap(QPoint(32, 32), picture); 0102 if (!artist.end()) return false; 0103 return true; 0104 } 0105 0106 // Get a pixmap containing the current picture 0107 QPixmap PlayGround::getPicture() 0108 { 0109 QPixmap result(mapFromScene(backgroundRect()).boundingRect().size()); 0110 QPainter artist(&result); 0111 scene()->render(&artist, QRectF(), backgroundRect(), Qt::IgnoreAspectRatio); 0112 artist.end(); 0113 return result; 0114 } 0115 0116 void PlayGround::connectRedoAction(QAction *action) 0117 { 0118 connect(action, &QAction::triggered, &m_undoGroup, &QUndoGroup::redo); 0119 connect(&m_undoGroup, &QUndoGroup::canRedoChanged, action, &QAction::setEnabled); 0120 } 0121 0122 void PlayGround::connectUndoAction(QAction *action) 0123 { 0124 connect(action, &QAction::triggered, &m_undoGroup, &QUndoGroup::undo); 0125 connect(&m_undoGroup, &QUndoGroup::canUndoChanged, action, &QAction::setEnabled); 0126 } 0127 0128 // Mouse pressed event 0129 void PlayGround::mousePressEvent(QMouseEvent *event) 0130 { 0131 if (m_gameboardFile.isEmpty()) return; 0132 0133 if (event->button() != Qt::LeftButton) return; 0134 0135 m_mousePressPos = event->pos(); 0136 0137 if (m_dragItem) placeDraggedItem(event->pos()); 0138 else if (m_newItem) placeNewItem(event->pos()); 0139 else 0140 { 0141 // see if the user clicked on the warehouse of items 0142 QPointF scenePos = mapToScene(event->pos()); 0143 QMap<QString, QString>::const_iterator it, itEnd; 0144 it = m_objectsNameSound.constBegin(); 0145 itEnd = m_objectsNameSound.constEnd(); 0146 QString foundElem; 0147 QRectF bounds; 0148 for( ; foundElem.isNull() && it != itEnd; ++it) 0149 { 0150 bounds = m_SvgRenderer.boundsOnElement(it.key()); 0151 if (bounds.contains(scenePos)) foundElem = it.key(); 0152 } 0153 0154 if (!foundElem.isNull()) 0155 { 0156 const double objectScale = m_objectsNameRatio.value(foundElem); 0157 const QSizeF elementSize = m_SvgRenderer.boundsOnElement(foundElem).size() * objectScale; 0158 QPointF itemPos = mapToScene(event->pos()); 0159 itemPos -= QPointF(elementSize.width()/2, elementSize.height()/2); 0160 0161 m_callbacks->playSound(m_objectsNameSound.value(foundElem)); 0162 0163 m_newItem = new ToDraw; 0164 m_newItem->setBeingDragged(true); 0165 m_newItem->setPos(clipPos(itemPos, m_newItem)); 0166 m_newItem->setSharedRenderer(&m_SvgRenderer); 0167 m_newItem->setElementId(foundElem); 0168 m_newItem->setZValue(m_nextZValue); 0169 m_nextZValue++; 0170 m_newItem->setTransform(QTransform::fromScale(objectScale, objectScale)); 0171 0172 scene()->addItem(m_newItem); 0173 setCursor(Qt::BlankCursor); 0174 } 0175 else 0176 { 0177 // see if the user clicked on an already existent item 0178 QGraphicsItem *dragItem = scene()->itemAt(mapToScene(event->pos()), QTransform()); 0179 m_dragItem = qgraphicsitem_cast<ToDraw*>(dragItem); 0180 if (m_dragItem) 0181 { 0182 QString elem = m_dragItem->elementId(); 0183 0184 m_callbacks->playSound(m_objectsNameSound.value(elem)); 0185 setCursor(Qt::BlankCursor); 0186 m_dragItem->setBeingDragged(true); 0187 m_itemDraggedPos = m_dragItem->pos(); 0188 0189 const QSizeF elementSize = m_dragItem->transform().mapRect(m_dragItem->unclippedRect()).size(); 0190 QPointF itemPos = mapToScene(event->pos()); 0191 itemPos -= QPointF(elementSize.width()/2, elementSize.height()/2); 0192 m_dragItem->setPos(clipPos(itemPos, m_dragItem)); 0193 } 0194 } 0195 } 0196 } 0197 0198 void PlayGround::mouseMoveEvent(QMouseEvent *event) 0199 { 0200 ToDraw *movingItem = m_newItem ? m_newItem : m_dragItem; 0201 if (movingItem) { 0202 QPointF itemPos = mapToScene(event->pos()); 0203 const QSizeF elementSize = movingItem->transform().mapRect(movingItem->unclippedRect()).size(); 0204 itemPos -= QPointF(elementSize.width()/2, elementSize.height()/2); 0205 0206 movingItem->setPos(clipPos(itemPos, movingItem)); 0207 } 0208 } 0209 0210 void PlayGround::mouseReleaseEvent(QMouseEvent *event) 0211 { 0212 QPoint point = event->pos() - m_mousePressPos; 0213 if (m_allowOnlyDrag || point.manhattanLength() > qApp->startDragDistance()) { 0214 if (m_dragItem) placeDraggedItem(event->pos()); 0215 else if (m_newItem) placeNewItem(event->pos()); 0216 } 0217 } 0218 0219 bool PlayGround::insideBackground(const QSizeF &size, const QPointF &pos) const 0220 { 0221 return backgroundRect().intersects(QRectF(pos, size)); 0222 } 0223 0224 QPointF PlayGround::clipPos(const QPointF &p, ToDraw *item) const 0225 { 0226 const qreal objectScale = m_objectsNameRatio.value(item->elementId()); 0227 0228 QPointF res = p; 0229 res.setX(qMax(qreal(0), res.x())); 0230 res.setY(qMax(qreal(0), res.y())); 0231 res.setX(qMin(m_SvgRenderer.defaultSize().width() - item->boundingRect().width() * objectScale, res.x())); 0232 res.setY(qMin(m_SvgRenderer.defaultSize().height()- item->boundingRect().height() * objectScale, res.y())); 0233 return res; 0234 } 0235 0236 QRectF PlayGround::backgroundRect() const 0237 { 0238 return m_SvgRenderer.boundsOnElement(QStringLiteral( "background" )); 0239 } 0240 0241 void PlayGround::placeDraggedItem(const QPoint &pos) 0242 { 0243 QPointF itemPos = mapToScene(pos); 0244 const QSizeF &elementSize = m_dragItem->transform().mapRect(m_dragItem->unclippedRect()).size(); 0245 itemPos -= QPointF(elementSize.width()/2, elementSize.height()/2); 0246 0247 if (insideBackground(elementSize, itemPos)) 0248 { 0249 m_dragItem->setBeingDragged(false); 0250 undoStack()->push(new ActionMove(m_dragItem, m_itemDraggedPos, m_nextZValue, scene())); 0251 m_nextZValue++; 0252 } 0253 else 0254 { 0255 undoStack()->push(new ActionRemove(m_dragItem, m_itemDraggedPos, scene())); 0256 } 0257 0258 setCursor(QCursor()); 0259 m_dragItem = nullptr; 0260 } 0261 0262 void PlayGround::placeNewItem(const QPoint &pos) 0263 { 0264 const QSizeF elementSize = m_newItem->transform().mapRect(m_newItem->unclippedRect()).size(); 0265 QPointF itemPos = mapToScene(pos); 0266 itemPos -= QPointF(elementSize.width()/2, elementSize.height()/2); 0267 if (insideBackground(elementSize, itemPos)) 0268 { 0269 m_newItem->setBeingDragged(false); 0270 undoStack()->push(new ActionAdd(m_newItem, scene())); 0271 } else { 0272 m_newItem->deleteLater(); 0273 } 0274 m_newItem = nullptr; 0275 setCursor(QCursor()); 0276 } 0277 0278 void PlayGround::recenterView() 0279 { 0280 // Cannot use sceneRect() because sometimes items get placed 0281 // with pos() outside rect (e.g. pizza theme) 0282 fitInView(QRect(QPoint(0,0), m_SvgRenderer.defaultSize()), 0283 m_lockAspect ? Qt::KeepAspectRatio : Qt::IgnoreAspectRatio); 0284 } 0285 0286 QGraphicsScene *PlayGround::scene() const 0287 { 0288 return m_scenes[m_gameboardFile].scene; 0289 } 0290 0291 QUndoStack *PlayGround::undoStack() const 0292 { 0293 return m_scenes[m_gameboardFile].undoStack; 0294 } 0295 0296 void PlayGround::resizeEvent(QResizeEvent *) 0297 { 0298 recenterView(); 0299 } 0300 0301 void PlayGround::lockAspectRatio(bool lock) 0302 { 0303 if (m_lockAspect != lock) 0304 { 0305 m_lockAspect = lock; 0306 recenterView(); 0307 } 0308 } 0309 0310 bool PlayGround::isAspectRatioLocked() const 0311 { 0312 return m_lockAspect; 0313 } 0314 0315 // Register the various playgrounds 0316 void PlayGround::registerPlayGrounds() 0317 { 0318 QSet<QString> list; 0319 const QStringList dirs = FileFactory::locateAll(QStringLiteral("pics")); 0320 for (const QString &dir : dirs) 0321 { 0322 const QStringList fileNames = QDir(dir).entryList(QStringList() << QStringLiteral("*.theme")); 0323 for (const QString &file : fileNames) 0324 { 0325 list << dir + QLatin1Char('/') + file; 0326 } 0327 } 0328 0329 QMultiMap<QString, QPair<QString, QPixmap>> sortedByName; 0330 0331 for(const QString &theme : std::as_const(list)) 0332 { 0333 QFile layoutFile(theme); 0334 if (layoutFile.open(QIODevice::ReadOnly)) 0335 { 0336 QDomDocument layoutDocument; 0337 if (layoutDocument.setContent(&layoutFile)) 0338 { 0339 QString desktop = layoutDocument.documentElement().attribute(QStringLiteral( "desktop" )); 0340 KConfig c( FileFactory::locate( QLatin1String( "pics/" ) + desktop ) ); 0341 KConfigGroup cg = c.group(QStringLiteral("KTuberlingTheme")); 0342 QString gameboard = layoutDocument.documentElement().attribute(QStringLiteral( "gameboard" )); 0343 QPixmap pixmap(200,100); 0344 pixmap.fill(Qt::transparent); 0345 playGroundPixmap(gameboard,pixmap); 0346 sortedByName.insert(cg.readEntry("Name"), QPair<QString, QPixmap>(theme, pixmap)); 0347 } 0348 } 0349 } 0350 0351 for(auto it = sortedByName.begin(); it != sortedByName.end(); ++it) { 0352 m_callbacks->registerGameboard(it.key(), it.value().first, it.value().second); 0353 } 0354 0355 } 0356 0357 void PlayGround::playGroundPixmap(const QString &playgroundName, QPixmap &pixmap) 0358 { 0359 m_SvgRenderer.load(FileFactory::locate(QLatin1String( "pics/" ) + playgroundName )); 0360 QPainter painter(&pixmap); 0361 m_SvgRenderer.render(&painter,QStringLiteral( "background" )); 0362 } 0363 0364 // Load background and draggable objects masks 0365 bool PlayGround::loadPlayGround(const QString &gameboardFile) 0366 { 0367 QFile layoutFile(gameboardFile); 0368 if (!layoutFile.open(QIODevice::ReadOnly)) return false; 0369 QDomDocument layoutDocument; 0370 if (!layoutDocument.setContent(&layoutFile)) return false; 0371 0372 const QDomElement playGroundElement = layoutDocument.documentElement(); 0373 0374 QString gameboardName = playGroundElement.attribute(QStringLiteral( "gameboard" )); 0375 0376 QColor bgColor = QColor(playGroundElement.attribute(QStringLiteral( "bgcolor" ), QStringLiteral( "#fff" ) ) ); 0377 if (!bgColor.isValid()) 0378 bgColor = Qt::white; 0379 0380 if (!m_SvgRenderer.load(FileFactory::locate( QLatin1String( "pics/" ) + gameboardName ))) 0381 return false; 0382 0383 const QDomNodeList objectsList = playGroundElement.elementsByTagName(QStringLiteral( "object" )); 0384 if (objectsList.count() < 1) 0385 return false; 0386 0387 m_objectsNameSound.clear(); 0388 0389 // create scene data if needed 0390 if(!m_scenes.contains(gameboardFile)) 0391 { 0392 SceneData &data = m_scenes[gameboardFile]; 0393 data.scene = new QGraphicsScene(); 0394 data.undoStack = new QUndoStack(); 0395 0396 QGraphicsSvgItem *background = new QGraphicsSvgItem(); 0397 background->setPos(QPoint(0,0)); 0398 background->setSharedRenderer(&m_SvgRenderer); 0399 background->setZValue(0); 0400 data.scene->addItem(background); 0401 0402 m_undoGroup.addStack(data.undoStack); 0403 } 0404 0405 for (int decoration = 0; decoration < objectsList.count(); decoration++) 0406 { 0407 const QDomElement objectElement = objectsList.item(decoration).toElement(); 0408 0409 const QString &objectName = objectElement.attribute(QStringLiteral( "name" )); 0410 if (m_SvgRenderer.elementExists(objectName)) 0411 { 0412 m_objectsNameSound.insert(objectName, objectElement.attribute(QStringLiteral( "sound" ))); 0413 m_objectsNameRatio.insert(objectName, objectElement.attribute(QStringLiteral( "scale" ), QStringLiteral( "1" )).toDouble()); 0414 } 0415 else 0416 { 0417 qCWarning(KTUBERLING_LOG) << objectName << "does not exist. Check" << gameboardFile; 0418 } 0419 } 0420 0421 setBackgroundBrush(bgColor); 0422 m_gameboardFile = gameboardFile; 0423 setScene(scene()); 0424 0425 recenterView(); 0426 0427 m_undoGroup.setActiveStack(undoStack()); 0428 0429 return true; 0430 } 0431 0432 void PlayGround::setAllowOnlyDrag(bool allowOnlyDrag) 0433 { 0434 m_allowOnlyDrag = allowOnlyDrag; 0435 } 0436 0437 QString PlayGround::currentGameboard() const 0438 { 0439 return m_gameboardFile; 0440 } 0441 0442 // Load objects and lay them down on the editable area 0443 PlayGround::LoadError PlayGround::loadFrom(const QString &name) 0444 { 0445 QFile f(name); 0446 if (!f.open(QIODevice::ReadOnly)) 0447 return OtherError; 0448 0449 QDataStream in(&f); 0450 in.setVersion(QDataStream::Qt_4_5); 0451 0452 bool scale = false; 0453 bool reopenInTextMode = false; 0454 QString magicText; 0455 in >> magicText; 0456 if ( QLatin1String( saveGameTextScaleTextMode ) == magicText) { 0457 scale = true; 0458 reopenInTextMode = true; 0459 } else if (QLatin1String( saveGameTextTextMode ) == magicText) { 0460 reopenInTextMode = true; 0461 } else if ( QLatin1String( saveGameText ) != magicText) { 0462 return OldFileVersionError; 0463 } 0464 0465 if (reopenInTextMode) { 0466 f.close(); 0467 if (!f.open(QIODevice::ReadOnly | QIODevice::Text)) 0468 return OtherError; 0469 in.setDevice(&f); 0470 in.setVersion(QDataStream::Qt_4_5); 0471 in >> magicText; 0472 } 0473 0474 sceneRect(); 0475 0476 if (in.atEnd()) 0477 return OtherError; 0478 0479 QString board; 0480 in >> board; 0481 0482 qreal xFactor = 1.0; 0483 qreal yFactor = 1.0; 0484 m_callbacks->changeGameboard(board); 0485 0486 reset(); 0487 0488 if (scale) { 0489 QSize defaultSize = m_SvgRenderer.defaultSize(); 0490 QSize currentSize = size(); 0491 xFactor = (qreal)defaultSize.width() / (qreal)currentSize.width(); 0492 yFactor = (qreal)defaultSize.height() / (qreal)currentSize.height(); 0493 } 0494 0495 while ( !in.atEnd() ) 0496 { 0497 ToDraw *obj = new ToDraw; 0498 if (!obj->load(in)) 0499 { 0500 delete obj; 0501 return OtherError; 0502 } 0503 obj->setSharedRenderer(&m_SvgRenderer); 0504 double objectScale = m_objectsNameRatio.value(obj->elementId()); 0505 obj->setTransform(QTransform::fromScale(objectScale, objectScale)); 0506 if (scale) { // Mimic old behavior 0507 QPointF storedPos = obj->pos(); 0508 storedPos.setX(storedPos.x() * xFactor); 0509 storedPos.setY(storedPos.y() * yFactor); 0510 obj->setPos(storedPos); 0511 } 0512 scene()->addItem(obj); 0513 undoStack()->push(new ActionAdd(obj, scene())); 0514 } 0515 if (f.error() == QFile::NoError) return NoError; 0516 else return OtherError; 0517 } 0518 0519 #include "moc_playground.cpp" 0520 0521 /* kate: replace-tabs on; indent-width 2; */