File indexing completed on 2024-04-14 03:59:03

0001 /*
0002     SPDX-FileCopyrightText: 2009 Mathias Kraus <k.hias@gmx.de>
0003     SPDX-FileCopyrightText: 2007 Mauricio Piacentini <mauricio@tabuleiro.com>
0004     SPDX-FileCopyrightText: 2007 Matt Williams <matt@milliams.com>
0005 
0006     SPDX-License-Identifier: GPL-2.0-or-later
0007 */
0008 
0009 #include "arenaselector.h"
0010 #include "arena.h"
0011 #include "arenaitem.h"
0012 #include "mapparser.h"
0013 #include "settings.h"
0014 
0015 #include <QPointer>
0016 
0017 #include <KGameTheme>
0018 #include <QIcon>
0019 #include <KGameRenderer>
0020 #include <KConfigSkeleton>
0021 #include <QFile>
0022 #include <QDir>
0023 #include <QStandardPaths>
0024 
0025 #include "ui_arenaselector.h"
0026 #include "arenasettings.h"
0027 
0028 class ArenaSelector::Private
0029 {
0030     public:
0031         Private(ArenaSelector* parent, Options options);
0032         ~Private();
0033 
0034         ArenaSelector* q;
0035         Options m_options;
0036 
0037         QMap<QString, ArenaSettings*> arenaMap;
0038         Ui::ArenaSelectorBase ui;
0039         QString lookupDirectory;
0040         QString groupName;
0041 
0042         Arena* m_arena;
0043         KGameRenderer* m_renderer;
0044         QGraphicsScene* m_graphicsScene;
0045         QList <KGameRenderedItem*> m_arenaItems;
0046         qreal m_svgScaleFactor;
0047 
0048         QStringList* m_randomArenaModeArenaList;
0049         QStringList m_tempRandomArenaModeArenaList;
0050 
0051         void setupData(KConfigSkeleton* aconfig);
0052         void findArenas(const QString &initialSelection);
0053         QSize calculateSvgSize();
0054 
0055         // private slots
0056         void _k_updatePreview(QListWidgetItem* currentItem = nullptr);
0057         void _k_updateArenaList(const QString& strArena);
0058         void _k_setRandomArenaMode(bool randomModeEnabled);
0059         void _k_updateRandomArenaModeArenaList(QListWidgetItem* item);
0060 };
0061 
0062 ArenaSelector::ArenaSelector(QWidget* parent, KConfigSkeleton* aconfig, QStringList* randomArenaModeArenaList, ArenaSelector::Options options, const QString& groupName, const QString& directory)
0063     : QWidget(parent), d(new Private(this, options))
0064 {
0065     d->m_randomArenaModeArenaList = randomArenaModeArenaList;
0066     d->lookupDirectory = directory;
0067     d->groupName = groupName;
0068     d->setupData(aconfig);
0069 }
0070 
0071 ArenaSelector::~ArenaSelector()
0072 {
0073     delete d;
0074 }
0075 
0076 void ArenaSelector::resizeEvent(QResizeEvent*)
0077 {
0078     d->_k_updatePreview();
0079 }
0080 
0081 void ArenaSelector::showEvent(QShowEvent*)
0082 {
0083     d->_k_updatePreview();
0084 }
0085 
0086 ArenaSelector::Private::Private(ArenaSelector* parent, Options options) : q(parent), m_options(options), m_arena(nullptr), m_graphicsScene(nullptr), m_svgScaleFactor(1)
0087 {
0088     auto* theme = new KGameTheme(QByteArray());
0089     theme->setGraphicsPath(QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("themes/granatier.svgz")));
0090     m_renderer = new KGameRenderer(theme);
0091 }
0092 
0093 ArenaSelector::Private::~Private()
0094 {
0095     qDeleteAll(arenaMap);
0096     if(m_graphicsScene)
0097     {
0098         qDeleteAll(m_arenaItems);
0099         delete m_graphicsScene;
0100     }
0101     delete m_renderer;
0102     delete m_arena;
0103 }
0104 
0105 void ArenaSelector::Private::setupData(KConfigSkeleton * aconfig)
0106 {
0107     ui.setupUi(q);
0108 
0109     ui.getNewButton->hide();
0110 
0111     //The lineEdit widget holds our arena path for automatic connection via KConfigXT.
0112     //But the user should not manipulate it directly, so we hide it.
0113     ui.kcfg_Arena->hide();
0114     connect(ui.kcfg_Arena, SIGNAL(textChanged(QString)), q, SLOT(_k_updateArenaList(QString)));
0115 
0116     //graphicsscene for new arena preview
0117     m_graphicsScene = new QGraphicsScene();
0118     ui.arenaPreview->setScene(m_graphicsScene);
0119     ui.arenaPreview->setBackgroundBrush(Qt::black);
0120 
0121     //Get the last used arena path from the KConfigSkeleton
0122     KConfigSkeletonItem * configItem = aconfig->findItem(QStringLiteral("Arena"));
0123     QString lastUsedArena = configItem->property().toString();
0124 
0125     configItem = aconfig->findItem(QStringLiteral("RandomArenaModeArenaList"));
0126     m_tempRandomArenaModeArenaList = configItem->property().toStringList();
0127     m_tempRandomArenaModeArenaList.removeDuplicates();
0128 
0129     //Now get our arenas into the list widget
0130     findArenas(lastUsedArena);
0131 
0132     connect(ui.kcfg_RandomArenaMode, SIGNAL(toggled(bool)), q, SLOT(_k_setRandomArenaMode(bool)));
0133 }
0134 
0135 void ArenaSelector::Private::findArenas(const QString &initialSelection)
0136 {
0137     qDeleteAll(arenaMap);
0138     arenaMap.clear();
0139 
0140     //Disconnect the arenaList as we are going to clear it and do not want previews generated
0141     ui.arenaList->disconnect();
0142     ui.arenaList->clear();
0143     ui.arenaList->setSortingEnabled(true);
0144 
0145     QStringList arenasAvailable;
0146     const QStringList dirs = QStandardPaths::locateAll(QStandardPaths::AppDataLocation, QStringLiteral("arenas"), QStandardPaths::LocateDirectory);
0147     for(auto& dir: dirs) {
0148          const QStringList fileNames = QDir(dir).entryList({QStringLiteral("*.desktop")});
0149          for(auto& file: fileNames) {
0150                 arenasAvailable.append(file);
0151          }
0152     }
0153     
0154     QStringList randomArenaModeArenaList;
0155     // store the random arenas if they are available
0156     for(const auto& randomArena: m_tempRandomArenaModeArenaList) {
0157         if(arenasAvailable.contains(randomArena)) {
0158             randomArenaModeArenaList.append(randomArena);
0159         }
0160     }
0161 
0162     m_tempRandomArenaModeArenaList = randomArenaModeArenaList.isEmpty() ? arenasAvailable : randomArenaModeArenaList;
0163 
0164     bool initialFound = false;
0165 
0166     for(const auto& file: arenasAvailable)
0167     {
0168       QString arenaPath = lookupDirectory + QLatin1Char('/') + file;
0169       auto* arenaSettings = new ArenaSettings(groupName);
0170 
0171       if (arenaSettings->load(arenaPath)) {
0172         QString arenaName; // Start with an empty QString here so that the first += allocates a reserve for future +=.
0173         arenaName += arenaSettings->arenaProperty(QStringLiteral("Name"));
0174         //Add underscores to avoid duplicate names.
0175         while (arenaMap.contains(arenaName))
0176           arenaName += QLatin1Char('_');
0177         arenaMap.insert(arenaName, arenaSettings);
0178         auto * item = new QListWidgetItem(arenaName, ui.arenaList);
0179         if(ui.kcfg_RandomArenaMode->isChecked())
0180         {
0181             if(m_tempRandomArenaModeArenaList.contains(file))
0182             {
0183                 item->setCheckState(Qt::Checked);
0184             }
0185             else
0186             {
0187                 item->setCheckState(Qt::Unchecked);
0188             }
0189             item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
0190         }
0191         else
0192         {
0193             item->setCheckState(Qt::PartiallyChecked);
0194             item->setFlags(item->flags() & ~Qt::ItemIsUserCheckable);
0195         }
0196 
0197         //Find if this is our currently configured arena
0198         if (arenaPath==initialSelection) {
0199           initialFound = true;
0200           ui.arenaList->setCurrentItem(item);
0201           _k_updatePreview(item);
0202         }
0203       } else {
0204         delete arenaSettings;
0205       }
0206     }
0207 
0208     if (!initialFound)
0209     {
0210       // TODO change this if we ever change ArenaSettings::loadDefault
0211       QLatin1String defaultPath("arenas/granatier.desktop");
0212       for(auto arenaSettings: std::as_const(arenaMap))
0213       {
0214         if (arenaSettings->path().endsWith(defaultPath))
0215         {
0216           const QList<QListWidgetItem *> itemList = ui.arenaList->findItems(arenaSettings->arenaProperty(QStringLiteral("Name")), Qt::MatchExactly);
0217           // never can be != 1 but better safe than sorry
0218           if (itemList.count() == 1)
0219           {
0220             ui.arenaList->setCurrentItem(itemList.first());
0221             _k_updatePreview(itemList.first());
0222           }
0223         }
0224       }
0225     }
0226 
0227     //Reconnect the arenaList
0228     connect(ui.arenaList, SIGNAL(currentItemChanged(QListWidgetItem*,QListWidgetItem*)), q, SLOT(_k_updatePreview(QListWidgetItem*)));
0229     if(ui.kcfg_RandomArenaMode->isChecked())
0230     {
0231         connect(ui.arenaList, SIGNAL(itemChanged(QListWidgetItem*)), q, SLOT(_k_updateRandomArenaModeArenaList(QListWidgetItem*)));
0232     }
0233 }
0234 
0235 void ArenaSelector::Private::_k_updatePreview(QListWidgetItem* currentItem)
0236 {
0237     if(currentItem != nullptr)
0238     {
0239         ArenaSettings * selArena = arenaMap.value(ui.arenaList->currentItem()->text());
0240         //Sanity checkings. Should not happen.
0241         if (!selArena) return;
0242         if (selArena->path() == ui.kcfg_Arena->text()) {
0243             return;
0244         }
0245         ui.kcfg_Arena->setText(selArena->fileName());
0246 
0247         QString authstr(QStringLiteral("Author"));
0248         QString contactstr(QStringLiteral("AuthorEmail"));
0249         QString descstr(QStringLiteral("Description"));
0250         QString emailstr = selArena->arenaProperty(contactstr);
0251         if(emailstr.compare(QLatin1String("-")) == 0) // the imported clanbomber arenas have a "-" if no email address was defined in the clanbomber arena file
0252         {
0253             emailstr.clear();
0254         }
0255         if (!emailstr.isEmpty())
0256         {
0257             emailstr = QStringLiteral("<a href=\"mailto:%1\">%1</a>").arg(selArena->arenaProperty(contactstr));
0258         }
0259 
0260         ui.arenaAuthor->setText(i18nc("Author attribution, e.g. \"by Jack\"", "by %1", selArena->arenaProperty(authstr)));
0261         ui.arenaContact->setText(emailstr);
0262         ui.arenaDescription->setText(selArena->arenaProperty(descstr));
0263 
0264         //show the arena without a preview pixmap
0265         delete m_arena;
0266         m_arena = new Arena;
0267         MapParser mapParser(m_arena);
0268         QFile arenaXmlFile(selArena->graphics());
0269         if (!arenaXmlFile.open(QIODevice::ReadOnly)) {
0270             qWarning() << " impossible to open file " << arenaXmlFile.fileName();
0271         }
0272         if (!mapParser.parse(&arenaXmlFile)) {
0273             qWarning() << " failed to parse file " << arenaXmlFile.fileName();
0274         }
0275         while(!m_arenaItems.isEmpty())
0276         {
0277             if(m_graphicsScene->items().contains(m_arenaItems.last()))
0278             {
0279                 m_graphicsScene->removeItem(m_arenaItems.last());
0280             }
0281             delete m_arenaItems.takeLast();
0282         }
0283 
0284         ui.arenaPreview->setSceneRect(0, 0, m_arena->getNbColumns()*Granatier::CellSize, m_arena->getNbRows()*Granatier::CellSize);
0285         ui.arenaPreview->fitInView(ui.arenaPreview->sceneRect(), Qt::KeepAspectRatio);
0286     }
0287 
0288     qreal svgScaleFactor;
0289     QRectF minSize = ui.arenaPreview->sceneRect();
0290 
0291     if(minSize.width() == 0)
0292     {
0293         minSize.setWidth(1);
0294     }
0295     if(minSize.height() == 0)
0296     {
0297         minSize.setHeight(1);
0298     }
0299 
0300     //calculate the scaling factor for the SVGs
0301     int horizontalPixelsPerCell = static_cast<int>((ui.arenaPreview->size().width() - 4) / (minSize.width()/Granatier::CellSize));
0302     int verticalPixelsPerCell = static_cast<int>((ui.arenaPreview->size().height() - 4) / (minSize.height()/Granatier::CellSize));
0303     if(horizontalPixelsPerCell < verticalPixelsPerCell)
0304     {
0305         svgScaleFactor = Granatier::CellSize / horizontalPixelsPerCell;
0306     }
0307     else
0308     {
0309         svgScaleFactor = Granatier::CellSize / verticalPixelsPerCell;
0310     }
0311 
0312     QTransform transform;
0313     transform.scale(1/svgScaleFactor, 1/svgScaleFactor);
0314     m_graphicsScene->views().constFirst()->setTransform(transform);
0315     m_graphicsScene->views().constFirst()->centerOn( ui.arenaPreview->sceneRect().center());
0316 
0317     if(currentItem == nullptr)
0318     {
0319         if(m_svgScaleFactor != svgScaleFactor)
0320         {
0321             m_svgScaleFactor = svgScaleFactor;
0322             for(auto arenaItem: std::as_const(m_arenaItems))
0323             {
0324                 arenaItem->setRenderSize(calculateSvgSize());
0325                 arenaItem->setScale(m_svgScaleFactor);
0326             }
0327         }
0328     }
0329     else
0330     {
0331         m_svgScaleFactor = svgScaleFactor;
0332         for (int i = 0; i < m_arena->getNbRows(); ++i)
0333         {
0334             for (int j = 0; j < m_arena->getNbColumns(); ++j)
0335             {
0336                 // Create the ArenaItem and set the image
0337                 auto* arenaItem = new ArenaItem(j * Granatier::CellSize, i * Granatier::CellSize, m_renderer, QLatin1String(""));
0338 
0339                 switch(m_arena->getCell(i,j).getType())
0340                 {
0341                     case Granatier::Cell::WALL:
0342                         arenaItem->setSpriteKey(QStringLiteral("arena_wall"));
0343                         arenaItem->setZValue(-2);
0344                         break;
0345                     case Granatier::Cell::BLOCK:
0346                         arenaItem->setSpriteKey(QStringLiteral("arena_block"));
0347                         arenaItem->setZValue(0);
0348                         break;
0349                     case Granatier::Cell::HOLE:
0350                         delete arenaItem;
0351                         arenaItem = nullptr;
0352                         break;
0353                     case Granatier::Cell::ICE:
0354                         arenaItem->setSpriteKey(QStringLiteral("arena_ice"));
0355                         arenaItem->setZValue(0);
0356                         break;
0357                     case Granatier::Cell::BOMBMORTAR:
0358                         arenaItem->setSpriteKey(QStringLiteral("arena_bomb_mortar"));
0359                         arenaItem->setZValue(0);
0360                         break;
0361                     case Granatier::Cell::ARROWUP:
0362                         arenaItem->setSpriteKey(QStringLiteral("arena_arrow_up"));
0363                         arenaItem->setZValue(0);
0364                         break;
0365                     case Granatier::Cell::ARROWRIGHT:
0366                         arenaItem->setSpriteKey(QStringLiteral("arena_arrow_right"));
0367                         arenaItem->setZValue(0);
0368                         break;
0369                     case Granatier::Cell::ARROWDOWN:
0370                         arenaItem->setSpriteKey(QStringLiteral("arena_arrow_down"));
0371                         arenaItem->setZValue(0);
0372                         break;
0373                     case Granatier::Cell::ARROWLEFT:
0374                         arenaItem->setSpriteKey(QStringLiteral("arena_arrow_left"));
0375                         arenaItem->setZValue(0);
0376                         break;
0377                     case Granatier::Cell::GROUND:
0378                     default:
0379                         arenaItem->setSpriteKey(QStringLiteral("arena_ground"));
0380                         arenaItem->setZValue(-1);
0381                 }
0382                 if(arenaItem)
0383                 {
0384                     arenaItem->setRenderSize(calculateSvgSize());
0385                     arenaItem->setScale(m_svgScaleFactor);
0386 
0387                     m_arenaItems.append(arenaItem);
0388                     m_graphicsScene->addItem(arenaItem);
0389                 }
0390             }
0391         }
0392     }
0393 }
0394 
0395 QSize ArenaSelector::Private::calculateSvgSize()
0396 {
0397     if(m_graphicsScene->views().isEmpty())
0398     {
0399         return {1, 1};
0400     }
0401 
0402     QPoint topLeft(0, 0);
0403     topLeft = m_graphicsScene->views().first()->mapFromScene(topLeft);
0404 
0405     QPoint bottomRight(static_cast<int>(Granatier::CellSize), static_cast<int>(Granatier::CellSize));
0406     bottomRight = m_graphicsScene->views().first()->mapFromScene(bottomRight);
0407 
0408     QSize svgSize;
0409     svgSize.setHeight(bottomRight.y() - topLeft.y());
0410     svgSize.setWidth(bottomRight.x() - topLeft.x());
0411 
0412     return svgSize;
0413 }
0414 
0415 void ArenaSelector::Private::_k_updateArenaList(const QString& strArena)
0416 {
0417     //find arena and set selection to the current arena; happens when pressing "Default"
0418     if(arenaMap.value(ui.arenaList->currentItem()->text())->fileName() != strArena)
0419     {
0420         for(int i = 0; i < ui.arenaList->count(); i++)
0421         {
0422             if(arenaMap.value(ui.arenaList->item(i)->text())->fileName() == strArena)
0423             {
0424                 ui.arenaList->setCurrentItem(ui.arenaList->item(i));
0425                 break;
0426             }
0427         }
0428     }
0429 }
0430 
0431 void ArenaSelector::Private::_k_setRandomArenaMode(bool randomModeEnabled)
0432 {
0433     if(!randomModeEnabled)
0434     {
0435         disconnect(ui.arenaList, SIGNAL(itemChanged(QListWidgetItem*)), q, SLOT(_k_updateRandomArenaModeArenaList(QListWidgetItem*)));
0436     }
0437 
0438     m_randomArenaModeArenaList->clear();
0439 
0440     int numberOfItems = ui.arenaList->count();
0441     for(int i = 0; i < numberOfItems; i++)
0442     {
0443         QListWidgetItem* item = ui.arenaList->item(i);
0444         if(randomModeEnabled)
0445         {
0446             QString arenaName = arenaMap.value(item->text())->fileName();
0447             arenaName.remove(0, 7); //length of "arenas/"
0448             if(m_tempRandomArenaModeArenaList.contains(arenaName))
0449             {
0450                 item->setCheckState(Qt::Checked);
0451             }
0452             else
0453             {
0454                 item->setCheckState(Qt::Unchecked);
0455             }
0456             item->setFlags(item->flags() | Qt::ItemIsUserCheckable);
0457         }
0458         else
0459         {
0460             item->setCheckState(Qt::PartiallyChecked);
0461             item->setFlags(item->flags() & ~Qt::ItemIsUserCheckable);
0462         }
0463     }
0464 
0465     if(randomModeEnabled)
0466     {
0467         *m_randomArenaModeArenaList = m_tempRandomArenaModeArenaList;
0468         connect(ui.arenaList, SIGNAL(itemChanged(QListWidgetItem*)), q, SLOT(_k_updateRandomArenaModeArenaList(QListWidgetItem*)));
0469     }
0470 }
0471 
0472 void ArenaSelector::Private::_k_updateRandomArenaModeArenaList(QListWidgetItem* item)
0473 {
0474     QString arenaName = arenaMap.value(item->text())->fileName();
0475     arenaName.remove(0, 7); //length of "arenas/"
0476     if(item->checkState() == Qt::Checked)
0477     {
0478         m_tempRandomArenaModeArenaList.append(arenaName);
0479     }
0480     else
0481     {
0482         int index = m_tempRandomArenaModeArenaList.indexOf(arenaName);
0483         if(index >= 0)
0484         {
0485             m_tempRandomArenaModeArenaList.removeAt(index);
0486         }
0487     }
0488     m_tempRandomArenaModeArenaList.removeDuplicates();
0489     *m_randomArenaModeArenaList = m_tempRandomArenaModeArenaList;
0490     Settings::self()->setDummy(Settings::self()->dummy() + 3);
0491 }
0492 #include "moc_arenaselector.cpp"
0493