From 6d25c63f29103f53fc151de0f2dfa91fdb852093 Mon Sep 17 00:00:00 2001 From: aqua Date: Sun, 8 Jan 2023 13:22:41 +0200 Subject: Initial commit --- .clang-format | 10 +++++ .gitignore | 77 +++++++++++++++++++++++++++++++++ CMakeLists.txt | 32 ++++++++++++++ cmake/CompilerWarnings.cmake | 43 +++++++++++++++++++ playlist/CMakeLists.txt | 6 +++ playlist/playlist.cpp | 36 ++++++++++++++++ playlist/playlist.hpp | 39 +++++++++++++++++ playlist/playlistmodel.cpp | 46 ++++++++++++++++++++ playlist/playlistmodel.hpp | 28 ++++++++++++ src/fsview.cpp | 50 ++++++++++++++++++++++ src/fsview.hpp | 30 +++++++++++++ src/main.cpp | 15 +++++++ src/maindialog.cpp | 44 +++++++++++++++++++ src/maindialog.hpp | 29 +++++++++++++ src/maindialog.ui | 100 +++++++++++++++++++++++++++++++++++++++++++ 15 files changed, 585 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 cmake/CompilerWarnings.cmake create mode 100644 playlist/CMakeLists.txt create mode 100644 playlist/playlist.cpp create mode 100644 playlist/playlist.hpp create mode 100644 playlist/playlistmodel.cpp create mode 100644 playlist/playlistmodel.hpp create mode 100644 src/fsview.cpp create mode 100644 src/fsview.hpp create mode 100644 src/main.cpp create mode 100644 src/maindialog.cpp create mode 100644 src/maindialog.hpp create mode 100644 src/maindialog.ui diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..c033b36 --- /dev/null +++ b/.clang-format @@ -0,0 +1,10 @@ +--- +BasedOnStyle: LLVM +AllowShortIfStatementsOnASingleLine: Always +AllowShortBlocksOnASingleLine: 'true' +AllowShortLoopsOnASingleLine: 'true' +BreakBeforeBraces: Stroustrup +ColumnLimit: '120' +UseTab: Never + +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b9ac12 --- /dev/null +++ b/.gitignore @@ -0,0 +1,77 @@ +# This file is used to ignore files which are generated +# ---------------------------------------------------------------------------- + +*~ +*.autosave +*.a +*.core +*.moc +*.o +*.obj +*.orig +*.rej +*.so +*.so.* +*_pch.h.cpp +*_resource.rc +*.qm +.#* +*.*# +core +!core/ +tags +.DS_Store +.directory +*.debug +Makefile* +*.prl +*.app +moc_*.cpp +ui_*.h +qrc_*.cpp +Thumbs.db +*.res +*.rc +/.qmake.cache +/.qmake.stash + +# build directories +build-*/ + +# qtcreator generated files +*.pro.user* +CMakeLists.txt.user* + +# xemacs temporary files +*.flc + +# Vim temporary files +.*.swp + +# Visual Studio generated files +*.ib_pdb_index +*.idb +*.ilk +*.pdb +*.sln +*.suo +*.vcproj +*vcproj.*.*.user +*.ncb +*.sdf +*.opensdf +*.vcxproj +*vcxproj.* + +# MinGW generated files +*.Debug +*.Release + +# Python byte code +*.pyc + +# Binaries +# -------- +*.dll +*.exe + diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..ddfcc98 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,32 @@ +cmake_minimum_required(VERSION 3.5) + +project(nyamp VERSION 0.1 LANGUAGES CXX) + +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +include(cmake/CompilerWarnings.cmake) + +find_package(Qt6 REQUIRED COMPONENTS Widgets MultimediaWidgets) +set(CMAKE_AUTOUIC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) + +add_subdirectory(playlist) + +set(PROJECT_SOURCES + src/main.cpp + src/maindialog.cpp src/maindialog.hpp src/maindialog.ui + src/fsview.hpp src/fsview.cpp +) + +add_executable(nyamp ${PROJECT_SOURCES}) +target_link_libraries(nyamp PRIVATE + Qt6::Widgets Qt6::MultimediaWidgets + playlist +) + +install(TARGETS nyamp + BUNDLE DESTINATION . + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) diff --git a/cmake/CompilerWarnings.cmake b/cmake/CompilerWarnings.cmake new file mode 100644 index 0000000..895a7e8 --- /dev/null +++ b/cmake/CompilerWarnings.cmake @@ -0,0 +1,43 @@ +# compiler warnings based on: https://lefticus.gitbooks.io/cpp-best-practices/content/02-Use_the_Tools_Available.html + +# only enable warnings for debug builds +if (NOT ${CMAKE_BUILD_TYPE} STREQUAL Debug) + return() +endif () + +set(BASE_WARNINGS + -Wall -Wextra # reasonable and standard + -Wpedantic # warn if non-standard c++ is used + -Werror=shadow # warn the user if a variable shadows one from a parent context + -Wunused # warn on anything being unused + + -Werror=non-virtual-dtor # warn the user if a class with virtual functions has a non-virtual dtor + -Werror=overloaded-virtual # warn if you overload (not override) a virtual function + + -Werror=old-style-cast # warn for c-style casts + -Wcast-align # warn for potential performance problem casts + + -Wconversion # warn on type conversions that may lose data + -Wsign-conversion # warn on sign conversions + -Wdouble-promotion # warn if float is implicit promoted to double + + -Wnull-dereference # warn if a null dereference is detected + -Wformat=2 # warn on security issues around functions that format output (ie printf) +) + +set(GCC_WARNINGS + -Wmisleading-indentation # (only in GCC >= 6.0) warn if indentation implies blocks where blocks do not exist + -Wduplicated-cond # (only in GCC >= 6.0) warn if if / else chain has duplicated conditions + -Wduplicated-branches # (only in GCC >= 7.0) warn if if / else branches have duplicated code + -Wlogical-op # (only in GCC) warn about logical operations being used where bitwise were probably wanted + -Wnull-dereference # (only in GCC >= 6.0) warn if a null dereference is detected + -Wuseless-cast # (only in GCC >= 4.8) warn if you perform a cast to the same type +) + +if (${CMAKE_CXX_COMPILER_ID} MATCHES Clang) + message(STATUS "Adding more ${CMAKE_CXX_COMPILER_ID} compiler warnings") + add_compile_options(${BASE_WARNINGS}) +elseif (${CMAKE_CXX_COMPILER_ID} MATCHES GNU) + message(STATUS "Adding more ${CMAKE_CXX_COMPILER_ID} compiler warnings") + add_compile_options(${BASE_WARNINGS} ${GCC_WARNINGS}) +endif () diff --git a/playlist/CMakeLists.txt b/playlist/CMakeLists.txt new file mode 100644 index 0000000..54b397b --- /dev/null +++ b/playlist/CMakeLists.txt @@ -0,0 +1,6 @@ +add_library(playlist STATIC + playlist.cpp playlist.hpp + playlistmodel.cpp playlistmodel.hpp +) +target_link_libraries(playlist PUBLIC Qt6::Core) +target_include_directories(playlist INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/playlist/playlist.cpp b/playlist/playlist.cpp new file mode 100644 index 0000000..778fae1 --- /dev/null +++ b/playlist/playlist.cpp @@ -0,0 +1,36 @@ +/* ============================================================ + * SPDX-License-Identifier: GPL-3.0-only + * Copyright (C) 2023 aqua + * ============================================================ */ + +#include "playlist.hpp" +#include "playlistmodel.hpp" +#include +#include + +Playlist::Playlist(QObject *parent) : QObject{parent}, m_model(new PlaylistModel(this)) {} + +void Playlist::appendUrl(const QUrl &url) +{ + const QFileInfo info(url.toLocalFile()); + if (!info.exists()) return; // if file doesn't exist, return + + if (info.isFile()) { + emit mediaAboutToBeInserted(m_urls.count(), m_urls.count() + 1); + m_urls.append(url.toLocalFile()); + emit mediaInserted(); + } + else if (info.isFile()) { + QDir dir(info.absoluteFilePath()); + const auto list = dir.entryInfoList(QDir::Files); + emit mediaAboutToBeInserted(m_urls.count(), m_urls.count() + list.count()); + for (const auto &i : list) { m_urls.append(i.absoluteFilePath()); } + emit mediaInserted(); + } +} + +QUrl Playlist::operator[](const int index) const +{ + if (index < m_urls.count()) return m_urls.at(index); + return {}; +} diff --git a/playlist/playlist.hpp b/playlist/playlist.hpp new file mode 100644 index 0000000..4c9e385 --- /dev/null +++ b/playlist/playlist.hpp @@ -0,0 +1,39 @@ +/* ============================================================ + * SPDX-License-Identifier: GPL-3.0-only + * Copyright (C) 2023 aqua + * ============================================================ */ + +#pragma once + +#include +#include + +class PlaylistModel; +class Playlist : public QObject { + Q_OBJECT +public: + explicit Playlist(QObject *parent = nullptr); + +public slots: + void appendUrl(const QUrl &url); + +public: + void appendList(const QList &urllist) + { + for (const auto &url : urllist) appendUrl(url); + } + + [[nodiscard]] QUrl operator[](const int index) const; + [[nodiscard]] auto count() const { return m_urls.count(); } + [[nodiscard]] auto model() { return m_model; } + +signals: + void mediaAboutToBeInserted(int start, int end); + void mediaInserted(); + void mediaAboutToBeRemoved(int start, int end); + void mediaRemoved(); + +private: + QVector m_urls; + PlaylistModel *m_model; +}; diff --git a/playlist/playlistmodel.cpp b/playlist/playlistmodel.cpp new file mode 100644 index 0000000..9eb2629 --- /dev/null +++ b/playlist/playlistmodel.cpp @@ -0,0 +1,46 @@ +/* ============================================================ + * SPDX-License-Identifier: GPL-3.0-only + * Copyright (C) 2023 aqua + * ============================================================ */ + +#include "playlistmodel.hpp" +#include "playlist.hpp" + +PlaylistModel::PlaylistModel(Playlist *parent) : QAbstractItemModel{parent}, playlist(parent) +{ + Q_CHECK_PTR(parent); + connect(parent, &Playlist::mediaAboutToBeInserted, this, &PlaylistModel::beginInsertItems); + connect(parent, &Playlist::mediaInserted, this, &PlaylistModel::endInsertItems); +} + +QModelIndex PlaylistModel::index(int row, int column, const QModelIndex &parent) const +{ + if (parent.isValid()) return {}; + return createIndex(row, column); +} + +QModelIndex PlaylistModel::parent(const QModelIndex &child) const { return {}; } + +int PlaylistModel::rowCount(const QModelIndex &parent) const +{ + if (parent.isValid()) return 0; + return playlist->count(); +} + +int PlaylistModel::columnCount(const QModelIndex &parent) const +{ + if (parent.isValid()) return 0; + return 1; +} + +QVariant PlaylistModel::data(const QModelIndex &index, int role) const +{ + if (!index.isValid()) return {}; + + switch (role) { + case Qt::DisplayRole: + return (*playlist)[index.row()]; + default: + return {}; + } +} diff --git a/playlist/playlistmodel.hpp b/playlist/playlistmodel.hpp new file mode 100644 index 0000000..e5cedfe --- /dev/null +++ b/playlist/playlistmodel.hpp @@ -0,0 +1,28 @@ +/* ============================================================ + * SPDX-License-Identifier: GPL-3.0-only + * Copyright (C) 2023 aqua + * ============================================================ */ + +#pragma once + +#include + +class Playlist; +class PlaylistModel : public QAbstractItemModel { +public: + explicit PlaylistModel(Playlist *parent); + ~PlaylistModel() = default; + + QModelIndex index(int row, int column, const QModelIndex &parent) const; + QModelIndex parent(const QModelIndex &child) const; + int rowCount(const QModelIndex &parent) const; + int columnCount(const QModelIndex &parent) const; + QVariant data(const QModelIndex &index, int role) const; + +private slots: + void beginInsertItems(int start, int end) { beginInsertRows(QModelIndex(), start, end); } + void endInsertItems() { endInsertRows(); } + +private: + Playlist *playlist; +}; diff --git a/src/fsview.cpp b/src/fsview.cpp new file mode 100644 index 0000000..3e9495e --- /dev/null +++ b/src/fsview.cpp @@ -0,0 +1,50 @@ +/* ============================================================ + * SPDX-License-Identifier: GPL-3.0-only + * Copyright (C) 2023 aqua + * ============================================================ */ + +#include "fsview.hpp" +#include +#include + +FSView::FSView(QWidget *parent) : QTreeView(parent), model{new QFileSystemModel} +{ + setModel(model); + setRootPath(QDir::homePath()); + + connect(this, &QWidget::customContextMenuRequested, this, &FSView::contextMenu); + setContextMenuPolicy(Qt::CustomContextMenu); +} + +FSView::~FSView() { delete model; } + +void FSView::setRootPath(const QString &path) +{ + model->setRootPath(path); + setRootIndex(model->index(path)); +} + +void FSView::contextMenu(const QPoint &pos) +{ + const auto idx = indexAt(pos); + if (idx.isValid()) { + QMenu menu; + + if (model->isDir(idx)) { + menu.addAction(tr("Set as root"), this, [this, idx]() { + const auto path = model->filePath(idx); + qDebug() << "set root path" << path; + model->setRootPath(path); + setRootIndex(idx); + }); + } + else { + menu.addAction(tr("Add to Playlist"), this, [this, idx]() { + const auto url = QUrl::fromLocalFile(model->filePath(idx)); + emit addLocalFile(url); + }); + } + + menu.exec(mapToGlobal(pos)); + } +} diff --git a/src/fsview.hpp b/src/fsview.hpp new file mode 100644 index 0000000..eea61c7 --- /dev/null +++ b/src/fsview.hpp @@ -0,0 +1,30 @@ +/* ============================================================ + * SPDX-License-Identifier: GPL-3.0-only + * Copyright (C) 2023 aqua + * ============================================================ */ + +#pragma once + +#include +#include + +class QFileSystemModel; +class FSView : public QTreeView { + Q_OBJECT + +public: + FSView(QWidget *parent = nullptr); + ~FSView(); + +signals: + void addLocalFile(const QUrl &url); + +public slots: + void setRootPath(const QString &path); + +private slots: + void contextMenu(const QPoint &pos); + +private: + QFileSystemModel *model; +}; diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..1fedca7 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,15 @@ +/* ============================================================ + * SPDX-License-Identifier: GPL-3.0-only + * Copyright (C) 2023 aqua + * ============================================================ */ + +#include "maindialog.hpp" +#include + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainDialog w; + w.show(); + return a.exec(); +} diff --git a/src/maindialog.cpp b/src/maindialog.cpp new file mode 100644 index 0000000..ee6fdd7 --- /dev/null +++ b/src/maindialog.cpp @@ -0,0 +1,44 @@ +/* ============================================================ + * SPDX-License-Identifier: GPL-3.0-only + * Copyright (C) 2023 aqua + * ============================================================ */ + +#include "maindialog.hpp" +#include "playlist.hpp" +#include "playlistmodel.hpp" +#include "ui_maindialog.h" +#include +#include +#include + +MainDialog::MainDialog(QWidget *parent) + : QDialog(parent), ui(new Ui::MainDialog), + m_playlist(new Playlist(this)), m_player{new QMediaPlayer(this)}, m_audioSink{new QAudioOutput(this)} +{ + m_player->setAudioOutput(m_audioSink); + m_audioSink->setVolume(50); + + ui->setupUi(this); + ui->playlist->setModel(m_playlist->model()); + + connect(ui->fsSetRoot, &QPushButton::clicked, this, [this]() { + const auto root = QFileDialog::getExistingDirectory(this, tr("Set Root"), QDir::homePath()); + if (!root.isEmpty()) ui->fsView->setRootPath(root); + }); + + connect(m_player, &QMediaPlayer::durationChanged, ui->progress, &QSlider::setMaximum); + connect(m_player, &QMediaPlayer::positionChanged, ui->progress, &QSlider::setValue); + + connect(ui->fsView, &FSView::addLocalFile, m_playlist, &Playlist::appendUrl); + connect(ui->open, &QPushButton::clicked, this, [this]() { + const auto filelist = QFileDialog::getOpenFileUrls(this, tr("Open Files")); + m_playlist->appendList(filelist); + }); + connect(ui->playlist, &QListView::activated, this, [this](const QModelIndex &index) { + const auto url = m_playlist->model()->data(index, Qt::DisplayRole).toUrl(); + m_player->setSource(url); + m_player->play(); + }); +} + +MainDialog::~MainDialog() { delete ui; } diff --git a/src/maindialog.hpp b/src/maindialog.hpp new file mode 100644 index 0000000..db22b2e --- /dev/null +++ b/src/maindialog.hpp @@ -0,0 +1,29 @@ +/* ============================================================ + * SPDX-License-Identifier: GPL-3.0-only + * Copyright (C) 2023 aqua + * ============================================================ */ + +#pragma once + +#include + +namespace Ui { +class MainDialog; +} +class Playlist; +class QMediaPlayer; +class QAudioOutput; +class MainDialog : public QDialog { + Q_OBJECT + +public: + MainDialog(QWidget *parent = nullptr); + ~MainDialog(); + +private: + Ui::MainDialog *ui; + Playlist *m_playlist; + + QMediaPlayer *m_player; + QAudioOutput *m_audioSink; +}; diff --git a/src/maindialog.ui b/src/maindialog.ui new file mode 100644 index 0000000..c2cb900 --- /dev/null +++ b/src/maindialog.ui @@ -0,0 +1,100 @@ + + + MainDialog + + + + 0 + 0 + 800 + 600 + + + + MainDialog + + + + + + + + 1 + + + + Tab 1 + + + + + Tab 2 + + + + + + Set Root + + + + + + + + + + + + + + + + + + + + + Qt::Horizontal + + + + + + + + + + + Open + + + + + + + Play + + + + + + + Stop + + + + + + + + + + FSView + QTreeView +
src/fsview.hpp
+
+
+ + +
-- cgit v1.2.1