File indexing completed on 2024-05-12 04:06:25

0001 /*
0002     SPDX-FileCopyrightText: 2009, 2010 Stefan Majewsky <majewsky@gmx.net>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "scene.h"
0008 #include "constraintvisualizer.h"
0009 #include "mergegroup.h"
0010 #include "piece.h"
0011 #include "settings.h"
0012 
0013 #include <QGraphicsView>
0014 #include "palapeli_debug.h"
0015 
0016 Palapeli::Scene::Scene(QObject* parent)
0017     : QGraphicsScene(parent)
0018     , m_constrained(false)
0019     , m_constraintVisualizer(new Palapeli::ConstraintVisualizer(this))
0020     , m_puzzle(nullptr)
0021     , m_pieceAreaSize(QSizeF(32.0, 32.0))   // Allow 1024 pixels initially.
0022     , m_margin(10.0)
0023     , m_handleWidth(7.0)
0024     , m_minGrid(1)              // Min. space for puzzle table.
0025 {
0026     initializeGrid(QPointF(0.0, 0.0));
0027 }
0028 
0029 void Palapeli::Scene::addPieceToList(Palapeli::Piece* piece)
0030 {
0031     m_pieces << piece;
0032 }
0033 
0034 void Palapeli::Scene::addPieceItemsToScene()
0035 {
0036     for (Palapeli::Piece * piece : std::as_const(m_pieces)) {
0037         addItem(piece);
0038         connect(piece, &Piece::moved, this, &Scene::pieceMoved);
0039     }
0040 }
0041 
0042 void Palapeli::Scene::dispatchPieces(const QList<Palapeli::Piece*> &pieces)
0043 {
0044     for (Palapeli::Piece * piece : pieces) {
0045         piece->setSelected(false);
0046         removeItem(piece);
0047         m_pieces.removeAll(piece);
0048         disconnect(piece, &Piece::moved, this, &Scene::pieceMoved);
0049     }
0050 }
0051 
0052 void Palapeli::Scene::clearPieces()
0053 {
0054     qCDebug(PALAPELI_LOG) << "Palapeli::Scene Delete" << m_pieces.count() << "pieces in  m_pieces list.";
0055     qDeleteAll(m_pieces);
0056     qCDebug(PALAPELI_LOG) << "Palapeli::Scene Clear m_pieces list.";
0057     m_pieces.clear();
0058     qCDebug(PALAPELI_LOG) << "Palapeli::Scene Stop m_constraintVisualizer.";
0059     m_constraintVisualizer->stop();
0060 }
0061 
0062 void Palapeli::Scene::addMargin(const qreal handleWidth, const qreal spacer) {
0063     m_handleWidth = handleWidth;
0064     m_margin = handleWidth + spacer;
0065     QRectF r = piecesBoundingRect();
0066     r.adjust(-m_margin, -m_margin, m_margin, m_margin);
0067     setSceneRect(r);
0068     m_constraintVisualizer->stop();
0069     m_constraintVisualizer->start(r, handleWidth);
0070     views()[0]->fitInView(r, Qt::KeepAspectRatio);
0071     qCDebug(PALAPELI_LOG) << "SCENE RECT" << r << "VIEW SIZE" << views()[0]->size();
0072 }
0073 
0074 QRectF Palapeli::Scene::extPiecesBoundingRect() const
0075 {
0076     // Bounding rectangle of pieces plus constraint visualizer (margin).
0077     QRectF result = piecesBoundingRect();
0078     result.adjust(-m_margin, -m_margin, m_margin, m_margin);
0079     return result;
0080 }
0081 
0082 void Palapeli::Scene::setMinGrid(const int minGrid)
0083 {
0084     m_minGrid = minGrid;
0085 }
0086 
0087 QRectF Palapeli::Scene::piecesBoundingRect() const
0088 {
0089     // If no pieces, space is >= m_minGrid*m_minGrid pieces (e.g. for a new
0090     // PieceHolder). Default is >= 1 piece for the puzzle table.
0091     QRectF result;
0092     for (Palapeli::Piece* piece : m_pieces)
0093         result |= piece->sceneBareBoundingRect();
0094     QSizeF minSize = m_minGrid * m_gridSpacing;
0095     QRectF minRect(QPointF(0.0, 0.0), minSize);
0096         if (!m_pieces.isEmpty()) {
0097         // Center the minRect over the piece(s).
0098         minRect.moveTopLeft(result.center() - minRect.center());
0099     }
0100     return (result | minRect);
0101 }
0102 
0103 bool Palapeli::Scene::isConstrained() const
0104 {
0105     return m_constrained;
0106 }
0107 
0108 void Palapeli::Scene::setConstrained(bool constrained)
0109 {
0110     if (m_constrained == constrained)
0111         return;
0112     m_constrained = constrained;
0113     m_constraintVisualizer->setActive(constrained);
0114     Q_EMIT constrainedChanged(constrained);
0115 }
0116 
0117 void Palapeli::Scene::validatePiecePosition(Palapeli::Piece* piece)
0118 {
0119     // Get current scene rectangle.
0120     const QRectF sr = sceneRect();
0121     // Get bounding rectangle of all pieces and add margin.
0122     QRectF br = piece->sceneBareBoundingRect(); //br = bounding rect
0123     br.adjust(-m_margin, -m_margin, m_margin, m_margin);
0124     if (sr.contains(br))
0125         return;
0126 
0127     // Check for constraint (ie. pieces must not "push" puzzle table edges).
0128     if (m_constrained) {
0129         // Constraint active -> make sure piece stays inside scene rect.
0130         QPointF pos = piece->pos();
0131         if (br.left() < sr.left())
0132             pos.rx() += sr.left() - br.left();
0133         if (br.right() > sr.right())
0134             pos.rx() += sr.right() - br.right();
0135         if (br.top() < sr.top())
0136             pos.ry() += sr.top() - br.top();
0137         if (br.bottom() > sr.bottom())
0138             pos.ry() += sr.bottom() - br.bottom();
0139         piece->setPos(pos);
0140     }
0141     else {
0142         // Constraint not active -> enlarge scene rect as necessary.
0143         setSceneRect(sr | br);
0144     }
0145 }
0146 
0147 void Palapeli::Scene::mergeLoadedPieces()
0148 {
0149     // After loading, merge previously assembled pieces, with no animation.
0150     // We need to check all the loaded atomic pieces in each scene.
0151     searchConnections(m_pieces, false);
0152 }
0153 
0154 void Palapeli::Scene::searchConnections(const QList<Palapeli::Piece*>& pieces,
0155                     const bool animatedMerging)
0156 {
0157     // Look for pieces that can be joined after moving or loading.
0158     // If any are found, merge them, with or without animation.
0159     QList<Palapeli::Piece*> uncheckedPieces(pieces);
0160     while (!uncheckedPieces.isEmpty())
0161     {
0162         Palapeli::Piece* piece = uncheckedPieces.takeFirst();
0163         const QList<Palapeli::Piece*> pieceGroup =
0164             Palapeli::MergeGroup::tryGrowMergeGroup(piece);
0165         for (Palapeli::Piece* checkedPiece : pieceGroup)
0166             uncheckedPieces.removeAll(checkedPiece);
0167         if (pieceGroup.size() > 1)
0168         {
0169             Palapeli::MergeGroup* mergeGroup =
0170                 new Palapeli::MergeGroup(pieceGroup, this,
0171                     m_pieceAreaSize, animatedMerging);
0172             connect(mergeGroup, &Palapeli::MergeGroup::pieceInstanceTransaction, this, &Scene::pieceInstanceTransaction);
0173             mergeGroup->start();
0174         }
0175     }
0176 }
0177 
0178 void Palapeli::Scene::pieceInstanceTransaction(const QList<Palapeli::Piece*>& deletedPieces, const QList<Palapeli::Piece*>& createdPieces)
0179 {
0180     // qCDebug(PALAPELI_LOG) << "Scene::pieceInstanceTransaction(delete" << deletedPieces.count() << "add" << createdPieces.count();
0181     const int oldPieceCount = m_pieces.count();
0182     dispatchPieces(deletedPieces);
0183     for (Palapeli::Piece* newPiece : createdPieces)
0184     {
0185         addPieceToList (newPiece);
0186         connect(newPiece, &Piece::moved,
0187             this, &Scene::pieceMoved);
0188     }
0189     // qCDebug(PALAPELI_LOG) << "emit saveMove(" << oldPieceCount - m_pieces.count();
0190     Q_EMIT saveMove(oldPieceCount - m_pieces.count());
0191 }
0192 
0193 void Palapeli::Scene::pieceMoved(bool finished)
0194 {
0195     if (!finished) {
0196         Q_EMIT saveMove(0);
0197         return;
0198     }
0199     // int before = m_pieces.count();
0200     QList<Palapeli::Piece*> mergeCandidates;
0201     const auto selectedItems = this->selectedItems();
0202     for (QGraphicsItem* item : selectedItems)
0203     {
0204         Palapeli::Piece* piece = Palapeli::Piece::fromSelectedItem(item);
0205         if (piece)
0206             mergeCandidates << piece;
0207     }
0208     searchConnections(mergeCandidates, true);   // With animation.
0209 }
0210 
0211 void Palapeli::Scene::initializeGrid(const QPointF& gridTopLeft)
0212 {
0213     m_gridTopLeft = gridTopLeft;
0214     m_gridSpacing = pieceAreaSize()*(1.0 + 0.05 * Settings::pieceSpacing());
0215     m_gridRank = 1;
0216     m_gridX = 0;
0217     m_gridY = 0;
0218     // qCDebug(PALAPELI_LOG) << "GRID INITIALIZED" << m_gridTopLeft
0219     //   << "spacing" << m_gridSpacing << "scene size" << sceneRect();
0220 }
0221 
0222 void Palapeli::Scene::addToGrid(Palapeli::Piece* piece)
0223 {
0224     // qCDebug(PALAPELI_LOG) << "ADD TO GRID AT" << m_gridTopLeft
0225     //   << QPoint(m_gridX, m_gridY)
0226     //   << "spacing" << m_gridSpacing << "random" << false;
0227     piece->setPlace(m_gridTopLeft, m_gridX, m_gridY, m_gridSpacing, false);
0228     // Calculate the next spot on the square grid.
0229     if (m_gridY == (m_gridRank - 1)) {
0230         m_gridX++;      // Add to bottom row.
0231         if (m_gridX > (m_gridRank - 1)) {
0232             m_gridRank++;   // Expand the square grid.
0233             m_gridX = m_gridRank - 1;
0234             m_gridY = 0;    // Start right-hand column.
0235         }
0236     }
0237     else {
0238         m_gridY++;      // Add to right-hand column.
0239         if (m_gridY == (m_gridRank - 1)) {
0240             m_gridX = 0;    // Start bottom row.
0241         }
0242     }
0243 }
0244 
0245 #include "moc_scene.cpp"