diff --git a/src/engine/gameplay.cpp b/src/engine/gameplay.cpp index 82a130f..e4af88e 100644 --- a/src/engine/gameplay.cpp +++ b/src/engine/gameplay.cpp @@ -1,1308 +1,1314 @@ /*************************************************************************** * Copyright 2009 Stefan Majewsky * Copyright 2014 Ian Wadham * * 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 "gameplay.h" #include "../file-io/collection-view.h" #include "../window/puzzletablewidget.h" #include "../window/pieceholder.h" #include "puzzlepreview.h" #include "scene.h" #include "view.h" #include "piece.h" #include "texturehelper.h" #include "../file-io/puzzle.h" #include "../file-io/components.h" #include "../file-io/collection.h" #include "../creator/puzzlecreator.h" #include "../config/configdialog.h" #include "settings.h" -#include +#include #include #include #include #include #include +#include #include #include +#include #include #include #include -#include -#include // Use this because comma in type is not possible in foreach macro. typedef QPair DoubleIntPair; //TODO: move LoadingWidget into here (stack into m_centralWidget) const int Palapeli::GamePlay::LargePuzzle = 300; const QString HeaderSaveGroup ("-PalapeliSavedPuzzle"); const QString HolderSaveGroup ("Holders"); const QString LocationSaveGroup ("XYCo-ordinates"); const QString FormerSaveGroup ("SaveGame"); const QString AppearanceSaveGroup("Appearance"); const QString PreviewSaveGroup ("PuzzlePreview"); Palapeli::GamePlay::GamePlay(MainWindow* mainWindow) : QObject(mainWindow) , m_centralWidget(new QStackedWidget) , m_collectionView(new Palapeli::CollectionView) , m_puzzleTable(new Palapeli::PuzzleTableWidget) , m_puzzlePreview(0) , m_mainWindow(mainWindow) , m_puzzle(0) , m_pieceAreaSize(QSizeF(32.0, 32.0)) // Allow 1024 pixels initially. , m_savegameTimer(new QTimer(this)) , m_currentHolder(0) , m_previousHolder(0) , m_loadingPuzzle(false) , m_restoredGame(false) , m_originalPieceCount(0) , m_currentPieceCount(0) , m_sizeFactor(1.3) , m_playing(false) , m_canDeletePuzzle(false) // No puzzle selected at startup. , m_canExportPuzzle(false) { m_puzzleTableScene = m_puzzleTable->view()->scene(); m_viewList << m_puzzleTable->view(); m_savegameTimer->setInterval(500); //write savegame twice per second at most m_savegameTimer->setSingleShot(true); connect(m_savegameTimer, SIGNAL(timeout()), this, SLOT(updateSavedGame())); connect(this, SIGNAL(reportProgress(int,int)), m_puzzleTable, SLOT(reportProgress(int,int))); connect(this, SIGNAL(victoryAnimationFinished()), m_puzzleTable->view(), SLOT(startVictoryAnimation())); connect(m_puzzleTable->view(), SIGNAL(teleport(Piece*,const QPointF&,View*)), this, SLOT(teleport(Piece*,const QPointF&,View*))); } Palapeli::GamePlay::~GamePlay() { deletePuzzleViews(); delete m_puzzlePreview; } void Palapeli::GamePlay::deletePuzzleViews() { qDebug() << "ENTERED GamePlay::deletePuzzleViews() ..."; while (! m_viewList.isEmpty()) { Palapeli::View* view = m_viewList.takeLast(); Palapeli::Scene* scene = view->scene(); qDebug() << "DISCONNECT SLOT(positionChanged(int))"; disconnect(scene, SIGNAL(saveMove(int)), this, SLOT(positionChanged(int))); qDebug() << "scene->clearPieces();"; scene->clearPieces(); qDebug() << "if (scene != m_puzzleTableScene) {"; if (scene != m_puzzleTableScene) { qDebug() << "DELETING holder" << view->windowTitle(); delete view; } } m_currentHolder = 0; m_previousHolder = 0; } void Palapeli::GamePlay::init() { // Set up the collection view. m_collectionView->setModel(Palapeli::Collection::instance()); connect(m_collectionView, SIGNAL(playRequest(Palapeli::Puzzle*)), SLOT(playPuzzle(Palapeli::Puzzle*))); // Set up the puzzle table. m_puzzleTable->showStatusBar(Settings::showStatusBar()); // Set up the central widget. m_centralWidget->addWidget(m_collectionView); m_centralWidget->addWidget(m_puzzleTable); m_centralWidget->setCurrentWidget(m_collectionView); m_mainWindow->setCentralWidget(m_centralWidget); // Get some current action states from the collection. m_canDeletePuzzle = m_mainWindow->actionCollection()-> action("game_delete")->isEnabled(); m_canExportPuzzle = m_mainWindow->actionCollection()-> action("game_export")->isEnabled(); // Enable collection actions and disable playing actions initially. setPalapeliMode(false); } void Palapeli::GamePlay::shutdown() { qDebug() << "ENTERED Palapeli::GamePlay::shutdown()"; // Make sure the last change is saved. if (m_savegameTimer->isActive()) { m_savegameTimer->stop(); updateSavedGame(); } // Delete piece-holders cleanly: no closeEvents in PieceHolder objects // and no messages about holders not being empty. deletePuzzleViews(); } //BEGIN action handlers void Palapeli::GamePlay::playPuzzle(Palapeli::Puzzle* puzzle) { t.start(); // IDW test. START the clock. qDebug() << "START playPuzzle(): elapsed 0"; // Get some current action states from the collection. m_canDeletePuzzle = m_mainWindow->actionCollection()-> action("game_delete")->isEnabled(); m_canExportPuzzle = m_mainWindow->actionCollection()-> action("game_export")->isEnabled(); m_centralWidget->setCurrentWidget(m_puzzleTable); m_puzzlePreview = new Palapeli::PuzzlePreview(m_mainWindow); if (m_loadingPuzzle || (!puzzle) || (m_puzzle == puzzle)) { if (m_puzzle == puzzle) { qDebug() << "RESUMING A PUZZLE."; // IDW TODO - Show piece-holders. // Check if puzzle has been completed. if (m_currentPieceCount == 1) { int result = KMessageBox::questionYesNo( m_mainWindow, i18n("You have finished the puzzle. Do you want to restart it now?")); if (result == KMessageBox::Yes) { restartPuzzle(); return; } } // True if same puzzle selected and not still loading. setPalapeliMode(! m_loadingPuzzle); } qDebug() << "NO LOAD: (m_puzzle == puzzle)" << (m_puzzle == puzzle); qDebug() << "m_loadingPuzzle" << m_loadingPuzzle << (puzzle ? "puzzle != 0" : "puzzle == 0"); return; // Already loaded, loading or failed to start. } m_puzzle = puzzle; qDebug() << "RESTART the clock: elapsed" << t.restart(); // IDW test. loadPuzzle(); qDebug() << "Returned from loadPuzzle(): elapsed" << t.elapsed(); // IDW TODO - There is no way to stop loading a puzzle and start loading // another. The only option is to Quit or abort Palapeli. QTimer::singleShot(0, this, SLOT(loadPreview())); } void Palapeli::GamePlay::loadPreview() { // IDW TODO - This WAS delaying the showing of the LoadingWidget. Now // it is preventing the balls from moving for a few seconds. // Get metadata from archive (tar), to be sure of getting image data. // The config/palapeli-collectionrc file lacks image metadata (because // Palapeli must load the collection-list quickly at startup time). const Palapeli::PuzzleComponent* as = m_puzzle->get(Palapeli::PuzzleComponent::ArchiveStorage); const Palapeli::PuzzleComponent* cmd = (as == 0) ? 0 : as->cast(Palapeli::PuzzleComponent::Metadata); if (cmd) { // Load puzzle preview image from metadata. const Palapeli::PuzzleMetadata md = dynamic_cast(cmd)-> metadata; m_puzzlePreview->loadImageFrom(md); m_mainWindow->setCaption(md.name); // Set main title. } m_puzzlePreview->setVisible(Settings::puzzlePreviewVisible()); connect (m_puzzlePreview, SIGNAL(closing()), SLOT(actionTogglePreview())); // Hide preview: do not delete. } void Palapeli::GamePlay::playPuzzleFile(const QString& path) { const QString id = Palapeli::Puzzle::fsIdentifier(path); playPuzzle(new Palapeli::Puzzle(new Palapeli::ArchiveStorageComponent, path, id)); } void Palapeli::GamePlay::actionGoCollection() { m_centralWidget->setCurrentWidget(m_collectionView); delete m_puzzlePreview; m_puzzlePreview = 0; m_mainWindow->setCaption(QString()); // IDW TODO - Disable piece-holder actions. foreach (Palapeli::View* view, m_viewList) { if (view != m_puzzleTable->view()) { view->hide(); } } // Disable playing actions and enable collection actions. setPalapeliMode(false); } void Palapeli::GamePlay::actionTogglePreview() { // This action is OK during puzzle loading. if (m_puzzlePreview) { m_puzzlePreview->toggleVisible(); m_mainWindow->actionCollection()->action("view_preview")-> setChecked(Settings::puzzlePreviewVisible()); } } void Palapeli::GamePlay::actionCreate() { QPointer creatorDialog(new Palapeli::PuzzleCreatorDialog); if (creatorDialog->exec()) { if (!creatorDialog) return; Palapeli::Puzzle* puzzle = creatorDialog->result(); if (!puzzle) { delete creatorDialog; return; } Palapeli::Collection::instance()->importPuzzle(puzzle); playPuzzle(puzzle); } delete creatorDialog; } void Palapeli::GamePlay::actionDelete() { QModelIndexList indexes = m_collectionView->selectedIndexes(); //ask user for confirmation QStringList puzzleNames; foreach (const QModelIndex& index, indexes) puzzleNames << index.data(Qt::DisplayRole).toString(); const int result = KMessageBox::warningContinueCancelList(m_mainWindow, i18n("The following puzzles will be deleted. This action cannot be undone."), puzzleNames); if (result != KMessageBox::Continue) return; //do deletion Palapeli::Collection* coll = Palapeli::Collection::instance(); // We cannot simply use a foreach here, because after deleting the first // puzzle, the rest of the indexes should no longer be used (model was // modified). Ask again for the list of selected indexes after each // step instead. while (indexes.size() > 0) { coll->deletePuzzle(indexes.at(0)); indexes = m_collectionView->selectedIndexes(); } } void Palapeli::GamePlay::actionImport() { - const QString filter = i18nc("Filter for a file dialog", "*.puzzle|Palapeli puzzles (*.puzzle)"); - const QStringList paths = KFileDialog::getOpenFileNames(KUrl("kfiledialog:///palapeli-import"), filter); + const QString filter = i18nc("Filter for a file dialog", "Palapeli puzzles (*.puzzle)"); + const QStringList paths = QFileDialog::getOpenFileNames(m_mainWindow, + i18n("Import Palapeli puzzles"), + QString(), + filter); Palapeli::Collection* coll = Palapeli::Collection::instance(); foreach (const QString& path, paths) coll->importPuzzle(path); } void Palapeli::GamePlay::actionExport() { QModelIndexList indexes = m_collectionView->selectedIndexes(); Palapeli::Collection* coll = Palapeli::Collection::instance(); foreach (const QModelIndex& index, indexes) { Palapeli::Puzzle* puzzle = coll->puzzleFromIndex(index); if (!puzzle) continue; //get puzzle name (as an initial guess for the file name) puzzle->get(Palapeli::PuzzleComponent::Metadata).waitForFinished(); const Palapeli::MetadataComponent* cmp = puzzle->component(); if (!cmp) continue; //ask user for target file name - const QString startLoc = QString::fromLatin1("kfiledialog:///palapeli-export/%1.puzzle").arg(cmp->metadata.name); - const QString filter = i18nc("Filter for a file dialog", "*.puzzle|Palapeli puzzles (*.puzzle)"); - const QString location = KFileDialog::getSaveFileName(KUrl(startLoc), filter); + const QString startLoc = QString::fromLatin1("%1.puzzle").arg(cmp->metadata.name); + const QString filter = i18nc("Filter for a file dialog", "Palapeli puzzles (*.puzzle)"); + const QString location = QFileDialog::getSaveFileName(m_mainWindow, + i18n("Save Palapeli puzzles"), + startLoc, + filter); if (location.isEmpty()) continue; //process aborted by user //do export coll->exportPuzzle(index, location); } } void Palapeli::GamePlay::createHolder() { qDebug() << "GamePlay::createHolder() entered"; bool OK; QString name = QInputDialog::getText(m_mainWindow, i18n("Create a piece holder"), i18n("Enter a short name (optional):"), QLineEdit::Normal, QString(""), &OK); if (! OK) { return; // If CANCELLED, do not create a piece holder. } createHolder(name); // Merges/moves in new holders add to the progress bar and are saved. Palapeli::View* view = m_viewList.last(); view->setCloseUp(true); // New holders start in close-up scale. connect(view->scene(), SIGNAL(saveMove(int)), this, SLOT(positionChanged(int))); connect(view, SIGNAL(teleport(Piece*,const QPointF&,View*)), this, SLOT(teleport(Piece*,const QPointF&,View*))); connect(view, SIGNAL(newPieceSelectionSeen(View*)), this, SLOT(handleNewPieceSelection(View*))); } void Palapeli::GamePlay::createHolder(const QString& name, bool sel) { Palapeli::PieceHolder* h = new Palapeli::PieceHolder(m_mainWindow, m_pieceAreaSize, name); m_viewList << h; h->initializeZooming(); // Min. view 2x2 to 6x6 pieces. connect(h, SIGNAL(selected(PieceHolder*)), this, SLOT(changeSelectedHolder(PieceHolder*))); connect (h, SIGNAL(closing(PieceHolder*)), SLOT(closeHolder(PieceHolder*))); if (sel) { changeSelectedHolder(h); } else { h->setSelected(false); } m_puzzleTable->view()->setFocus(Qt::OtherFocusReason); m_puzzleTable->activateWindow(); // Return focus to main window. positionChanged(0); // Save holder - a little later. } void Palapeli::GamePlay::deleteHolder() { qDebug() << "GamePlay::deleteHolder() entered"; if (m_currentHolder) { closeHolder(m_currentHolder); } else { KMessageBox::information(m_mainWindow, i18n("You need to click on a piece holder to " "select it before you can delete it, or " "you can just click on its Close button.")); } } void Palapeli::GamePlay::closeHolder(Palapeli::PieceHolder* h) { if (h->scene()->pieces().isEmpty()) { int count = m_viewList.count(); m_viewList.removeOne(h); qDebug() << "m_viewList WAS" << count << "NOW" << m_viewList.count(); m_currentHolder = 0; m_previousHolder = 0; h->deleteLater(); positionChanged(0); // Save change - a little later. } else { KMessageBox::information(m_mainWindow, i18n("The selected piece holder must be empty " "before you can delete it.")); } } void Palapeli::GamePlay::selectAll() { qDebug() << "GamePlay::selectAll() entered"; if (m_currentHolder) { QList pieces = m_currentHolder->scene()->pieces(); if (! pieces.isEmpty()) { foreach (Palapeli::Piece* piece, pieces) { piece->setSelected(true); } handleNewPieceSelection(m_currentHolder); } else { KMessageBox::information(m_mainWindow, i18n("The selected piece holder must contain " "some pieces for 'Select all' to use.")); } } else { KMessageBox::information(m_mainWindow, i18n("You need to click on a piece holder to " "select it before you can select all the " "pieces in it.")); } } void Palapeli::GamePlay::rearrangePieces() { qDebug() << "GamePlay::rearrangePieces() entered"; QList selectedPieces; Palapeli::View* view = m_puzzleTable->view(); selectedPieces = getSelectedPieces(view); if (selectedPieces.isEmpty()) { if (m_currentHolder) { view = m_currentHolder; selectedPieces = getSelectedPieces(view); } } if (selectedPieces.isEmpty()) { KMessageBox::information(m_mainWindow, i18n("To rearrange pieces, either the puzzle table " "must have some selected pieces or there " "must be a selected holder with some selected " "pieces in it.")); return; } QRectF bRect; foreach (Palapeli::Piece* piece, selectedPieces) { bRect |= piece->sceneBareBoundingRect(); } Palapeli::Scene* scene = view->scene(); // If in a piece-holder and ALL pieces are selected, start at (0, 0). scene->initializeGrid(((view == m_currentHolder) && (selectedPieces.count() == scene->pieces().count())) ? QPointF(0.0, 0.0) : bRect.topLeft()); foreach (Palapeli::Piece* piece, selectedPieces) { scene->addToGrid(piece); } if (view == m_currentHolder) { // Adjust the piece-holder's scene to frame the pieces. scene->setSceneRect(scene->extPiecesBoundingRect()); } positionChanged(0); // There is no attempt to merge pieces here. } void Palapeli::GamePlay::actionZoomIn() { // IDW TODO - Make ZoomIn work for whichever view is active. m_puzzleTable->view()->zoomIn(); } void Palapeli::GamePlay::actionZoomOut() { // IDW TODO - Make ZoomOut work for whichever view is active. m_puzzleTable->view()->zoomOut(); } void Palapeli::GamePlay::restartPuzzle() { if (!m_puzzle) { return; // If no puzzle was successfully loaded and started. } // Discard the *.save file. static const QString pathTemplate = QString::fromLatin1("collection/%1.save"); - QFile(KStandardDirs::locateLocal("appdata", + QFile(QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, pathTemplate.arg(m_puzzle->identifier()))).remove(); // Load the puzzle and re-shuffle the pieces. loadPuzzle(); } void Palapeli::GamePlay::teleport(Palapeli::Piece* pieceUnderMouse, const QPointF& scenePos, Palapeli::View* view) { qDebug() << "GamePlay::teleport: pieceUnder" << (pieceUnderMouse != 0) << "scPos" << scenePos << "PuzzleTable?" << (view == m_puzzleTable->view()) << "CurrentHolder?" << (view == m_currentHolder); if (! m_currentHolder) { KMessageBox::information(m_mainWindow, i18n("You need to have a piece holder and click it to " "select it before you can transfer pieces into or " "out of it.")); return; } bool puzzleTableClick = (view == m_puzzleTable->view()); QList selectedPieces; if (puzzleTableClick) { if (pieceUnderMouse && (!pieceUnderMouse->isSelected())) { pieceUnderMouse->setSelected(true); } selectedPieces = getSelectedPieces(view); if (selectedPieces.count() > 0) { // Transfer from the puzzle table to a piece-holder. foreach (Palapeli::Piece* piece, selectedPieces) { if (piece->representedAtomicPieces().count() > 6) { int ans = 0; ans = KMessageBox::questionYesNo ( m_mainWindow, i18n("You have selected to " "transfer a large piece " "containing more than six " "small pieces to a holder. Do " "you really wish to do that?")); if (ans == KMessageBox::No) { return; } } } transferPieces(selectedPieces, view, m_currentHolder); } else { selectedPieces = getSelectedPieces(m_currentHolder); qDebug() << "Transfer from holder" << selectedPieces.count() << m_currentHolder->name(); // Transfer from a piece-holder to the puzzle table. if (selectedPieces.count() > 0) { transferPieces(selectedPieces, m_currentHolder, view, scenePos); } else { KMessageBox::information(m_mainWindow, i18n("You need to select one or more " "pieces to be transferred out of " "the selected holder or select " "pieces from the puzzle table " "to be transferred into it.")); } } } else { if (m_previousHolder) { selectedPieces = getSelectedPieces(m_previousHolder); // Transfer from one piece-holder to another. if (selectedPieces.count() > 0) { transferPieces(selectedPieces, m_previousHolder, view, scenePos); } else { KMessageBox::information(m_mainWindow, i18n("You need to select one or more " "pieces to be transferred from " "the previous holder into the " "newly selected holder.")); } } else { KMessageBox::information(m_mainWindow, i18n("You need to have at least two holders, " "one of them selected and with selected " "pieces inside it, before you can " "transfer pieces to a second holder.")); } } positionChanged(0); // Save the transfer - a little later. } void Palapeli::GamePlay::handleNewPieceSelection(Palapeli::View* view) { // De-select pieces on puzzle table, to prevent teleport bounce-back. Palapeli::View* m_puzzleTableView = m_puzzleTable->view(); if (view != m_puzzleTableView) { // Pieces selected in a holder. foreach (Palapeli::Piece* piece, getSelectedPieces(m_puzzleTableView)) { piece->setSelected(false); } } } void Palapeli::GamePlay::transferPieces(const QList pieces, Palapeli::View* source, Palapeli::View* dest, const QPointF& scenePos) { qDebug() << "ENTERED GamePlay::transferPieces(): pieces" << pieces.count() << "SourceIsTable" << (source == m_puzzleTable->view()) << "DestIsTable" << (dest == m_puzzleTable->view()) << "scenePos" << scenePos; source->scene()->dispatchPieces(pieces); if ((source != m_puzzleTable->view()) && // If empty holder. (source->scene()->pieces().isEmpty())) { source->scene()->initializeGrid(QPointF(0.0, 0.0)); } bool destIsPuzzleTable = (dest == m_puzzleTable->view()); if (destIsPuzzleTable) { m_puzzleTableScene->initializeGrid(scenePos); } Palapeli::Scene* scene = dest->scene(); foreach (Palapeli::Piece* piece, scene->pieces()) { // Clear all previous selections in the destination scene. if (piece->isSelected()) { piece->setSelected(false); } } foreach (Palapeli::Piece* piece, pieces) { // Leave the new arrivals selected, connected and in a grid. scene->addPieceToList(piece); scene->addItem(piece); scene->addToGrid(piece); piece->setSelected(true); connect(piece, SIGNAL(moved(bool)), scene, SLOT(pieceMoved(bool))); } scene->setSceneRect(scene->extPiecesBoundingRect()); if (! destIsPuzzleTable) { dest->centerOn(pieces.last()->sceneBareBoundingRect().center()); } } void Palapeli::GamePlay::setPalapeliMode(bool playing) { // Palapeli has three modes: playing, loading and managing a collection. // When playing, collection actions are disabled and playing actions are // enabled: vice versa when managing the collection. When loading a // puzzle, both sets of actions are disabled, because they cannot work // concurrently with loading (enPlaying and enCollection both false). const char* playingActions[] = {"view_collection", "game_restart", "view_preview", "move_create_holder", "move_delete_holder", "move_select_all", "move_rearrange", "view_zoom_in", "view_zoom_out", "END" }; const char* collectionActions[] = {"game_new", "game_delete", "game_import", "game_export", "END" }; bool enPlaying = (! m_loadingPuzzle) && playing; bool enCollection = (! m_loadingPuzzle) && (! playing); for (uint i = 0; (strcmp (playingActions[i], "END") != 0); i++) { m_mainWindow->actionCollection()-> action(playingActions[i])->setEnabled(enPlaying); } for (uint i = 0; (strcmp (collectionActions[i], "END") != 0); i++) { m_mainWindow->actionCollection()-> action(collectionActions[i])->setEnabled(enCollection); } // The collection view may enable or disable Delete and Export actions, // depending on what puzzle, if any, is currently selected. if (enCollection) { m_mainWindow->actionCollection()-> action("game_delete")->setEnabled(m_canDeletePuzzle); m_mainWindow->actionCollection()-> action("game_export")->setEnabled(m_canExportPuzzle); } m_playing = playing; } QList Palapeli::GamePlay::getSelectedPieces(Palapeli::View* v) { qDebug() << "ENTERED GamePlay::getSelectedPieces(): PuzzleTable" << (v == m_puzzleTable->view()); const QList sel = v->scene()->selectedItems(); QList pieces; foreach (QGraphicsItem* item, sel) { Palapeli::Piece* p = Palapeli::Piece::fromSelectedItem(item); if (p) { pieces << p; } } return pieces; } void Palapeli::GamePlay::configure() { if (Palapeli::ConfigDialog().exec() == QDialog::Accepted) { if (m_playing) { qDebug() << "SAVING SETTINGS FOR THIS PUZZLE"; updateSavedGame(); // Save current puzzle Settings. } } } //END action handlers void Palapeli::GamePlay::loadPuzzle() { qDebug() << "START loadPuzzle()"; m_restoredGame = false; // Disable all collection and playing actions during loading. m_loadingPuzzle = true; setPalapeliMode(false); // Stop autosaving and progress-reporting and start the loading-widget. m_savegameTimer->stop(); // Just in case it is running ... emit reportProgress(0, 0); // Is there a saved game? static const QString pathTemplate = QString::fromLatin1("collection/%1.save"); - KConfig savedConfig(KStandardDirs::locateLocal("appdata", + KConfig savedConfig(QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, pathTemplate.arg(m_puzzle->identifier()))); if (savedConfig.hasGroup(AppearanceSaveGroup)) { // Get settings for background, shadows, etc. in this puzzle. restorePuzzleSettings(&savedConfig); } // Return to the event queue to start the loading-widget graphics ASAP. QTimer::singleShot(0, this, SLOT(loadPuzzleFile())); qDebug() << "END loadPuzzle()"; } void Palapeli::GamePlay::loadPuzzleFile() { // Clear all scenes, and delete any piece holders that exist. qDebug() << "Start clearing all scenes: elapsed" << t.elapsed(); deletePuzzleViews(); m_viewList << m_puzzleTable->view(); // Re-list the puzzle-table. qDebug() << "Finish clearing all scenes: elapsed" << t.elapsed(); qDebug() << "Start loadPuzzleFile(): elapsed" << t.restart(); // Begin loading the puzzle. // It is loaded asynchronously and processed one piece at a time. m_loadedPieces.clear(); if (m_puzzle) { Palapeli::FutureWatcher* watcher = new Palapeli::FutureWatcher; connect(watcher, SIGNAL(finished()), SLOT(loadNextPiece())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); watcher->setFuture( m_puzzle->get(Palapeli::PuzzleComponent::Contents)); } qDebug() << "Finish loadPuzzleFile(): time" << t.restart(); } void Palapeli::GamePlay::loadNextPiece() { if (!m_puzzle) return; const Palapeli::ContentsComponent* component = m_puzzle->component(); if (!component) return; // Add pieces, but only one at a time. // PuzzleContents structure is defined in src/file-io/puzzlestructs.h. // We iterate over contents.pieces: key = pieceID, value = QImage. const Palapeli::PuzzleContents contents = component->contents; QMap::const_iterator iterPieces = contents.pieces.begin(); const QMap::const_iterator iterPiecesEnd = contents.pieces.end(); for (int pieceID = iterPieces.key(); iterPieces != iterPiecesEnd; pieceID = (++iterPieces).key()) { if (m_loadedPieces.contains(pieceID)) continue; // Already loaded. // Create a Palapeli::Piece from its image, offsets and ID. // This also adds bevels, if required. Palapeli::Piece* piece = new Palapeli::Piece( iterPieces.value(), contents.pieceOffsets[pieceID]); piece->addRepresentedAtomicPieces(QList() << pieceID); piece->addAtomicSize(iterPieces.value().size()); // IDW test. qDebug() << "PIECE" << pieceID // << "offset" << contents.pieceOffsets[pieceID] // << "size" << iterPieces.value().size(); m_loadedPieces[pieceID] = piece; piece->completeVisuals(); // Add a shadow, if required. // Continue with next piece or next stage, after event loop run. if (contents.pieces.size() > m_loadedPieces.size()) QTimer::singleShot(0, this, SLOT(loadNextPiece())); else QTimer::singleShot(0, this, SLOT(loadPiecePositions())); return; } } void Palapeli::GamePlay::loadPiecePositions() { qDebug() << "Finish loadNextPiece() calls: time" << t.restart(); if (!m_puzzle) return; qDebug() << "loadPiecePositions():"; m_originalPieceCount = m_loadedPieces.count(); const Palapeli::PuzzleContents contents = m_puzzle->component()->contents; //add piece relations foreach (const DoubleIntPair& relation, contents.relations) { Palapeli::Piece* firstPiece = m_loadedPieces.value(relation.first, 0); Palapeli::Piece* secondPiece = m_loadedPieces.value(relation.second, 0); firstPiece->addLogicalNeighbors(QList() << secondPiece); secondPiece->addLogicalNeighbors(QList() << firstPiece); } calculatePieceAreaSize(); m_puzzleTableScene->setPieceAreaSize(m_pieceAreaSize); // Is there a saved game? static const QString pathTemplate = QString::fromLatin1("collection/%1.save"); - KConfig savedConfig(KStandardDirs::locateLocal("appdata", + KConfig savedConfig(QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, pathTemplate.arg(m_puzzle->identifier()))); bool oldFormat = false; m_restoredGame = false; int nHolders = 0; if (savedConfig.hasGroup(HeaderSaveGroup)) { KConfigGroup headerGroup(&savedConfig, HeaderSaveGroup); nHolders = headerGroup.readEntry("N_Holders", 0); m_restoredGame = true; } else if (savedConfig.hasGroup(FormerSaveGroup)) { m_restoredGame = true; oldFormat = true; } if (m_restoredGame) { // IDW TODO - Enable piece-holder actions. // Read piece positions from the LocationSaveGroup. // The current positions of atomic pieces are listed. If // neighbouring pieces are joined, their position values are // identical and searchConnections(m_pieces) handles that by // calling on a MergeGroup object to join the pieces. qDebug() << "RESTORING SAVED PUZZLE."; KConfigGroup holderGroup (&savedConfig, HolderSaveGroup); KConfigGroup locationGroup (&savedConfig, oldFormat ? FormerSaveGroup : LocationSaveGroup); // Re-create the saved piece-holders, if any. m_currentHolder = 0; for (int groupID = 1; groupID <= nHolders; groupID++) { KConfigGroup holder (&savedConfig, QString("Holder_%1").arg(groupID)); // Re-create a piece-holder and add it to m_viewList. qDebug() << "RE-CREATE HOLDER" << QString("Holder_%1").arg(groupID) << "name" << holder.readEntry("Name", QString("")); createHolder(holder.readEntry("Name", QString("")), holder.readEntry("Selected", false)); // Restore the piece-holder's size and position. QRect r = holder.readEntry("Geometry", QRect()); qDebug() << "GEOMETRY" << r; Palapeli::View* v = m_viewList.at(groupID); v->resize(r.size()); int x = (r.left() < 0) ? 0 : r.left(); int y = (r.top() < 0) ? 0 : r.top(); v->move(x, y); } // Move pieces to saved positions, in holders or puzzle table. qDebug() << "START POSITIONING PIECES"; qDebug() << "Old format" << oldFormat << HolderSaveGroup << (oldFormat ? FormerSaveGroup : LocationSaveGroup); QMap::const_iterator i = m_loadedPieces.constBegin(); const QMap::const_iterator end = m_loadedPieces.constEnd(); for (int pieceID = i.key(); i != end; pieceID = (++i).key()) { Palapeli::Piece* piece = i.value(); const QString ID = QString::number(pieceID); const int group = oldFormat ? 0 : holderGroup.readEntry(ID, 0); const QPointF p = locationGroup.readEntry(ID, QPointF()); // qDebug() << "Piece ID" << ID << "group" << group << "pos" << p; Palapeli::View* view = m_viewList.at(group); // qDebug() << "View" << (view != 0) << "Scene" << (view->scene() != 0); view->scene()->addPieceToList(piece); // qDebug() << "PIECE HAS BEEN ADDED TO SCENE's LIST"; piece->setPos(p); // qDebug() << "PIECE HAS BEEN POSITIONED"; // IDW TODO - Selecting/unselecting did not trigger a // save. Needed to bring back a "dirty" flag. // IDW TODO - Same for all other saveable actions? } qDebug() << "FINISHED POSITIONING PIECES"; // Each scene re-merges pieces, as required, with no animation. foreach (Palapeli::View* view, m_viewList) { view->scene()->mergeLoadedPieces(); } } else { // Place pieces at nice positions. qDebug() << "GENERATING A NEW PUZZLE BY SHUFFLING."; // Step 1: determine maximum piece size. QSizeF pieceAreaSize = m_pieceAreaSize; m_sizeFactor = 1.0 + 0.05 * Settings::pieceSpacing(); qDebug() << "PIECE SPACING FACTOR" << m_sizeFactor; pieceAreaSize *= m_sizeFactor; // Allow more space for pieces. // Step 2: place pieces in a grid in random order. QList piecePool(m_loadedPieces.values()); int nPieces = piecePool.count(); Palapeli::ConfigDialog::SolutionSpace space = (nPieces < 20) ? Palapeli::ConfigDialog::None : (Palapeli::ConfigDialog::SolutionSpace) Settings::solutionArea(); // Find the size of the area required for the solution. QRectF r; foreach (Palapeli::Piece* piece, piecePool) { r |= piece->sceneBareBoundingRect(); } int xResv = 0; int yResv = 0; if (space != Palapeli::ConfigDialog::None) { xResv = r.width()/pieceAreaSize.width() + 1.0; yResv = r.height()/pieceAreaSize.height() + 1.0; } // To get "a" pieces around the solution, both horizontally and // vertically, we need to solve for "a" in: // (a+xResv) * (a+yResv) = piecePool.count() + xResv*yResv // or a^2 + (xResv+yResv)*a - piecePool.count() = 0 // Let q = qSqrt(((xResv+yResv)^2 + 4.piecePool.count())), then // a = (-xResv-yResv +- q)/2, the solution of the quadratic. // // The positive root is a = (-xResv - yResv + q)/2. If there is // no solution area, xResv == yResv == 0 and the above equation // degenerates to "a" = sqrt(number of pieces), as in earlier // versions of Palapeli. qreal q = qSqrt((xResv + yResv)*(xResv + yResv) + 4*nPieces); int a = qRound((-xResv-yResv+q)/2.0); int xMax = xResv + a; // Set solution space for None or TopLeft: modify as required. int x1 = 0; int y1 = 0; if (space == Palapeli::ConfigDialog::TopRight) { x1 = a; } else if (space == Palapeli::ConfigDialog::Center) { x1 = a/2; y1 = a/2; } else if (space == Palapeli::ConfigDialog::BottomLeft) { y1 = a; // If the rows are uneven, push the partial row right. if ((nPieces + xResv*yResv) % xMax) { yResv++; } } else if (space == Palapeli::ConfigDialog::BottomRight) { x1 = a; y1 = a; } int x2 = x1 + xResv; int y2 = y1 + yResv; qDebug() << "Reserve:" << xResv << yResv << "position" << space; qDebug() << "Pieces" << piecePool.count() << "rect" << r << "pieceAreaSize" << pieceAreaSize; qDebug() << "q" << q << "a" << a << "a/2" << a/2; qDebug() << "xMax" << xMax << "x1 y1" << x1 << y1 << "x2 y2" << x2 << y2; for (int y = 0; !piecePool.isEmpty(); ++y) { for (int x = 0; x < xMax && !piecePool.isEmpty(); ++x) { if ((x >= x1) && (x < x2) && (y >= y1) && (y < y2)) { continue; // This space reserved. } // Select a random piece. Palapeli::Piece* piece = piecePool.takeAt( qrand() % piecePool.count()); // Place it randomly in grid-cell (x, y). const QPointF p0(0.0, 0.0); piece->setPlace(p0, x, y, pieceAreaSize, true); // Add piece to the puzzle table list (only). m_puzzleTableScene->addPieceToList(piece); } } // Save the generated puzzle. // // If the user goes back to the collection, without making any // moves, and looks at another puzzle, the generated puzzle // should not be shuffled again when he/she reloads: only when // he/she hits Restart Puzzle or chooses to resart a previously // solved puzzle. updateSavedGame(); } // Add constraint_handles+spacer to puzzle table and setSceneRect(). QRectF s = m_puzzleTableScene->piecesBoundingRect(); qreal handleWidth = qMin(s.width(), s.height())/100.0; m_puzzleTableScene->addMargin(handleWidth, 0.5*handleWidth); // Add all the pieces to the puzzle table and piece-holder scenes. foreach (Palapeli::View* view, m_viewList) { Palapeli::Scene* scene = view->scene(); scene->addPieceItemsToScene(); if (scene != m_puzzleTableScene) { // Expand the piece-holder sceneRects. scene->setSceneRect(scene->extPiecesBoundingRect()); } } qDebug() << "Finish loadPiecePositions(): time" << t.restart(); finishLoading(); } void Palapeli::GamePlay::finishLoading() { // qDebug() << "finishLoading(): Starting"; m_puzzle->dropComponent(Palapeli::PuzzleComponent::Contents); // Start each scene and view. qDebug() << "COUNTING CURRENT PIECES"; m_currentPieceCount = 0; foreach (Palapeli::View* view, m_viewList) { Palapeli::Scene* scene = view->scene(); m_currentPieceCount = m_currentPieceCount + scene->pieces().size(); qDebug() << "Counted" << scene->pieces().size(); if (view != m_puzzleTable->view()) { // Saved-and-restored holders start in close-up scale. view->setCloseUp(true); } else { qDebug() << "Puzzle table" << scene->pieces().size(); } } // Initialize external progress display, hide loading widget, show view. emit reportProgress(m_originalPieceCount, m_currentPieceCount); // Adjust zoom-levels, center the view, show autosave message if needed. m_puzzleTable->view()->puzzleStarted(); if (!m_restoredGame && (m_originalPieceCount >= LargePuzzle)) { // New puzzle and a large one: create a default PieceHolder. createHolder(i18nc("For holding pieces", "Hand")); KMessageBox::information(m_mainWindow, i18nc("Hints for solving large puzzles", "You have just created a large puzzle: Palapeli has " "several features to help you solve it within the " "limited space on the desktop. They are described in " "detail in the Palapeli Handbook (on the Help menu). " "Here are just a few quick tips.\n\n" "Before beginning, it may be best not to use bevels or " "shadowing with large puzzles (see the Settings " "dialog), because they make loading slower and " "highlighting harder to see when the pieces in the " "view are very small.\n\n" "The first feature is the puzzle Preview (a picture of " "the completed puzzle) and a toolbar button to turn it " "on or off. If you hover over it with the mouse, it " "magnifies parts of the picture, so the window size " "you choose for the Preview can be quite small.\n\n" "Next, there are close-up and distant views of the " "puzzle table, which you can switch quickly by using " "a mouse button (default Middle-Click). In close-up " "view, use the empty space in the scroll bars to " "search through the puzzle pieces a 'page' at a time. " "You can adjust the two views by zooming in or out " "and your changes will be remembered.\n\n" "Then there is a space on the puzzle table reserved " "for building up the solution.\n\n" "Last but not least, there are small windows called " "'holders'. They are for sorting pieces into groups " "such as edges, sky or white house on left. You can " "have as many holders as you like and can give " "them names. You should already have one named " "'Hand', for carrying pieces from wherever you find " "them to the solution area.\n\n" "You use a special mouse click to transfer pieces into " "or out of a holder (default Shift Left-Click). First " "make sure the holder you want to use is active: it " "should have a blue outline. If not, click on it. To " "transfer pieces into the holder, select them on the " "puzzle table then do the special click to 'teleport' " "them into the holder. Or you can just do the special " "click on one piece at a time.\n\n" "To transfer pieces out of a holder, make " "sure no pieces are selected on the puzzle table, go " "into the holder window and select some pieces, using " "normal Palapeli mouse operations, then go back to the " "puzzle table and do the special click on an empty " "space where you want the pieces to arrive. Transfer " "no more than a few pieces at a time, to avoid " "collisions of pieces on the puzzle table.\n\n" "By the way, holders can do almost all the things the " "puzzle table and its window can do, including joining " "pieces to build up a part of the solution."), i18nc("Caption for hints", "Solving Large Puzzles"), QStringLiteral("largepuzzle-introduction")); } // Check if puzzle has been completed. if (m_currentPieceCount == 1) { int result = KMessageBox::questionYesNo(m_mainWindow, i18n("You have finished the puzzle. Do you want to restart it now?")); if (result == KMessageBox::Yes) { restartPuzzle(); return; } } // Connect moves and merges of pieces to autosaving and progress-report. foreach (Palapeli::View* view, m_viewList) { connect(view->scene(), SIGNAL(saveMove(int)), this, SLOT(positionChanged(int))); if (view != m_puzzleTable->view()) { connect(view, SIGNAL(teleport(Piece*,const QPointF&,View*)), this, SLOT(teleport(Piece*,const QPointF&,View*))); connect(view, SIGNAL(newPieceSelectionSeen(View*)), this, SLOT(handleNewPieceSelection(View*))); } } // Enable playing actions. m_loadingPuzzle = false; setPalapeliMode(true); qDebug() << "finishLoading(): time" << t.restart(); } void Palapeli::GamePlay::calculatePieceAreaSize() { m_pieceAreaSize = QSizeF(0.0, 0.0); foreach (Palapeli::Piece* piece, m_loadedPieces) { m_pieceAreaSize = m_pieceAreaSize.expandedTo (piece->sceneBareBoundingRect().size()); } qDebug() << "m_pieceAreaSize =" << m_pieceAreaSize; } void Palapeli::GamePlay::playVictoryAnimation() { m_puzzleTableScene->setConstrained(true); QPropertyAnimation* animation = new QPropertyAnimation (m_puzzleTableScene, "sceneRect", this); animation->setStartValue(m_puzzleTableScene->sceneRect()); animation->setEndValue(m_puzzleTableScene->extPiecesBoundingRect()); animation->setDuration(1000); connect(animation, SIGNAL(finished()), this, SLOT(playVictoryAnimation2())); animation->start(QAbstractAnimation::DeleteWhenStopped); } void Palapeli::GamePlay::playVictoryAnimation2() { m_puzzleTableScene->setSceneRect(m_puzzleTableScene->extPiecesBoundingRect()); QTimer::singleShot(100, this, SIGNAL(victoryAnimationFinished())); // Give the View some time to play its part of the victory animation. QTimer::singleShot(1500, this, SLOT(playVictoryAnimation3())); } void Palapeli::GamePlay::playVictoryAnimation3() { KMessageBox::information(m_mainWindow, i18n("Great! You have finished the puzzle.")); } void Palapeli::GamePlay::positionChanged(int reduction) { if (reduction) { qDebug() << "Reduction:" << reduction << "from" << m_currentPieceCount; bool victory = (m_currentPieceCount > 1) && ((m_currentPieceCount - reduction) <= 1); m_currentPieceCount = m_currentPieceCount - reduction; emit reportProgress(m_originalPieceCount, m_currentPieceCount); if (victory) { playVictoryAnimation(); } } if (!m_savegameTimer->isActive()) m_savegameTimer->start(); } void Palapeli::GamePlay::updateSavedGame() { static const QString pathTemplate = QString::fromLatin1("collection/%1.save"); - KConfig savedConfig(KStandardDirs::locateLocal("appdata", + KConfig savedConfig(QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, pathTemplate.arg(m_puzzle->identifier()))); savePuzzleSettings(&savedConfig); // Save the positions of pieces and attributes of piece-holders. KConfigGroup headerGroup (&savedConfig, HeaderSaveGroup); KConfigGroup holderGroup (&savedConfig, HolderSaveGroup); KConfigGroup locationGroup (&savedConfig, LocationSaveGroup); headerGroup.writeEntry("N_Holders", m_viewList.count() - 1); int groupID = 0; foreach (Palapeli::View* view, m_viewList) { bool isHolder = (view != m_puzzleTable->view()); if (isHolder) { KConfigGroup holderDetails(&savedConfig, QString("Holder_%1").arg(groupID)); Palapeli::PieceHolder* holder = qobject_cast(view); bool selected = (view == m_currentHolder); holderDetails.writeEntry("Name", holder->name()); holderDetails.writeEntry("Selected", selected); holderDetails.writeEntry("Geometry", QRect(view->frameGeometry().topLeft(), view->size())); } const QList pieces = view->scene()->pieces(); foreach (Palapeli::Piece* piece, pieces) { const QPointF position = piece->pos(); foreach (int atomicPieceID, piece->representedAtomicPieces()) { const QString ID = QString::number(atomicPieceID); locationGroup.writeEntry(ID, position); if (isHolder) { holderGroup.writeEntry(ID, groupID); } else { holderGroup.deleteEntry(ID); } } } groupID++; } } void Palapeli::GamePlay::savePuzzleSettings(KConfig* savedConfig) { // Save the Appearance settings of the pieces and puzzle background. KConfigGroup settingsGroup (savedConfig, AppearanceSaveGroup); settingsGroup.writeEntry("PieceBevelsEnabled", Settings::pieceBevelsEnabled()); settingsGroup.writeEntry("PieceShadowsEnabled", Settings::pieceShadowsEnabled()); settingsGroup.writeEntry("PieceSpacing", Settings::pieceSpacing()); settingsGroup.writeEntry("ViewBackground", Settings::viewBackground()); settingsGroup.writeEntry("ViewBackgroundColor", Settings::viewBackgroundColor()); settingsGroup.writeEntry("ViewHighlightColor", Settings::viewHighlightColor()); Palapeli::ConfigDialog::SolutionSpace solutionArea = (Palapeli::ConfigDialog::SolutionSpace) Settings::solutionArea(); settingsGroup.writeEntry("SolutionArea", (int)solutionArea); // Save the Preview settings. KConfigGroup previewGroup (savedConfig, PreviewSaveGroup); previewGroup.writeEntry("PuzzlePreviewGeometry", Settings::puzzlePreviewGeometry()); previewGroup.writeEntry("PuzzlePreviewVisible", Settings::puzzlePreviewVisible()); } void Palapeli::GamePlay::restorePuzzleSettings(KConfig* savedConfig) { // Assume Palapeli::loadPuzzle() has tested if Appearance group exists. KConfigGroup settingsGroup(savedConfig, AppearanceSaveGroup); Settings::setPieceBevelsEnabled(settingsGroup.readEntry( "PieceBevelsEnabled", false)); Settings::setPieceShadowsEnabled(settingsGroup.readEntry( "PieceShadowsEnabled", false)); Settings::setPieceSpacing(settingsGroup.readEntry( "PieceSpacing", 6)); Settings::setViewBackground(settingsGroup.readEntry( "ViewBackground", "background.svg")); Settings::setViewBackgroundColor(settingsGroup.readEntry( "ViewBackgroundColor", QColor(0xfff7eb))); Settings::setViewHighlightColor(settingsGroup.readEntry( "ViewHighlightColor", QColor(0x6effff))); Settings::setSolutionArea(settingsGroup.readEntry( "SolutionArea", 2)); // Ask TextureHelper to re-draw background (but only after KConfigDialog // has written the settings, which might happen after this slot call). QTimer::singleShot(0, Palapeli::TextureHelper::instance(), SLOT(readSettings())); if (savedConfig->hasGroup(PreviewSaveGroup)) { KConfigGroup previewGroup(savedConfig, PreviewSaveGroup); Settings::setPuzzlePreviewGeometry(previewGroup.readEntry( "PuzzlePreviewGeometry", QRect(-1,-1,320,240))); Settings::setPuzzlePreviewVisible(previewGroup.readEntry( "PuzzlePreviewVisible", true)); } } void Palapeli::GamePlay::changeSelectedHolder(Palapeli::PieceHolder* h) { if (m_currentHolder && (m_currentHolder != h)) { m_previousHolder = m_currentHolder; m_currentHolder->setSelected(false); } m_currentHolder = h; } diff --git a/src/engine/texturehelper.cpp b/src/engine/texturehelper.cpp index 0faf254..11457e6 100644 --- a/src/engine/texturehelper.cpp +++ b/src/engine/texturehelper.cpp @@ -1,128 +1,134 @@ /*************************************************************************** * Copyright 2009, 2010 Stefan Majewsky * * 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 "texturehelper.h" #include "settings.h" -#include +#include +#include #include #include -#include #include -#include #include const QSize Palapeli::TextureHelper::DefaultThumbnailSize(32, 32); const QSize Palapeli::TextureHelper::DefaultPixmapSize(128, 128); Palapeli::TextureHelper* Palapeli::TextureHelper::instance() { static Palapeli::TextureHelper instance; return &instance; } -QPixmap Palapeli::TextureHelper::render(const QString& fileName) +QPixmap Palapeli::TextureHelper::render(const QString& filePath) { - const QString path = KStandardDirs::locate("appdata", "backgrounds/" + fileName); QPixmap pixmap; - if (fileName.contains(".svg")) + if (filePath.endsWith(QLatin1String(".svg"))) { - QSvgRenderer renderer(path); + QSvgRenderer renderer(filePath); pixmap = QPixmap(DefaultPixmapSize); pixmap.fill(Qt::transparent); QPainter painter(&pixmap); renderer.render(&painter); painter.end(); } else - pixmap.load(path); + pixmap.load(filePath); return pixmap; } Palapeli::TextureHelper::TextureHelper() : m_currentIndex(-1) { //create menu item for solid color QPixmap colorThumbnail(DefaultThumbnailSize); colorThumbnail.fill(Qt::transparent); QStandardItem* colorItem = new QStandardItem; colorItem->setData(QLatin1String("__color__"), IdentifierRole); colorItem->setData(colorThumbnail, Qt::DecorationRole); colorItem->setData(i18nc("@item:inlistbox", "Single color"), Qt::DisplayRole); appendRow(colorItem); //fetch backgrounds, and create menu items - const QStringList backgroundFiles = KGlobal::dirs()->findAllResources("appdata", "backgrounds/*"); - foreach (const QString& path, backgroundFiles) + const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::AppLocalDataLocation, + "backgrounds", + QStandardPaths::LocateDirectory); + foreach (const QString& dir, dirs) { - //get file name and find selected or default backgrounds - const QString fileName = QFileInfo(path).fileName(); - //create item for this brush - const QPixmap pixmap = render(fileName); - QStandardItem* item = new QStandardItem; - item->setData(pixmap, BrushRole); - item->setData(fileName, IdentifierRole); - item->setData(pixmap.scaled(DefaultThumbnailSize, Qt::KeepAspectRatio), Qt::DecorationRole); - item->setData(fileName, Qt::DisplayRole); - appendRow(item); + QDirIterator dirIt(dir, QDir::Files); + while (dirIt.hasNext()) + { + const QString filePath = dirIt.next(); + const QString fileName = dirIt.fileName(); + //create item for this brush + const QPixmap pixmap = render(filePath); + if (pixmap.isNull()) + continue; + QStandardItem* item = new QStandardItem; + item->setData(pixmap, BrushRole); + item->setData(fileName, IdentifierRole); + item->setData(pixmap.scaled(DefaultThumbnailSize, Qt::KeepAspectRatio), Qt::DecorationRole); + item->setData(fileName, Qt::DisplayRole); + appendRow(item); + } } //select initial brush readSettings(); } int Palapeli::TextureHelper::currentIndex() const { return m_currentIndex; } void Palapeli::TextureHelper::readSettings() { //read config const QString selectedBackground = Settings::viewBackground(); const QColor selectedColor = Settings::viewBackgroundColor(); for (int i = 0; i < rowCount(); ++i) { QStandardItem* item = this->item(i); if (item->data(IdentifierRole) != selectedBackground) continue; //use this brush m_currentIndex = i; if (selectedBackground == QLatin1String("__color__")) m_currentBrush = selectedColor; else m_currentBrush = item->data(BrushRole).value(); foreach (QGraphicsScene* scene, m_scenes) scene->setBackgroundBrush(m_currentBrush); } } void Palapeli::TextureHelper::addScene(QGraphicsScene* scene) { if (!scene || m_scenes.contains(scene)) return; m_scenes << scene; scene->setBackgroundBrush(m_currentBrush); connect(scene, SIGNAL(destroyed(QObject*)), SLOT(removeScene(QObject*))); } void Palapeli::TextureHelper::removeScene(QObject* scene) { //called by scene->QObject::destroyed signal, so qobject_cast won't work anymore m_scenes.removeAll(static_cast(scene)); } diff --git a/src/engine/texturehelper.h b/src/engine/texturehelper.h index d07da47..98b5d30 100644 --- a/src/engine/texturehelper.h +++ b/src/engine/texturehelper.h @@ -1,56 +1,56 @@ /*************************************************************************** * Copyright 2009, 2010 Stefan Majewsky * * 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. ***************************************************************************/ #ifndef PALAPELI_TEXTUREHELPER_H #define PALAPELI_TEXTUREHELPER_H class QGraphicsScene; #include namespace Palapeli { //NOTE: Singleton, needs to be initialized in main(). class TextureHelper : public QStandardItemModel { Q_OBJECT public: enum CustomRoles { BrushRole = Qt::UserRole + 1, IdentifierRole = Qt::UserRole + 2 }; static Palapeli::TextureHelper* instance(); int currentIndex() const; public Q_SLOTS: void readSettings(); void addScene(QGraphicsScene* scene); void removeScene(QObject* object); private: TextureHelper(); - static QPixmap render(const QString& fileName); + static QPixmap render(const QString& filePath); QList m_scenes; int m_currentIndex; QBrush m_currentBrush; static const QSize DefaultThumbnailSize; static const QSize DefaultPixmapSize; }; } #endif // PALAPELI_TEXTUREHELPER_H diff --git a/src/file-io/collection.cpp b/src/file-io/collection.cpp index 29b113d..ba5a017 100644 --- a/src/file-io/collection.cpp +++ b/src/file-io/collection.cpp @@ -1,213 +1,213 @@ /*************************************************************************** * Copyright 2009-2011 Stefan Majewsky * * 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 "collection.h" #include "collection_p.h" #include "components.h" #include "puzzle.h" #include #include #include #include +#include #include #include #include -#include //BEGIN Palapeli::Collection::Item Palapeli::Collection::Item::Item(Palapeli::Puzzle* puzzle) : m_puzzle(puzzle) { //NOTE: Previously, the metadata.modifyProtection flag was used to decide //whether the puzzle is deletable. The current implementation uses the //identifier: Puzzles that have been imported by the user have UUID //identifiers. QUuid::createUuid().toString() always encloses the UUID in //curly braces. const QString id = puzzle->identifier(); setData(id, Palapeli::Collection::IdentifierRole); setData(id.startsWith(QChar('{')), Palapeli::Collection::IsDeleteableRole); setData(i18n("Loading puzzle..."), Qt::DisplayRole); setFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable); //request metadata Palapeli::FutureWatcher* watcher = new Palapeli::FutureWatcher; connect(watcher, SIGNAL(finished()), SLOT(populate())); connect(watcher, SIGNAL(finished()), watcher, SLOT(deleteLater())); watcher->setFuture(puzzle->get(Palapeli::PuzzleComponent::Metadata)); //take ownership of puzzle m_puzzle->QObject::setParent(this); } void Palapeli::Collection::Item::populate() { const Palapeli::MetadataComponent* cmp = m_puzzle->component(); if (!cmp) return; const Palapeli::PuzzleMetadata metadata = cmp->metadata; setData(metadata.name, Palapeli::Collection::NameRole); setData(metadata.comment, Palapeli::Collection::CommentRole); setData(metadata.author, Palapeli::Collection::AuthorRole); setData(metadata.pieceCount, Palapeli::Collection::PieceCountRole); setData(metadata.thumbnail, Palapeli::Collection::ThumbnailRole); } //END Palapeli::Collection::Item Palapeli::Collection* Palapeli::Collection::instance() { static Palapeli::Collection instance; return &instance; } static QString readPseudoUrl(const QString& path_, bool local = false) { if (path_.startsWith(QLatin1String("palapeli:/"))) { QString path(path_); path.remove(QRegExp("^palapeli:/*")); if (local) - return KStandardDirs::locateLocal("appdata", path); + return QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, path); else - return KStandardDirs::locate("appdata", path); + return QStandardPaths::locate(QStandardPaths::AppDataLocation, path); } else return path_; } Palapeli::Collection::Collection() : m_config(new KConfig("palapeli-collectionrc", KConfig::CascadeConfig)) , m_group(new KConfigGroup(m_config, "Palapeli Collection")) { //read the puzzles const QStringList puzzleIds = m_group->groupList(); foreach (const QString& puzzleId, puzzleIds) { KConfigGroup* puzzleGroup = new KConfigGroup(m_group, puzzleId); //find involved files const QString basePath = puzzleGroup->readEntry("Location", QString()); const QString path = readPseudoUrl(basePath); QString baseDesktopPath(basePath); baseDesktopPath.replace(QRegExp("\\.puzzle$"), ".desktop"); const QString desktopPath = readPseudoUrl(baseDesktopPath); //construct puzzle with CollectionStorageComponent if (!path.isEmpty() && (desktopPath.isEmpty() || QFileInfo(path).lastModified() >= QFileInfo(desktopPath).lastModified())) { Palapeli::Puzzle* puzzle = new Palapeli::Puzzle(new Palapeli::CollectionStorageComponent(puzzleGroup), path, puzzleId); appendRow(new Item(puzzle)); continue; } //no success - try to construct with RetailStorageComponent if (desktopPath.isEmpty()) continue; const QString puzzlePath = readPseudoUrl(basePath, true); Palapeli::Puzzle* puzzle = new Palapeli::Puzzle(new Palapeli::RetailStorageComponent(desktopPath), puzzlePath, puzzleId); appendRow(new Item(puzzle)); delete puzzleGroup; //make sure puzzle gets converted to archive format puzzle->get(Palapeli::PuzzleComponent::ArchiveStorage); } } Palapeli::Collection::~Collection() { delete m_config; delete m_group; } Palapeli::Puzzle* Palapeli::Collection::puzzleFromIndex(const QModelIndex& index) const { //a simple lookup like dynamic_cast(itemFromIndex(index))->puzzle() //does not work because of proxy models in the CollectionView const QString identifier = index.data(IdentifierRole).toString(); const int count = rowCount(); for (int row = 0; row < count; ++row) { Item* item = dynamic_cast(this->item(row)); if (item && item->puzzle()->identifier() == identifier) return item->puzzle(); } return 0; } void Palapeli::Collection::importPuzzle(Palapeli::Puzzle* puzzle) { //determine new location const QString id = puzzle->identifier(); const QString fileName = QString::fromLatin1("collection/%1.puzzle").arg(id); - puzzle->setLocation(KStandardDirs::locateLocal("appdata", fileName)); + puzzle->setLocation(QStandardPaths::locate(QStandardPaths::AppLocalDataLocation, fileName)); //store puzzle puzzle->get(Palapeli::PuzzleComponent::ArchiveStorage).waitForFinished(); //create the config group for this puzzle (use pseudo-URL to avoid problems //when the configuration directory is moved) KConfigGroup puzzleGroup(m_group, id); puzzleGroup.writeEntry("Location", QString("palapeli:///%1").arg(fileName)); m_config->sync(); //add to the model appendRow(new Item(puzzle)); } Palapeli::Puzzle* Palapeli::Collection::importPuzzle(const QString& path) { const QString id = Palapeli::Puzzle::fsIdentifier(path); Palapeli::Puzzle* puzzle = new Palapeli::Puzzle(new Palapeli::ArchiveStorageComponent, path, id); //insert a copy of this puzzle into the collection const QString newId = QUuid::createUuid().toString(); Palapeli::Puzzle* newPuzzle = new Palapeli::Puzzle(new Palapeli::CopyComponent(puzzle), path, newId); importPuzzle(newPuzzle); //cleanup puzzle->QObject::setParent(newPuzzle); return newPuzzle; } void Palapeli::Collection::exportPuzzle(const QModelIndex& index, const QString& path) { //find existing puzzle Palapeli::Puzzle* puzzle = puzzleFromIndex(index); if (!puzzle) return; //create a copy of the given puzzle, and relocate it to the new location const QString identifier = Palapeli::Puzzle::fsIdentifier(path); Palapeli::Puzzle* newPuzzle = new Palapeli::Puzzle(new Palapeli::CopyComponent(puzzle), path, identifier); newPuzzle->get(Palapeli::PuzzleComponent::ArchiveStorage).waitForFinished(); } bool Palapeli::Collection::deletePuzzle(const QModelIndex& index) { Palapeli::Puzzle* puzzle = puzzleFromIndex(index); if (!puzzle) return false; //check whether that particular file can be removed if (!QFile(puzzle->location()).remove()) return false; //remove puzzle from config KConfigGroup(m_group, puzzle->identifier()).deleteGroup(); m_config->sync(); //remove puzzle from model and delete it const int count = rowCount(); for (int row = 0; row < count; ++row) { Item* item = dynamic_cast(this->item(row)); if (item && item->puzzle() == puzzle) qDeleteAll(this->takeRow(row)); } return true; } //