Add src/xmbmodel.cpp

This commit is contained in:
milonekrone 2026-01-14 16:07:33 +01:00
parent 41816c0a16
commit 999da7ed86

176
src/xmbmodel.cpp Normal file
View File

@ -0,0 +1,176 @@
#include "xmbmodel.h"
#include "apppaths.h"
#include "util.h"
#include "xmbentry.h"
#include <QDir>
#include <QFile>
#include <QFileInfo>
#include <QJsonDocument>
#include <QJsonObject>
#include <QSettings>
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();
}