feat: add playlist, recent files and MPRIS backends

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
triandClaude Opus 5.5 committed 2026-10-28 09:26:12 +07:00
1 parent 6dc9377de1
commit 54ad4eecf3
18 files changed
+2240 -4

No files matched your search

+122
View File
@@ -0,0 +1,122 @@
// SPDX-FileCopyrightText: 2026 Tri Vo Khac
// SPDX-License-Identifier: GPL-3.0-or-later
#include "recentfilesmodel.h"
#include <KConfigGroup>
#include <KSharedConfig>
#include <algorithm>
using namespace Qt::Literals::StringLiterals;
namespace
{
// Stored even when fewer are shown, so raising the limit brings old entries back.
constexpr int StoredFiles = 50;
KConfigGroup configGroup()
{
return KSharedConfig::openStateConfig()->group(u"RecentFiles"_s);
}
}
RecentFilesModel::RecentFilesModel(QObject *parent)
: QAbstractListModel(parent)
{
const QStringList urls = configGroup().readEntry(u"Urls"_s, QStringList());
for (const QString &url : urls) {
m_urls.append(QUrl(url));
}
}
int RecentFilesModel::rowCount(const QModelIndex &parent) const
{
return parent.isValid() ? 0 : count();
}
QVariant RecentFilesModel::data(const QModelIndex &index, int role) const
{
if (!checkIndex(index, CheckIndexOption::IndexIsValid)) {
return {};
}
const QUrl &url = m_urls.at(index.row());
switch (role) {
case UrlRole:
return url;
case Qt::DisplayRole:
case NameRole:
return url.isLocalFile() ? url.fileName() : url.toDisplayString();
case PathRole:
return url.isLocalFile() ? url.toLocalFile() : url.toDisplayString();
}
return {};
}
QHash<int, QByteArray> RecentFilesModel::roleNames() const
{
return {
{UrlRole, "url"},
{NameRole, "name"},
{PathRole, "path"},
};
}
int RecentFilesModel::count() const
{
return int(std::min<qsizetype>(m_urls.size(), m_maxCount));
}
int RecentFilesModel::maxCount() const
{
return m_maxCount;
}
void RecentFilesModel::setMaxCount(int maxCount)
{
maxCount = std::clamp(maxCount, 0, StoredFiles);
if (maxCount == m_maxCount) {
return;
}
beginResetModel();
m_maxCount = maxCount;
endResetModel();
Q_EMIT maxCountChanged();
Q_EMIT countChanged();
}
void RecentFilesModel::add(const QUrl &url)
{
if (!url.isValid()) {
return;
}
beginResetModel();
m_urls.removeAll(url);
m_urls.prepend(url);
if (m_urls.size() > StoredFiles) {
m_urls.resize(StoredFiles);
}
endResetModel();
Q_EMIT countChanged();
save();
}
void RecentFilesModel::clear()
{
beginResetModel();
m_urls.clear();
endResetModel();
Q_EMIT countChanged();
save();
}
void RecentFilesModel::save() const
{
QStringList urls;
for (const QUrl &url : m_urls) {
urls.append(url.toString());
}
KConfigGroup group = configGroup();
group.writeEntry(u"Urls"_s, urls);
group.sync();
}
+53
View File
@@ -0,0 +1,53 @@
// SPDX-FileCopyrightText: 2026 Tri Vo Khac
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include <QAbstractListModel>
#include <QList>
#include <QUrl>
#include <QtQml/qqmlregistration.h>
// Recently opened files, newest first, kept in the application's state file.
class RecentFilesModel : public QAbstractListModel
{
Q_OBJECT
QML_ELEMENT
QML_SINGLETON
Q_PROPERTY(int count READ count NOTIFY countChanged)
// Rows beyond this are hidden (but kept, in case it is raised again).
Q_PROPERTY(int maxCount READ maxCount WRITE setMaxCount NOTIFY maxCountChanged)
public:
enum Roles {
UrlRole = Qt::UserRole + 1,
NameRole,
PathRole,
};
Q_ENUM(Roles)
explicit RecentFilesModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = {}) const override;
QVariant data(const QModelIndex &index, int role) const override;
QHash<int, QByteArray> roleNames() const override;
int count() const;
int maxCount() const;
void setMaxCount(int maxCount);
// Moves \a url to the top, adding it if needed.
Q_INVOKABLE void add(const QUrl &url);
Q_INVOKABLE void clear();
Q_SIGNALS:
void countChanged();
void maxCountChanged();
private:
void save() const;
QList<QUrl> m_urls;
int m_maxCount = 10;
};