File indexing completed on 2025-02-09 04:33:05
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