File indexing completed on 2024-04-28 04:05:21
0001 /* 0002 SPDX-FileCopyrightText: 2015 Jakob Gruber <jakob.gruber@gmail.com> 0003 SPDX-FileCopyrightText: 2011 Julian Helfferich <julian.helfferich@gmail.com> 0004 0005 SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include <config.h> 0009 #include "levelloader.h" 0010 0011 #include <KLocalizedString> 0012 #include "picmi_debug.h" 0013 #include <QDir> 0014 #include <QDomDocument> 0015 #include <QFile> 0016 #include <QStandardPaths> 0017 0018 #include "settings.h" 0019 #include "src/systemexception.h" 0020 0021 class LevelList : public QList<QSharedPointer<Level> > 0022 { 0023 public: 0024 LevelList() : QList<QSharedPointer<Level> >() { } 0025 void append(const QList<QSharedPointer<Level> > &t); 0026 private: 0027 bool containsLevel(QSharedPointer<Level> level) const; 0028 }; 0029 0030 void LevelList::append(const QList<QSharedPointer<Level> > &t) { 0031 for (int i = 0; i < t.size(); i++) { 0032 QSharedPointer<Level> level = t[i]; 0033 if (!containsLevel(level)) { 0034 QList<QSharedPointer<Level> >::append(level); 0035 } 0036 } 0037 } 0038 0039 bool LevelList::containsLevel(QSharedPointer<Level> level) const { 0040 for (int i = 0; i < size(); i++) { 0041 if (*at(i) == *level) { 0042 return true; 0043 } 0044 } 0045 return false; 0046 } 0047 0048 Level::Level() : m_solved(false), m_solved_time(0) { } 0049 0050 QString Level::visibleName() const 0051 { 0052 if (solved()) { 0053 return name(); 0054 } 0055 /* Mask the real name if unsolved. Unicode 0x26AB is the bullet point ('⚫') */ 0056 return QString(name().length(), QChar(0x26AB)); 0057 } 0058 0059 QString Level::name() const { 0060 QByteArray bytes = m_name.toUtf8(); 0061 return i18n(bytes.constData()); 0062 } 0063 0064 QString Level::author() const { 0065 QByteArray bytes = m_author.toUtf8(); 0066 return i18n(bytes.constData()); 0067 } 0068 0069 QString Level::key() const { 0070 return QStringLiteral("preset_scores/%1_%2").arg(m_levelset, m_name); 0071 } 0072 0073 void Level::writeSettings(int seconds) { 0074 QSharedPointer<QSettings> settings = Settings::instance()->qSettings(); 0075 QString k = key(); 0076 0077 settings->setValue(k, seconds); 0078 settings->sync(); 0079 } 0080 0081 void Level::finalize() { 0082 constructPreview(); 0083 readSettings(); 0084 } 0085 0086 void Level::readSettings() { 0087 QSharedPointer<QSettings> settings = Settings::instance()->qSettings(); 0088 QString k = key(); 0089 0090 if (settings->contains(k)) { 0091 m_solved = true; 0092 m_solved_time = settings->value(k).toInt(); 0093 } 0094 } 0095 0096 void Level::constructPreview() { 0097 QImage preview(width(), height(), QImage::Format_Mono); 0098 preview.fill(Qt::color1); 0099 0100 for (int y = 0; y < height(); y++) { 0101 for (int x = 0; x < width(); x++) { 0102 if (m_map[y * width() + x] == Board::Box) { 0103 preview.setPixel(x, y, 0); 0104 } 0105 } 0106 } 0107 0108 m_preview = QPixmap::fromImage(preview); 0109 } 0110 0111 void Level::setSolved(int seconds) { 0112 if (m_solved_time > 0 && seconds >= m_solved_time) { 0113 return; 0114 } 0115 m_solved = true; 0116 m_solved_time = seconds; 0117 writeSettings(seconds); 0118 } 0119 0120 bool Level::operator==(const Level &that) const { 0121 return (that.m_name == m_name && that.m_author == m_author); 0122 } 0123 0124 QList<QSharedPointer<Level> > LevelLoader::load() { 0125 const QString prefix = QStringLiteral("levels/"); 0126 QList<QString> paths; 0127 paths << QString(prefix) 0128 << QStringLiteral(FILEPATH) + QStringLiteral("/") + prefix 0129 << QStandardPaths::locate(QStandardPaths::AppDataLocation, 0130 prefix, 0131 QStandardPaths::LocateOption::LocateDirectory); 0132 0133 LevelList list; 0134 0135 for (int i = 0; i < paths.size(); i++) { 0136 QDir dir(paths[i]); 0137 if (!dir.exists()) { 0138 continue; 0139 } 0140 0141 QStringList files = dir.entryList(QStringList(QStringLiteral("*.xml"))); 0142 0143 for (int j = 0; j < files.size(); j++) { 0144 LevelLoader loader(dir.absoluteFilePath(files[j])); 0145 list.append(loader.loadLevels()); 0146 } 0147 } 0148 0149 return list; 0150 } 0151 0152 LevelLoader::LevelLoader(const QString &filename) : 0153 m_filename(filename), m_valid(true) 0154 { 0155 setLevelset(filename); 0156 } 0157 0158 void LevelLoader::setLevelset(const QString& filename) 0159 { 0160 m_levelset = QSharedPointer<QDomDocument>(new QDomDocument()); 0161 0162 QFile file(filename); 0163 if (!file.open( QIODevice::ReadOnly)) { 0164 throw SystemException(QStringLiteral("Can't open file %1").arg(filename)); 0165 } 0166 0167 const QDomDocument::ParseResult parseResult = m_levelset->setContent(&file); 0168 file.close(); 0169 if (!parseResult) { 0170 qCDebug(PICMIC_LOG) << QStringLiteral("Can't read levelset from %1 \nError: %2 in Line %3, Column %4") 0171 .arg(filename, parseResult.errorMessage).arg(parseResult.errorLine).arg(parseResult.errorColumn); 0172 m_valid = false; 0173 } 0174 } 0175 0176 QList<QSharedPointer<Level> > LevelLoader::loadLevels() { 0177 QList<QSharedPointer<Level> > l; 0178 0179 if (!m_valid) { 0180 return l; 0181 } 0182 0183 QDomElement levels = m_levelset->documentElement(); 0184 if (!levels.hasAttribute(QStringLiteral("name"))) { 0185 qCDebug(PICMIC_LOG) << "Loading level failed: no levelset name specified"; 0186 return l; 0187 } 0188 m_levelsetname = levels.attribute(QStringLiteral("name")); 0189 0190 QDomNodeList childNodes = levels.childNodes(); 0191 for (int i = 0; i < childNodes.size(); i++) { 0192 try { 0193 l.append(loadLevel(childNodes.at(i).toElement())); 0194 } catch (const SystemException &e) { 0195 qCDebug(PICMIC_LOG) << "Loading level failed: " << e.what(); 0196 } 0197 } 0198 return l; 0199 } 0200 0201 QSharedPointer<Level> LevelLoader::loadLevel(const QDomElement &node) const { 0202 if (node.isNull() || node.tagName() != QLatin1String("board")) { 0203 throw SystemException(QStringLiteral("Unexpected level node")); 0204 } 0205 0206 if (!node.hasAttribute(QStringLiteral("name")) || !node.hasAttribute(QStringLiteral("author")) 0207 || !node.hasAttribute(QStringLiteral("difficulty"))) { 0208 throw SystemException(QStringLiteral("Level node missing attribute.")); 0209 } 0210 0211 QSharedPointer<Level> p(new Level); 0212 p->m_name = node.attribute(QStringLiteral("name")); 0213 p->m_author = node.attribute(QStringLiteral("author")); 0214 p->m_levelset = m_levelsetname; 0215 p->m_difficulty = node.attribute(QStringLiteral("difficulty")).toInt(); 0216 0217 QDomNodeList childNodes = node.childNodes(); 0218 0219 if (childNodes.isEmpty()) { 0220 throw SystemException(QStringLiteral("Empty level definition.")); 0221 } 0222 0223 const QString tag_name = childNodes.at(0).toElement().tagName(); 0224 if (tag_name == QLatin1String("row")) { 0225 int i; 0226 QList<Board::State> l; 0227 for (i = 0; i < childNodes.size(); i++) { 0228 l = loadRow(childNodes.at(i).toElement()); 0229 p->m_map.append(l); 0230 } 0231 p->m_width = l.size(); 0232 p->m_height = i; 0233 } else if (tag_name == QLatin1String("xpm")) { 0234 QImage xpm = openXPM(childNodes.at(0).toElement()); 0235 p->m_map = loadXPM(xpm); 0236 p->m_width = xpm.width(); 0237 p->m_height = xpm.height(); 0238 } 0239 0240 if (p->m_map.size() != p->height() * p->width()) { 0241 throw SystemException(QStringLiteral("Invalid board size")); 0242 } 0243 0244 p->finalize(); 0245 0246 return p; 0247 } 0248 0249 static Board::State charToState(const QChar &c) { 0250 switch (c.toLatin1()) { 0251 case '-': return Board::Nothing; 0252 case '1': return Board::Box; 0253 default: throw SystemException(QStringLiteral("Invalid char in level definition")); 0254 } 0255 } 0256 0257 QImage LevelLoader::openXPM(const QDomElement &node) const { 0258 if (node.isNull() || node.tagName() != QLatin1String("xpm")) { 0259 throw SystemException(QStringLiteral("Unexpected row node")); 0260 } 0261 0262 QFileInfo file(m_filename); 0263 QString filepath = file.absolutePath() + QLatin1Char('/') + node.text(); 0264 0265 QImage xpm(filepath); 0266 0267 if (xpm.isNull()) { 0268 throw SystemException(QStringLiteral("Could not load %1").arg(filepath)); 0269 } 0270 0271 return xpm; 0272 } 0273 0274 QList<Board::State> LevelLoader::loadXPM(const QImage &xpm) const { 0275 QList<Board::State> list; 0276 for (int y = 0; y < xpm.height(); y++) { 0277 for (int x = 0; x < xpm.width(); x++) { 0278 QRgb pix = xpm.pixel(x, y); 0279 list.append((pix == 0) ? Board::Nothing : Board::Box); 0280 } 0281 } 0282 0283 return list; 0284 } 0285 0286 QList<Board::State> LevelLoader::loadRow(const QDomElement &node) const { 0287 if (node.isNull() || node.tagName() != QLatin1String("row")) { 0288 throw SystemException(QStringLiteral("Unexpected row node")); 0289 } 0290 0291 const QString text = node.text(); 0292 QList<Board::State> list; 0293 for (const QChar &c : text) { 0294 Board::State s = charToState(c); 0295 list.append(s); 0296 } 0297 0298 return list; 0299 }