Skip to content

Commit

Permalink
widget: same-folder playlist support
Browse files Browse the repository at this point in the history
  • Loading branch information
BLumia committed Jul 21, 2024
1 parent 22f7ebf commit 03cff16
Show file tree
Hide file tree
Showing 9 changed files with 458 additions and 15 deletions.
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
cmake_minimum_required(VERSION 3.8)

project(pineapple-tracker-player VERSION 0.1 LANGUAGES CXX)
project(pineapple-tracker-player VERSION 0.2 LANGUAGES CXX)

add_subdirectory(player)
add_subdirectory(widget)
Expand Down
1 change: 1 addition & 0 deletions player/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pkg_check_modules(PortAudio REQUIRED portaudiocpp IMPORTED_TARGET)
set(PROJECT_SOURCES
player.h player.cpp
util.h util.cpp
playlistmanager.h playlistmanager.cpp
)

qt_add_library(player-shared OBJECT
Expand Down
253 changes: 253 additions & 0 deletions player/playlistmanager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,253 @@
// SPDX-FileCopyrightText: 2024 Gary Wang <[email protected]>
//
// SPDX-License-Identifier: MIT

#include "playlistmanager.h"

#include <QCollator>
#include <QDir>
#include <QFileInfo>
#include <QUrl>

PlaylistModel::PlaylistModel(QObject *parent)
: QAbstractListModel(parent)
{

}

PlaylistModel::~PlaylistModel()
{

}

void PlaylistModel::setPlaylist(const QList<QUrl> &urls)
{
beginResetModel();
m_playlist = urls;
endResetModel();
}

QModelIndex PlaylistModel::loadPlaylist(const QList<QUrl> & urls)
{
if (urls.isEmpty()) return QModelIndex();
if (urls.count() == 1) {
return loadPlaylist(urls.constFirst());
} else {
setPlaylist(urls);
return createIndex(0);
}
}

QModelIndex PlaylistModel::loadPlaylist(const QUrl &url)
{
QFileInfo info(url.toLocalFile());
QDir dir(info.path());
QString && currentFileName = info.fileName();

if (dir.path() == m_currentDir) {
int index = indexOf(url);
return index == -1 ? appendToPlaylist(url) : createIndex(index);
}

QStringList entryList = dir.entryList(
m_autoLoadSuffixes,
QDir::Files | QDir::NoSymLinks, QDir::NoSort);

QCollator collator;
collator.setNumericMode(true);

std::sort(entryList.begin(), entryList.end(), collator);

QList<QUrl> playlist;

int index = -1;
for (int i = 0; i < entryList.count(); i++) {
const QString & fileName = entryList.at(i);
const QString & oneEntry = dir.absoluteFilePath(fileName);
const QUrl & url = QUrl::fromLocalFile(oneEntry);
playlist.append(url);
if (fileName == currentFileName) {
index = i;
}
}
if (index == -1) {
index = playlist.count();
playlist.append(url);
}
m_currentDir = dir.path();

setPlaylist(playlist);

return createIndex(index);
}

QModelIndex PlaylistModel::appendToPlaylist(const QUrl &url)
{
const int lastIndex = rowCount();
beginInsertRows(QModelIndex(), lastIndex, lastIndex);
m_playlist.append(url);
endInsertRows();
return createIndex(lastIndex);
}

bool PlaylistModel::removeAt(int index)
{
if (index < 0 || index >= rowCount()) return false;
beginRemoveRows(QModelIndex(), index, index);
m_playlist.removeAt(index);
endRemoveRows();
return true;
}

int PlaylistModel::indexOf(const QUrl &url) const
{
return m_playlist.indexOf(url);
}

QUrl PlaylistModel::urlByIndex(int index) const
{
return m_playlist.value(index);
}

QStringList PlaylistModel::autoLoadFilterSuffixes() const
{
return m_autoLoadSuffixes;
}

QModelIndex PlaylistModel::createIndex(int row) const
{
return QAbstractItemModel::createIndex(row, 0, nullptr);
}

int PlaylistModel::rowCount(const QModelIndex &parent) const
{
return m_playlist.count();
}

QVariant PlaylistModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid()) return QVariant();

switch (role) {
case Qt::DisplayRole:
return m_playlist.at(index.row()).fileName();
case UrlRole:
return m_playlist.at(index.row());
}

return QVariant();
}

PlaylistManager::PlaylistManager(QObject *parent)
: QObject(parent)
{
connect(&m_model, &PlaylistModel::rowsRemoved, this,
[this](const QModelIndex &, int, int) {
if (m_model.rowCount() <= m_currentIndex) {
setProperty("currentIndex", m_currentIndex - 1);
}
});

auto onRowCountChanged = [this](){
emit totalCountChanged(m_model.rowCount());
};

connect(&m_model, &PlaylistModel::rowsInserted, this, onRowCountChanged);
connect(&m_model, &PlaylistModel::rowsRemoved, this, onRowCountChanged);
connect(&m_model, &PlaylistModel::modelReset, this, onRowCountChanged);
}

PlaylistManager::~PlaylistManager()
{

}

PlaylistModel *PlaylistManager::model()
{
return &m_model;
}

void PlaylistManager::setPlaylist(const QList<QUrl> &urls)
{
m_model.setPlaylist(urls);
}

QModelIndex PlaylistManager::loadPlaylist(const QList<QUrl> &urls)
{
QModelIndex idx = m_model.loadPlaylist(urls);
setProperty("currentIndex", idx.row());
return idx;
}

