summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authoraqua <aqua@iserlohn-fortress.net>2023-01-08 13:22:41 +0200
committeraqua <aqua@iserlohn-fortress.net>2023-01-08 13:22:41 +0200
commit6d25c63f29103f53fc151de0f2dfa91fdb852093 (patch)
treef5fb9e8d4fbc854377d82e69a8c88d0814149b13
downloadnyamp-master.tar.xz
Initial commitHEADmaster
-rw-r--r--.clang-format10
-rw-r--r--.gitignore77
-rw-r--r--CMakeLists.txt32
-rw-r--r--cmake/CompilerWarnings.cmake43
-rw-r--r--playlist/CMakeLists.txt6
-rw-r--r--playlist/playlist.cpp36
-rw-r--r--playlist/playlist.hpp39
-rw-r--r--playlist/playlistmodel.cpp46
-rw-r--r--playlist/playlistmodel.hpp28
-rw-r--r--src/fsview.cpp50
-rw-r--r--src/fsview.hpp30
-rw-r--r--src/main.cpp15
-rw-r--r--src/maindialog.cpp44
-rw-r--r--src/maindialog.hpp29
-rw-r--r--src/maindialog.ui100
15 files changed, 585 insertions, 0 deletions
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 <aqua@iserlohn-fortress.net>
+ * ============================================================ */
+
+#include "playlist.hpp"
+#include "playlistmodel.hpp"
+#include <QDir>
+#include <QFileInfo>
+
+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 <aqua@iserlohn-fortress.net>
+ * ============================================================ */
+
+#pragma once
+
+#include <QObject>
+#include <QUrl>
+
+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<QUrl> &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<QUrl> 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 <aqua@iserlohn-fortress.net>
+ * ============================================================ */
+
+#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 <aqua@iserlohn-fortress.net>
+ * ============================================================ */
+
+#pragma once
+
+#include <QAbstractItemModel>
+
+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 <aqua@iserlohn-fortress.net>
+ * ============================================================ */
+
+#include "fsview.hpp"
+#include <QFileSystemModel>
+#include <QMenu>
+
+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 <aqua@iserlohn-fortress.net>
+ * ============================================================ */
+
+#pragma once
+
+#include <QTreeView>
+#include <QUrl>
+
+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 <aqua@iserlohn-fortress.net>
+ * ============================================================ */
+
+#include "maindialog.hpp"
+#include <QApplication>
+
+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 <aqua@iserlohn-fortress.net>
+ * ============================================================ */
+
+#include "maindialog.hpp"
+#include "playlist.hpp"
+#include "playlistmodel.hpp"
+#include "ui_maindialog.h"
+#include <QAudioOutput>
+#include <QFileDialog>
+#include <QMediaPlayer>
+
+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 <aqua@iserlohn-fortress.net>
+ * ============================================================ */
+
+#pragma once
+
+#include <QDialog>
+
+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 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>MainDialog</class>
+ <widget class="QDialog" name="MainDialog">
+ <property name="geometry">
+ <rect>
+ <x>0</x>
+ <y>0</y>
+ <width>800</width>
+ <height>600</height>
+ </rect>
+ </property>
+ <property name="windowTitle">
+ <string>MainDialog</string>
+ </property>
+ <layout class="QVBoxLayout" name="verticalLayout">
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_2">
+ <item>
+ <widget class="QTabWidget" name="tabWidget">
+ <property name="currentIndex">
+ <number>1</number>
+ </property>
+ <widget class="QWidget" name="tab">
+ <attribute name="title">
+ <string>Tab 1</string>
+ </attribute>
+ </widget>
+ <widget class="QWidget" name="tab_2">
+ <attribute name="title">
+ <string>Tab 2</string>
+ </attribute>
+ <layout class="QVBoxLayout" name="verticalLayout_2">
+ <item>
+ <widget class="QPushButton" name="fsSetRoot">
+ <property name="text">
+ <string>Set Root</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="FSView" name="fsView"/>
+ </item>
+ </layout>
+ </widget>
+ </widget>
+ </item>
+ <item>
+ <widget class="QListView" name="playlist"/>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout_3">
+ <item>
+ <widget class="QSlider" name="progress">
+ <property name="orientation">
+ <enum>Qt::Horizontal</enum>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ <item>
+ <layout class="QHBoxLayout" name="horizontalLayout">
+ <item>
+ <widget class="QPushButton" name="open">
+ <property name="text">
+ <string>Open</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="play">
+ <property name="text">
+ <string>Play</string>
+ </property>
+ </widget>
+ </item>
+ <item>
+ <widget class="QPushButton" name="stop">
+ <property name="text">
+ <string>Stop</string>
+ </property>
+ </widget>
+ </item>
+ </layout>
+ </item>
+ </layout>
+ </widget>
+ <customwidgets>
+ <customwidget>
+ <class>FSView</class>
+ <extends>QTreeView</extends>
+ <header>src/fsview.hpp</header>
+ </customwidget>
+ </customwidgets>
+ <resources/>
+ <connections/>
+</ui>