From 999da7ed86b58d8ffc6e32d192a94bba6892e4b6 Mon Sep 17 00:00:00 2001 From: milonekrone Date: Wed, 14 Jan 2026 16:07:33 +0100 Subject: [PATCH] Add src/xmbmodel.cpp --- src/xmbmodel.cpp | 176 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) create mode 100644 src/xmbmodel.cpp diff --git a/src/xmbmodel.cpp b/src/xmbmodel.cpp new file mode 100644 index 0000000..c3b29eb --- /dev/null +++ b/src/xmbmodel.cpp @@ -0,0 +1,176 @@ +#include "xmbmodel.h" +#include "apppaths.h" +#include "util.h" +#include "xmbentry.h" + +#include +#include +#include +#include +#include +#include + +static QString pickIcon(const QString& desired, bool category) { + if (!desired.isEmpty() && QFileInfo::exists(desired)) return QUrl::fromLocalFile(desired).toString(); + return Util::fallbackIconQrc(category); +} + +static XmbEntry parseDesktop(const QString& filePath) { + // Minimal .desktop parsing via QSettings INI + QSettings s(filePath, QSettings::IniFormat); + s.setIniCodec("UTF-8"); + + XmbEntry e; + e.type = EntryType::Desktop; + e.path = filePath; + e.title = s.value("Desktop Entry/Name", QFileInfo(filePath).baseName()).toString(); + e.subtitle = s.value("Desktop Entry/Comment", "").toString(); + e.exec = s.value("Desktop Entry/Exec", "").toString(); + // Icon field kann Theme-Icon sein; wir lassen es erstmal als String stehen (Fallback greift). + const auto iconField = s.value("Desktop Entry/Icon", "").toString(); + e.icon = iconField.isEmpty() ? Util::fallbackIconQrc(false) : iconField; + return e; +} + +static XmbEntry parseLinkFile(const QString& filePath) { + // Einfaches INI: [Link] title=..., icon=..., url=..., exec=..., args=... + QSettings s(filePath, QSettings::IniFormat); + s.setIniCodec("UTF-8"); + + XmbEntry e; + e.type = EntryType::Link; + e.path = filePath; + e.title = s.value("Link/title", QFileInfo(filePath).baseName()).toString(); + e.subtitle = s.value("Link/subtitle", "").toString(); + e.url = s.value("Link/url", "").toString(); + e.exec = s.value("Link/exec", "").toString(); + e.args = s.value("Link/args", "").toString().split(' ', Qt::SkipEmptyParts); + const auto icon = s.value("Link/icon", "").toString(); + e.icon = icon.isEmpty() ? Util::fallbackIconQrc(false) : icon; + e.gameId = s.value("Link/gameId", "").toString(); // optional + return e; +} + +static XmbEntry parseJsonAction(const QString& filePath) { + QFile f(filePath); + XmbEntry e; + e.type = EntryType::JsonAction; + e.path = filePath; + + if (!f.open(QIODevice::ReadOnly)) { + e.title = QFileInfo(filePath).baseName(); + e.icon = Util::fallbackIconQrc(false); + return e; + } + + const auto doc = QJsonDocument::fromJson(f.readAll()); + const auto o = doc.object(); + e.title = o.value("title").toString(QFileInfo(filePath).baseName()); + e.subtitle = o.value("subtitle").toString(); + e.exec = o.value("exec").toString(); + e.url = o.value("url").toString(); + e.gameId = o.value("gameId").toString(); + const auto icon = o.value("icon").toString(); + e.icon = icon.isEmpty() ? Util::fallbackIconQrc(false) : icon; + + if (o.value("args").isArray()) { + for (const auto& v : o.value("args").toArray()) + e.args << v.toString(); + } + return e; +} + +XmbModel::XmbModel(QObject* parent) : QObject(parent) { + m_cfg.load(); + build(); +} + +QString XmbModel::assetUrl(const QString& rel) const { + // rel: "sounds/move.wav" etc. + const QString base = AppPaths::assetsRoot(); + const QString abs = QDir(base).filePath(rel); + if (QFileInfo::exists(abs)) return QUrl::fromLocalFile(abs).toString(); + + // Fallback: resources/ + return QString("qrc:/%1").arg(rel); +} + +QString XmbModel::bootQml() const { + // 1) Config boot animation + const QString p = m_cfg.bootAnimation(); + if (!p.isEmpty() && QFileInfo::exists(p)) return QUrl::fromLocalFile(p).toString(); + // 2) bundled fallback + return "qrc:/qml/FallbackBoot.qml"; +} + +QVariantList XmbModel::itemsForCategory(int idx) const { + if (idx < 0 || idx >= m_categories.size()) return {}; + const auto cat = m_categories[idx].toMap(); + return cat.value("items").toList(); +} + +void XmbModel::reload() { + m_cfg.load(); + build(); +} + +void XmbModel::build() { + m_categories.clear(); + + const QDir xmbDir(AppPaths::xmbRoot()); + xmbDir.mkpath("."); // ensure exists + + // Order aus config; wenn Ordner fehlen: trotzdem als Kategorie anlegen + const auto order = m_cfg.order(); + + for (const auto& catNameRaw : order) { + const QString catName = catNameRaw.trimmed(); + QVariantMap cat; + cat["title"] = catName; + cat["icon"] = Util::fallbackIconQrc(true); + + const QString catPath = xmbDir.filePath(catName); + QDir catDir(catPath); + + QVariantList items; + + if (catDir.exists()) { + const auto entries = catDir.entryInfoList(QDir::Dirs | QDir::Files | QDir::NoDotAndDotDot, + QDir::Name | QDir::IgnoreCase); + + for (const auto& fi : entries) { + XmbEntry e; + + if (fi.isDir()) { + e.type = EntryType::Folder; + e.title = Util::sanitizeTitle(fi.fileName()); + e.subtitle = fi.absoluteFilePath(); + e.icon = Util::fallbackIconQrc(false); + e.path = fi.absoluteFilePath(); + items << e.toVariant(); + continue; + } + + const QString ext = fi.suffix().toLower(); + if (ext == "desktop") { + e = parseDesktop(fi.absoluteFilePath()); + items << e.toVariant(); + } else if (ext == "link") { + e = parseLinkFile(fi.absoluteFilePath()); + items << e.toVariant(); + } else if (ext == "json") { + e = parseJsonAction(fi.absoluteFilePath()); + items << e.toVariant(); + } else { + // ignorieren oder als Unknown anzeigen + continue; + } + } + } + + cat["items"] = items; + m_categories << cat; + } + + emit categoriesChanged(); +}