QModelIndex PlaylistManager::loadPlaylist(const QUrl &url)
{
QModelIndex idx = m_model.loadPlaylist(url);
setProperty("currentIndex", idx.row());
return idx;
}

int PlaylistManager::totalCount() const
{
return m_model.rowCount();
}

QModelIndex PlaylistManager::previousIndex() const
{
int count = totalCount();
if (count == 0) return QModelIndex();

return m_model.createIndex(m_currentIndex - 1 < 0 ? count - 1 : m_currentIndex - 1);
}

QModelIndex PlaylistManager::nextIndex() const
{
int count = totalCount();
if (count == 0) return QModelIndex();

return m_model.createIndex(m_currentIndex + 1 == count ? 0 : m_currentIndex + 1);
}

QModelIndex PlaylistManager::curIndex() const
{
return m_model.createIndex(m_currentIndex);
}

void PlaylistManager::setCurrentIndex(const QModelIndex &index)
{
if (index.isValid() && index.row() >= 0 && index.row() < totalCount()) {
setProperty("currentIndex", index.row());
}
}

QUrl PlaylistManager::urlByIndex(const QModelIndex &index)
{
return m_model.urlByIndex(index.row());
}

QString PlaylistManager::localFileByIndex(const QModelIndex &index)
{
return urlByIndex(index).toLocalFile();
}

bool PlaylistManager::removeAt(const QModelIndex &index)
{
return m_model.removeAt(index.row());
}

void PlaylistManager::setAutoLoadFilterSuffixes(const QStringList &nameFilters)
{
m_model.setProperty("autoLoadFilterSuffixes", nameFilters);
}

QList<QUrl> PlaylistManager::convertToUrlList(const QStringList &files)
{
QList<QUrl> urlList;
for (const QString & str : std::as_const(files)) {
QUrl url = QUrl::fromLocalFile(str);
if (url.isValid()) {
urlList.append(url);
}
}

return urlList;
}
82 changes: 82 additions & 0 deletions player/playlistmanager.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// SPDX-FileCopyrightText: 2024 Gary Wang <[email protected]>
//
// SPDX-License-Identifier: MIT

#pragma once

#include <QAbstractListModel>

class PlaylistModel : public QAbstractListModel
{
Q_OBJECT
public:
enum PlaylistRole {
UrlRole = Qt::UserRole
};
Q_ENUM(PlaylistRole)
Q_PROPERTY(QStringList autoLoadFilterSuffixes MEMBER m_autoLoadSuffixes NOTIFY autoLoadFilterSuffixesChanged)

explicit PlaylistModel(QObject *parent = nullptr);
~PlaylistModel();

void setPlaylist(const QList<QUrl> & urls);
QModelIndex loadPlaylist(const QList<QUrl> & urls);
QModelIndex loadPlaylist(const QUrl & url);
QModelIndex appendToPlaylist(const QUrl & url);
bool removeAt(int index);
int indexOf(const QUrl & url) const;
QUrl urlByIndex(int index) const;
QStringList autoLoadFilterSuffixes() const;

QModelIndex createIndex(int row) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;

signals:
void autoLoadFilterSuffixesChanged(QStringList suffixes);

private:
// model data
QList<QUrl> m_playlist;
// properties
QStringList m_autoLoadSuffixes = {};
// internal
QString m_currentDir;
};

class PlaylistManager : public QObject
{
Q_OBJECT
public:
Q_PROPERTY(int currentIndex MEMBER m_currentIndex NOTIFY currentIndexChanged)

explicit PlaylistManager(QObject *parent = nullptr);
~PlaylistManager();

PlaylistModel * model();

void setPlaylist(const QList<QUrl> & url);
QModelIndex loadPlaylist(const QList<QUrl> & urls);
QModelIndex loadPlaylist(const QUrl & url);

int totalCount() const;
QModelIndex previousIndex() const;
QModelIndex nextIndex() const;
QModelIndex curIndex() const;
void setCurrentIndex(const QModelIndex & index);
QUrl urlByIndex(const QModelIndex & index);
QString localFileByIndex(const QModelIndex & index);
bool removeAt(const QModelIndex & index);

void setAutoLoadFilterSuffixes(const QStringList &nameFilters);

static QList<QUrl> convertToUrlList(const QStringList & files);

signals:
void currentIndexChanged(int index);
void totalCountChanged(int count);

private:
int m_currentIndex;
PlaylistModel m_model;
};
4 changes: 3 additions & 1 deletion player/util.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
#include "util.h"

#include <utility>

#include <QUrl>
#include <QFile>
#include <QFontDatabase>

QList<QUrl> Util::convertToUrlList(const QStringList &files)
{
QList<QUrl> urlList;
for (const QString & str : qAsConst(files)) {
for (const QString & str : std::as_const(files)) {
if (!QFile::exists(str)) continue;
QUrl url = QUrl::fromLocalFile(str);
if (url.isValid()) {
Expand Down
4 changes: 4 additions & 0 deletions widget/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ qt_add_executable(pineapple-tracker-player
${PROJECT_SOURCES}
)

target_compile_definitions(pineapple-tracker-player PRIVATE
PTPLAY_VERSION_STRING="${CMAKE_PROJECT_VERSION}"
)

if(ECM_FOUND)
ecm_add_app_icon(pineapple-tracker-player
ICONS
Expand Down
Loading

0 comments on commit 03cff16

Please sign in to comment.