2026-01-18 12:13:07 +00:00
|
|
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
|
// SPDX-FileCopyrightText: 2024 A-La-Karte Contributors
|
|
|
|
|
|
|
|
|
|
#include "gamemodel.h"
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
|
|
GameModel::GameModel(QObject *parent)
|
|
|
|
|
: QAbstractListModel(parent)
|
|
|
|
|
{
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int GameModel::rowCount(const QModelIndex &parent) const
|
|
|
|
|
{
|
|
|
|
|
if (parent.isValid()) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
return m_filteredGames.count();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QVariant GameModel::data(const QModelIndex &index, int role) const
|
|
|
|
|
{
|
|
|
|
|
if (!index.isValid() || index.row() < 0 || index.row() >= m_filteredGames.count()) {
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Game *game = m_filteredGames.at(index.row());
|
|
|
|
|
|
|
|
|
|
switch (role) {
|
|
|
|
|
case IdRole:
|
|
|
|
|
return game->id();
|
|
|
|
|
case NameRole:
|
|
|
|
|
case Qt::DisplayRole:
|
|
|
|
|
return game->name();
|
|
|
|
|
case DescriptionRole:
|
|
|
|
|
return game->description();
|
|
|
|
|
case DeveloperRole:
|
|
|
|
|
return game->developer();
|
|
|
|
|
case PublisherRole:
|
|
|
|
|
return game->publisher();
|
|
|
|
|
case CoverUrlRole:
|
|
|
|
|
return game->coverUrl();
|
|
|
|
|
case IconUrlRole:
|
|
|
|
|
return game->iconUrl();
|
|
|
|
|
case LaunchCommandRole:
|
|
|
|
|
return game->launchCommand();
|
|
|
|
|
case PlatformRole:
|
|
|
|
|
return game->platform();
|
|
|
|
|
case PlatformIdRole:
|
|
|
|
|
return game->platformId();
|
|
|
|
|
case LastPlayedRole:
|
|
|
|
|
return game->lastPlayed();
|
|
|
|
|
case PlayTimeRole:
|
|
|
|
|
return game->playTime();
|
|
|
|
|
case FavoriteRole:
|
|
|
|
|
return game->favorite();
|
|
|
|
|
case HiddenRole:
|
|
|
|
|
return game->hidden();
|
|
|
|
|
case InstalledRole:
|
|
|
|
|
return game->installed();
|
|
|
|
|
case RunningRole:
|
|
|
|
|
return game->running();
|
|
|
|
|
case GameObjectRole:
|
|
|
|
|
return QVariant::fromValue(game);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return {};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GameModel::setData(const QModelIndex &index, const QVariant &value, int role)
|
|
|
|
|
{
|
|
|
|
|
if (!index.isValid() || index.row() < 0 || index.row() >= m_filteredGames.count()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Game *game = m_filteredGames.at(index.row());
|
|
|
|
|
|
|
|
|
|
switch (role) {
|
|
|
|
|
case FavoriteRole:
|
|
|
|
|
game->setFavorite(value.toBool());
|
|
|
|
|
break;
|
|
|
|
|
case HiddenRole:
|
|
|
|
|
game->setHidden(value.toBool());
|
|
|
|
|
break;
|
|
|
|
|
default:
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Q_EMIT dataChanged(index, index, {role});
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Qt::ItemFlags GameModel::flags(const QModelIndex &index) const
|
|
|
|
|
{
|
|
|
|
|
if (!index.isValid()) {
|
|
|
|
|
return Qt::NoItemFlags;
|
|
|
|
|
}
|
|
|
|
|
return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsEditable;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QHash<int, QByteArray> GameModel::roleNames() const
|
|
|
|
|
{
|
|
|
|
|
return {{IdRole, "gameId"},
|
|
|
|
|
{NameRole, "name"},
|
|
|
|
|
{DescriptionRole, "description"},
|
|
|
|
|
{DeveloperRole, "developer"},
|
|
|
|
|
{PublisherRole, "publisher"},
|
|
|
|
|
{CoverUrlRole, "coverUrl"},
|
|
|
|
|
{IconUrlRole, "iconUrl"},
|
|
|
|
|
{LaunchCommandRole, "launchCommand"},
|
|
|
|
|
{PlatformRole, "platform"},
|
|
|
|
|
{PlatformIdRole, "platformId"},
|
|
|
|
|
{LastPlayedRole, "lastPlayed"},
|
|
|
|
|
{PlayTimeRole, "playTime"},
|
|
|
|
|
{FavoriteRole, "favorite"},
|
|
|
|
|
{HiddenRole, "hidden"},
|
|
|
|
|
{InstalledRole, "installed"},
|
|
|
|
|
{RunningRole, "running"},
|
|
|
|
|
{GameObjectRole, "gameObject"}};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int GameModel::count() const
|
|
|
|
|
{
|
|
|
|
|
return m_filteredGames.count();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString GameModel::filterText() const
|
|
|
|
|
{
|
|
|
|
|
return m_filterText;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameModel::setFilterText(const QString &text)
|
|
|
|
|
{
|
|
|
|
|
if (m_filterText != text) {
|
|
|
|
|
m_filterText = text;
|
|
|
|
|
applyFilter();
|
|
|
|
|
Q_EMIT filterTextChanged();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QString GameModel::filterPlatform() const
|
|
|
|
|
{
|
|
|
|
|
return m_filterPlatform;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameModel::setFilterPlatform(const QString &platform)
|
|
|
|
|
{
|
|
|
|
|
if (m_filterPlatform != platform) {
|
|
|
|
|
m_filterPlatform = platform;
|
|
|
|
|
applyFilter();
|
|
|
|
|
Q_EMIT filterPlatformChanged();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GameModel::showHidden() const
|
|
|
|
|
{
|
|
|
|
|
return m_showHidden;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameModel::setShowHidden(bool show)
|
|
|
|
|
{
|
|
|
|
|
if (m_showHidden != show) {
|
|
|
|
|
m_showHidden = show;
|
|
|
|
|
applyFilter();
|
|
|
|
|
Q_EMIT showHiddenChanged();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool GameModel::favoritesOnly() const
|
|
|
|
|
{
|
|
|
|
|
return m_favoritesOnly;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameModel::setFavoritesOnly(bool favorites)
|
|
|
|
|
{
|
|
|
|
|
if (m_favoritesOnly != favorites) {
|
|
|
|
|
m_favoritesOnly = favorites;
|
|
|
|
|
applyFilter();
|
|
|
|
|
Q_EMIT favoritesOnlyChanged();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GameModel::SortMode GameModel::sortMode() const
|
|
|
|
|
{
|
|
|
|
|
return m_sortMode;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameModel::setSortMode(SortMode mode)
|
|
|
|
|
{
|
|
|
|
|
if (m_sortMode != mode) {
|
|
|
|
|
m_sortMode = mode;
|
|
|
|
|
applySort();
|
|
|
|
|
Q_EMIT sortModeChanged();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameModel::addGame(Game *game)
|
|
|
|
|
{
|
|
|
|
|
if (!game)
|
|
|
|
|
return;
|
|
|
|
|
|
|
|
|
|
// Check for duplicates
|
|
|
|
|
for (Game *existing : m_games) {
|
2026-01-19 23:13:11 +00:00
|
|
|
if (existing->id() == game->id()) {
|
2026-01-24 12:50:54 +00:00
|
|
|
if (!game->parent()) {
|
|
|
|
|
game->setParent(this);
|
|
|
|
|
}
|
|
|
|
|
game->deleteLater();
|
2026-01-18 12:13:07 +00:00
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
game->setParent(this);
|
|
|
|
|
|
2026-01-20 09:59:29 +00:00
|
|
|
connect(
|
|
|
|
|
game,
|
|
|
|
|
&Game::nameChanged,
|
|
|
|
|
this,
|
|
|
|
|
[this]() {
|
|
|
|
|
applyFilter();
|
|
|
|
|
},
|
|
|
|
|
Qt::QueuedConnection);
|
|
|
|
|
connect(
|
|
|
|
|
game,
|
|
|
|
|
&Game::developerChanged,
|
|
|
|
|
this,
|
|
|
|
|
[this]() {
|
|
|
|
|
applyFilter();
|
|
|
|
|
},
|
|
|
|
|
Qt::QueuedConnection);
|
|
|
|
|
connect(
|
|
|
|
|
game,
|
|
|
|
|
&Game::publisherChanged,
|
|
|
|
|
this,
|
|
|
|
|
[this]() {
|
|
|
|
|
applyFilter();
|
|
|
|
|
},
|
|
|
|
|
Qt::QueuedConnection);
|
|
|
|
|
connect(
|
|
|
|
|
game,
|
|
|
|
|
&Game::favoriteChanged,
|
|
|
|
|
this,
|
|
|
|
|
[this]() {
|
|
|
|
|
applyFilter();
|
|
|
|
|
},
|
|
|
|
|
Qt::QueuedConnection);
|
|
|
|
|
connect(
|
|
|
|
|
game,
|
|
|
|
|
&Game::hiddenChanged,
|
|
|
|
|
this,
|
|
|
|
|
[this]() {
|
|
|
|
|
applyFilter();
|
|
|
|
|
},
|
|
|
|
|
Qt::QueuedConnection);
|
|
|
|
|
connect(
|
|
|
|
|
game,
|
|
|
|
|
&Game::platformChanged,
|
|
|
|
|
this,
|
|
|
|
|
[this]() {
|
|
|
|
|
applyFilter();
|
|
|
|
|
},
|
|
|
|
|
Qt::QueuedConnection);
|
|
|
|
|
connect(
|
|
|
|
|
game,
|
|
|
|
|
&Game::lastPlayedChanged,
|
|
|
|
|
this,
|
|
|
|
|
[this]() {
|
|
|
|
|
applyFilter();
|
|
|
|
|
},
|
|
|
|
|
Qt::QueuedConnection);
|
|
|
|
|
connect(
|
|
|
|
|
game,
|
|
|
|
|
&Game::playTimeChanged,
|
|
|
|
|
this,
|
|
|
|
|
[this]() {
|
|
|
|
|
applyFilter();
|
|
|
|
|
},
|
|
|
|
|
Qt::QueuedConnection);
|
|
|
|
|
|
2026-01-18 12:13:07 +00:00
|
|
|
m_games.append(game);
|
|
|
|
|
applyFilter();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameModel::removeGame(const QString &id)
|
|
|
|
|
{
|
|
|
|
|
for (int i = 0; i < m_games.count(); ++i) {
|
|
|
|
|
if (m_games.at(i)->id() == id) {
|
|
|
|
|
int filteredIndex = m_filteredGames.indexOf(m_games.at(i));
|
|
|
|
|
if (filteredIndex >= 0) {
|
|
|
|
|
beginRemoveRows(QModelIndex(), filteredIndex, filteredIndex);
|
|
|
|
|
m_filteredGames.removeAt(filteredIndex);
|
|
|
|
|
endRemoveRows();
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 12:50:54 +00:00
|
|
|
Game *game = m_games.takeAt(i);
|
|
|
|
|
if (game) {
|
|
|
|
|
game->deleteLater();
|
|
|
|
|
}
|
2026-01-18 12:13:07 +00:00
|
|
|
Q_EMIT countChanged();
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Game *GameModel::gameAt(int index) const
|
|
|
|
|
{
|
|
|
|
|
if (index < 0 || index >= m_filteredGames.count()) {
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
return m_filteredGames.at(index);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Game *GameModel::gameById(const QString &id) const
|
|
|
|
|
{
|
|
|
|
|
for (Game *game : m_games) {
|
|
|
|
|
if (game->id() == id) {
|
|
|
|
|
return game;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nullptr;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameModel::clear()
|
|
|
|
|
{
|
|
|
|
|
beginResetModel();
|
2026-01-24 12:50:54 +00:00
|
|
|
for (Game *game : m_games) {
|
|
|
|
|
if (game) {
|
|
|
|
|
game->deleteLater();
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-01-18 12:13:07 +00:00
|
|
|
m_games.clear();
|
|
|
|
|
m_filteredGames.clear();
|
|
|
|
|
endResetModel();
|
|
|
|
|
Q_EMIT countChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
QStringList GameModel::platforms() const
|
|
|
|
|
{
|
|
|
|
|
QStringList result;
|
|
|
|
|
for (Game *game : m_games) {
|
|
|
|
|
if (!result.contains(game->platform())) {
|
|
|
|
|
result.append(game->platform());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
result.sort();
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-19 23:13:11 +00:00
|
|
|
QList<Game *> GameModel::allGames() const
|
|
|
|
|
{
|
|
|
|
|
return m_games;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-24 12:50:54 +00:00
|
|
|
bool GameModel::hasPlatformPrefix(const QString &platformPrefix) const
|
|
|
|
|
{
|
|
|
|
|
if (platformPrefix.isEmpty()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (Game *game : m_games) {
|
|
|
|
|
if (!game) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (game->platform().startsWith(platformPrefix)) {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int GameModel::removeByPlatformPrefix(const QString &platformPrefix)
|
|
|
|
|
{
|
|
|
|
|
if (platformPrefix.isEmpty()) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bool hasMatch = false;
|
|
|
|
|
for (Game *game : m_games) {
|
|
|
|
|
if (!game) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (game->platform().startsWith(platformPrefix)) {
|
|
|
|
|
hasMatch = true;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!hasMatch) {
|
|
|
|
|
return 0;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
beginResetModel();
|
|
|
|
|
|
|
|
|
|
int removed = 0;
|
|
|
|
|
for (int i = m_games.count() - 1; i >= 0; --i) {
|
|
|
|
|
Game *game = m_games.at(i);
|
|
|
|
|
if (!game) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (game->platform().startsWith(platformPrefix)) {
|
|
|
|
|
m_games.takeAt(i)->deleteLater();
|
|
|
|
|
removed++;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m_filteredGames.clear();
|
|
|
|
|
for (Game *game : m_games) {
|
|
|
|
|
if (matchesFilter(game)) {
|
|
|
|
|
m_filteredGames.append(game);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
applySort();
|
|
|
|
|
|
|
|
|
|
endResetModel();
|
|
|
|
|
|
|
|
|
|
if (removed > 0) {
|
|
|
|
|
Q_EMIT countChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return removed;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-18 12:13:07 +00:00
|
|
|
bool GameModel::matchesFilter(Game *game) const
|
|
|
|
|
{
|
|
|
|
|
if (!m_showHidden && game->hidden()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (m_favoritesOnly && !game->favorite()) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!m_filterPlatform.isEmpty() && game->platform() != m_filterPlatform) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!m_filterText.isEmpty()) {
|
|
|
|
|
bool matches = game->name().contains(m_filterText, Qt::CaseInsensitive) || game->developer().contains(m_filterText, Qt::CaseInsensitive)
|
|
|
|
|
|| game->publisher().contains(m_filterText, Qt::CaseInsensitive);
|
|
|
|
|
if (!matches) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameModel::applyFilter()
|
|
|
|
|
{
|
|
|
|
|
beginResetModel();
|
|
|
|
|
|
|
|
|
|
m_filteredGames.clear();
|
|
|
|
|
for (Game *game : m_games) {
|
|
|
|
|
if (matchesFilter(game)) {
|
|
|
|
|
m_filteredGames.append(game);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
applySort();
|
|
|
|
|
|
|
|
|
|
endResetModel();
|
|
|
|
|
Q_EMIT countChanged();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void GameModel::applySort()
|
|
|
|
|
{
|
|
|
|
|
std::sort(m_filteredGames.begin(), m_filteredGames.end(), [this](Game *a, Game *b) {
|
|
|
|
|
// Favorites always first
|
|
|
|
|
if (a->favorite() != b->favorite()) {
|
|
|
|
|
return a->favorite();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
switch (m_sortMode) {
|
|
|
|
|
case SortByName:
|
|
|
|
|
return a->name().compare(b->name(), Qt::CaseInsensitive) < 0;
|
|
|
|
|
case SortByLastPlayed:
|
|
|
|
|
return a->lastPlayed() > b->lastPlayed();
|
|
|
|
|
case SortByPlayTime:
|
|
|
|
|
return a->playTime() > b->playTime();
|
|
|
|
|
case SortByPlatform:
|
|
|
|
|
if (a->platform() != b->platform()) {
|
|
|
|
|
return a->platform() < b->platform();
|
|
|
|
|
}
|
|
|
|
|
return a->name().compare(b->name(), Qt::CaseInsensitive) < 0;
|
|
|
|
|
case SortByRecent:
|
|
|
|
|
return a->lastPlayed() > b->lastPlayed();
|
|
|
|
|
}
|
|
|
|
|
return false;
|
|
|
|
|
});
|
|
|
|
|
}
|