From a2031c8b31d7f9de7e07781001a376dee25d6c5f Mon Sep 17 00:00:00 2001 From: Hans Dijkema Date: Tue, 29 Jul 2025 23:51:21 +0200 Subject: [PATCH 1/9] Initial import of filesystem view, that enables a music library to be added to audacious. --- configure.ac | 5 +- src/filesystem-qt/Makefile | 13 + src/filesystem-qt/filesystem-qt.cc | 563 +++++++++++++++++++++++++++++ src/filesystem-qt/meson.build | 7 + 4 files changed, 586 insertions(+), 2 deletions(-) create mode 100644 src/filesystem-qt/Makefile create mode 100644 src/filesystem-qt/filesystem-qt.cc create mode 100644 src/filesystem-qt/meson.build diff --git a/configure.ac b/configure.ac index 41a77591f..71f84681c 100644 --- a/configure.ac +++ b/configure.ac @@ -83,13 +83,13 @@ TRANSPORT_PLUGINS="gio" if test "x$USE_GTK" = "xyes" ; then EFFECT_PLUGINS="$EFFECT_PLUGINS ladspa" - GENERAL_PLUGINS="$GENERAL_PLUGINS albumart lyrics-gtk playlist-manager search-tool statusicon" + GENERAL_PLUGINS="$GENERAL_PLUGINS albumart lyrics-gtk playlist-manager fsearch-tool statusicon" GENERAL_PLUGINS="$GENERAL_PLUGINS gtkui skins" VISUALIZATION_PLUGINS="$VISUALIZATION_PLUGINS blur_scope cairo-spectrum vumeter" fi if test "x$USE_QT" = "xyes" ; then - GENERAL_PLUGINS="$GENERAL_PLUGINS albumart-qt lyrics-qt playback-history-qt playlist-manager-qt search-tool-qt song-info-qt statusicon-qt" + GENERAL_PLUGINS="$GENERAL_PLUGINS albumart-qt lyrics-qt playback-history-qt playlist-manager-qt filesystem-qt search-tool-qt song-info-qt statusicon-qt" GENERAL_PLUGINS="$GENERAL_PLUGINS qtui skins-qt" VISUALIZATION_PLUGINS="$VISUALIZATION_PLUGINS blur_scope-qt qt-spectrum vumeter-qt" fi @@ -896,6 +896,7 @@ if test "x$USE_QT" = "xyes" ; then echo " OpenGL Spectrum Analyzer: $have_qtglspectrum" echo " Playback History: yes" echo " Playlist Manager: yes" + echo " Filesystem View: yes" echo " Search Tool: yes" echo " Song Info: yes" echo " Spectrum Analyzer (2D): yes" diff --git a/src/filesystem-qt/Makefile b/src/filesystem-qt/Makefile new file mode 100644 index 000000000..142ea9cbb --- /dev/null +++ b/src/filesystem-qt/Makefile @@ -0,0 +1,13 @@ +PLUGIN = filesystem-qt${PLUGIN_SUFFIX} + +SRCS = filesystem-qt.cc + +include ../../buildsys.mk +include ../../extra.mk + +plugindir := ${plugindir}/${GENERAL_PLUGIN_DIR} + +LD = ${CXX} +CPPFLAGS += -I../.. ${QT_CFLAGS} +CFLAGS += ${PLUGIN_CFLAGS} +LIBS += ${QT_LIBS} -laudqt diff --git a/src/filesystem-qt/filesystem-qt.cc b/src/filesystem-qt/filesystem-qt.cc new file mode 100644 index 000000000..a359bcd50 --- /dev/null +++ b/src/filesystem-qt/filesystem-qt.cc @@ -0,0 +1,563 @@ +/* + * playlist-manager-qt.cc + * Copyright 2015 John Lindgren + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions, and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions, and the following disclaimer in the documentation + * provided with the distribution. + * + * This software is provided "as is" and without any warranty, express or + * implied. In no event shall the authors be liable for any damages arising from + * the use of this software. + */ + +//#define FS_TEST_VERSION + +#ifdef FS_TEST_VERSION +#define PACKAGE "audacious-test" +#define EXPORT +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#ifndef FS_TEST_VERSION +#include +#endif +#include + +//#include "../ui-common/qt-compat.h" + + +class FilesystemQt : public GeneralPlugin +{ +public: + static const char * const defaults[]; + static const char about[]; + + static constexpr PluginInfo info = {N_("Filesystem Manager"), PACKAGE, + about, // about + nullptr, // prefs + PluginQtOnly}; + + constexpr FilesystemQt() : GeneralPlugin(info, false) {} + + void * get_qt_widget(); + int take_message(const char * code, const void *, int); +}; + +const char FilesystemQt::about[] = + N_("(p) 2025 Hans Dijkema\n\n" + "The FilesystemQt plugin gives a dockable widget that can be used\n" + "to browse folders for music files (mp3|flac|ogg)\n" + "\n" + "Right mouse on a folder or file and these can be added to, or replace\n" + "the current playlist." + ); + +EXPORT FilesystemQt aud_plugin_instance; + +#ifdef FS_TEST_VERSION +QWidget *pluginWidget() +{ + return (QWidget *) aud_plugin_instance.get_qt_widget(); +} +#endif + +class FilesystemTree +{ + public: + static const int DIR = 1; + static const int FILE = 2; + static QString _sep; + private: + QFileInfo _fi; + int _kind; + QList _entries; + FilesystemTree *_parent; + int _parent_index; + bool _loaded; + private: + void clearList() { + int i; + for(i = 0; i < _entries.size(); i++) { + FilesystemTree *t = _entries[i]; + delete t; + } + _entries.clear(); + } + public: + FilesystemTree(int kind, const QString & p, int idx, FilesystemTree *parent) + { + _sep = "/"; + _fi = QFileInfo(p); + _kind = kind; + _parent_index = idx; + _parent = parent; + _loaded = false; + //qDebug() << _fi << " - " << path() << " - " << _kind; + } + + ~FilesystemTree() { + clearList(); + } + public: + int nItems() { load();return _entries.size(); } + QString name() { load();return _fi.fileName(); } + QString path() const{ return _fi.absoluteFilePath(); } + int kind() const { return _kind; } + int isDir() { return _kind == DIR; } + int isFile() { return _kind == FILE; } + FilesystemTree *entry(int row) { + load(); + if (row >= _entries.size()) { + qDebug() << "Unexpected: " << row << " - " << _entries.size(); + return nullptr; + } + return _entries[row]; + } + + public: + void setPath(const QString &p) + { + _fi = QFileInfo(p); + reload(); + } + public: + void reload() { + _loaded = false; + load(); + } + void load() { + if (_loaded) { return; } + + _loaded = true; + if (_kind == DIR) { + clearList(); + + QDir d(_fi.absoluteFilePath()); + + QStringList filters; + filters << "*.mp3" << "*.flac" << "*.ogg"; + d.setNameFilters(filters); + + QDir::Filters d_filters = QDir::AllDirs | QDir::NoDot | QDir::NoDotDot | QDir::Files | QDir::Readable; + d.setFilter(d_filters); + + QStringList l = d.entryList(filters, d_filters, QDir::SortFlag::IgnoreCase); + + QString p = path(); + int i; + for(i = 0; i < l.size(); i++) { + QString new_file = p + _sep + l[i]; + QFileInfo f(new_file); + int k = (f.isDir()) ? DIR : FILE; + //qDebug() << i << " - " << new_file << " - " << k; + _entries.append(new FilesystemTree(k, new_file, i, this)); + } + } + } + public: + FilesystemTree *parent() const { return _parent; } + int parentIndex() const { return _parent_index; } + +}; + +QString FilesystemTree::_sep; + + +class FilesystemModel : public QAbstractItemModel +{ +private: + QString _base_dir; + FilesystemTree *_tree; + +public: + enum + { + ColumnTitle, + NColumns + }; + + FilesystemModel() + { + _base_dir = ""; + _tree = new FilesystemTree(FilesystemTree::DIR, _base_dir, -1, nullptr); + } + + ~FilesystemModel() + { + delete _tree; + } + +public: + void setLibraryPath(const QString &library_path) + { + _base_dir = library_path; + if (_base_dir != _tree->path()) { + this->beginResetModel(); + _tree->setPath(_base_dir); + this->endResetModel(); + } + } + + QModelIndex index(int row, int column, const QModelIndex &parent) const + { + if (parent.isValid()) { + FilesystemTree *t = (FilesystemTree *) parent.internalPointer(); + //qDebug() << "index: " << row << " - " << column << " - " << t->path(); + int parent_row = parent.row(); + return createIndex(row, column, t->entry(parent_row)); + } else { + return createIndex(row, column, _tree); + } + } + + QList get(QModelIndexList s) + { + QList l; + int i; + for(i = 0; i < s.size(); i++) { + QModelIndex idx = s[i]; + if (idx.isValid()) { + int row = idx.row(); + FilesystemTree *t = (FilesystemTree *) idx.internalPointer(); + FilesystemTree *entry = t->entry(row); + l.append(entry); + } + } + return l; + } + + bool hasChildren(const QModelIndex &parent) const + { + if (parent.isValid()) { + FilesystemTree * t; + t = (FilesystemTree *) parent.internalPointer(); + int row = parent.row(); + return t->entry(row)->nItems() > 0; + //return t->nItems() > 0; + } else { + return _tree->nItems() > 0; + } + } + + int rowCount(const QModelIndex &parent) const + { + if (parent.isValid()) { + FilesystemTree * t = (FilesystemTree *) parent.internalPointer(); + int row = parent.row(); + return t->entry(row)->nItems(); + } else { + return _tree->nItems(); + } + } + + int columnCount(const QModelIndex &parent) const + { + return NColumns; + } + + QVariant data(const QModelIndex &index, int role) const + { + if (index.isValid()) { + if (role == Qt::DisplayRole || role == Qt::ToolTipRole) { + int row = index.row(); + int column = index.column(); + FilesystemTree *t = (FilesystemTree *) index.internalPointer(); + FilesystemTree *entry = t->entry(row); + if (entry == nullptr) { + return QVariant(); + } + if (column == ColumnTitle) { + QString name = entry->name(); + if (entry->kind() == FilesystemTree::DIR) { + name += QString::asprintf(" (%d)", entry->nItems()); + } + return name; + } + } + } + + return QVariant(); + } + + QModelIndex parent(const QModelIndex &child) const + { + if (child.isValid()) { + FilesystemTree *t = (FilesystemTree *) child.internalPointer(); + if (t->parent() == nullptr) { + return QModelIndex(); + } else { + return createIndex(t->parentIndex(), 0, t->parent()); + } + } else { + return QModelIndex(); + } + } +}; + +class FilesystemView : public audqt::TreeView +{ +private: + QList current_selected; + + // Preferences +private: + int max_files_to_add; + QString library_path; + +public: + FilesystemView(); + +public slots: + void configMusicLibrary(); + void configMaxFiles(); + +public: + void connectConfigButtons(QPushButton *configLib, QPushButton *configMaxFiles); + +private: + FilesystemModel m_model; + +protected: + void contextMenuEvent(QContextMenuEvent *event); + +public: + void playThis(bool checked); + void addThis(bool checked); + void insertEntries(Playlist &list, bool play); + +protected slots: + void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); +}; + +void FilesystemView::contextMenuEvent(QContextMenuEvent *evt) +{ + QMenu *menu = new QMenu(this); + QString play = QString("Replace current playlist && play"); + QString add = QString("Add to current playlist"); + QAction *action_play = new QAction(play); + QAction *action_add = new QAction(add); + connect(action_play, &QAction::triggered, this, &FilesystemView::playThis); + connect(action_add, &QAction::triggered, this, &FilesystemView::addThis); + menu->addAction(action_play); + menu->addAction(action_add); + menu->popup(evt->globalPos()); +} + +void assembleFiles(FilesystemTree *e, QStringList &l, int & count, int max) +{ + if (e->isFile()) { + l.append(e->path()); + count += 1; + } else { + int i, N; + for(i = 0, N = e->nItems(); i < N && count < max; i++) { + assembleFiles(e->entry(i), l, count, max); + } + } +} + +void FilesystemView::playThis(bool checked) +{ + //qDebug() << "REPLACE"; +#ifndef FS_TEST_VERSION + auto list = Playlist::active_playlist(); + list.remove_all_entries(); + insertEntries(list, true); +#endif +} + +void FilesystemView::addThis(bool checked) +{ +#ifndef FS_TEST_VERSION + //qDebug() << "ADD"; + auto list = Playlist::active_playlist(); + insertEntries(list, false); +#endif +} + +void FilesystemView::insertEntries(Playlist &list, bool do_play) +{ + QItemSelection sel = this->selectionModel()->selection(); + QModelIndexList l = sel.indexes(); + current_selected = m_model.get(l); + + QStringList files; + int count = 0; + int i; + for(i = 0; i < current_selected.size() && count < max_files_to_add; i++) { + assembleFiles(current_selected[i], files, count, max_files_to_add); + } + +#ifndef FS_TEST_VERSION + for(i = 0; i < files.size(); i++) { + QUrl u; + u = u.fromLocalFile(files[i]); + QString f = u.toString(); + list.insert_entry(-1, f.toUtf8(), Tuple(), do_play); + if (do_play) { do_play = false; } + } +#endif +} + +void FilesystemView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) +{ + QTreeView::selectionChanged(selected, deselected); + //QModelIndexList l = selected.indexes(); + //current_selected = m_model.get(l); +} + + +FilesystemView::FilesystemView() +{ + setModel(&m_model); + + QSettings s("filesystem", "audacious"); + + QString std_music_loc; + QStringList std_music_locs = QStandardPaths::standardLocations(QStandardPaths::MusicLocation); + if (std_music_locs.size() > 0) { + std_music_loc = std_music_locs[0]; + } else { + QStringList l = QStandardPaths::standardLocations(QStandardPaths::HomeLocation); + if (l.size() > 0) { std_music_loc = l[0]; } + } + + max_files_to_add = s.value("max_files_to_add", 100).toInt(); + library_path = s.value("library_path", std_music_loc).toString(); + m_model.setLibraryPath(library_path); + + setAllColumnsShowFocus(true); + setFrameShape(QFrame::NoFrame); + + horizontalScrollBar()->setEnabled(true); + setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); + header()->setStretchLastSection(false); + header()->setSectionResizeMode(0, QHeaderView::ResizeToContents); + header()->hide(); + + setSelectionBehavior(SelectRows); + setSelectionMode(ExtendedSelection); +} + +void FilesystemView::configMusicLibrary() +{ + QString n_lib_path = QFileDialog::getExistingDirectory(this, N_("Select a folder as Music Library"), library_path, QFileDialog::ShowDirsOnly); + if (n_lib_path != "") { + QSettings s("filesystem", "audacious"); + library_path = n_lib_path; + s.setValue("library_path", library_path); + m_model.setLibraryPath(library_path); + } +} + +void FilesystemView::configMaxFiles() +{ + QSettings s("filesystem", "audacious"); + QDialog *dlg = new QDialog(this); + dlg->setModal(true); + dlg->setWindowTitle(N_("Maximum Files to a Playlist")); + QHBoxLayout *hbox = new QHBoxLayout(); + hbox->addWidget(new QLabel(N_("Maximum number of files to add from library to playlist at once:"))); + + QSpinBox *sp = new QSpinBox(); + sp->setMaximum(500); + sp->setMinimum(10); + sp->setValue(s.value("max_files_to_add", max_files_to_add).toInt()); + hbox->addWidget(sp, 1); + + connect(sp, &QSpinBox::valueChanged, [this](int v) { + max_files_to_add = v; + QSettings s("filesystem", "audacious"); + s.setValue("max_files_to_add", max_files_to_add); + }); + + QPushButton *ok = new QPushButton(N_("Close")); + connect(ok, &QPushButton::clicked, dlg, &QDialog::close); + QHBoxLayout *hbox1 = new QHBoxLayout(); + hbox1->addStretch(1); + hbox1->addWidget(ok); + + QVBoxLayout *vbox = new QVBoxLayout(); + vbox->addLayout(hbox); + vbox->addLayout(hbox1); + + dlg->setLayout(vbox); + dlg->exec(); +} + +void FilesystemView::connectConfigButtons(QPushButton *configLib, QPushButton *configMaxFiles) +{ + connect(configLib, &QPushButton::clicked, this, &FilesystemView::configMusicLibrary); + connect(configMaxFiles, &QPushButton::clicked,this, &FilesystemView::configMaxFiles); +} + + +static QPointer s_filesystem_view; + +void * FilesystemQt::get_qt_widget() +{ + s_filesystem_view = new FilesystemView; + +#ifndef FS_TEST_VERSION + auto hbox = audqt::make_hbox(nullptr); + hbox->setContentsMargins(audqt::margins.TwoPt); + + QPushButton *fbtn = new QPushButton(N_("Max files to add")); + QPushButton *btn = new QPushButton(N_("Set Music Library Folder")); + s_filesystem_view->connectConfigButtons(btn, fbtn); + hbox->addWidget(fbtn); + hbox->addWidget(btn); + + auto widget = new QWidget; + auto vbox = audqt::make_vbox(widget, 0); + vbox->addWidget(s_filesystem_view, 1); + vbox->addLayout(hbox); +#else + QWidget *widget = s_filesystem_view; +#endif + return widget; +} + +int FilesystemQt::take_message(const char * code, const void *p, int n) +{ + qDebug() << code << " - " << n; + if (!strcmp(code, "grab focus") && s_filesystem_view) + { + s_filesystem_view->setFocus(Qt::OtherFocusReason); + return 0; + } + + return -1; +} diff --git a/src/filesystem-qt/meson.build b/src/filesystem-qt/meson.build new file mode 100644 index 000000000..db42aecda --- /dev/null +++ b/src/filesystem-qt/meson.build @@ -0,0 +1,7 @@ +shared_module('filesystem-qt', + 'filesystem-qt.cc', + dependencies: [audacious_dep, qt_dep, audqt_dep], + name_prefix: '', + install: true, + install_dir: general_plugin_dir +) From 368dc89d5261937234c01c789993f15c1e4c28b7 Mon Sep 17 00:00:00 2001 From: Hans Dijkema Date: Wed, 30 Jul 2025 00:16:19 +0200 Subject: [PATCH 2/9] Added some comments and cleaned up some code. Also removed a small typo from configure.ac --- configure.ac | 2 +- src/filesystem-qt/filesystem-qt.cc | 76 ++++++++++++++---------------- 2 files changed, 37 insertions(+), 41 deletions(-) diff --git a/configure.ac b/configure.ac index 71f84681c..423bd1ce4 100644 --- a/configure.ac +++ b/configure.ac @@ -83,7 +83,7 @@ TRANSPORT_PLUGINS="gio" if test "x$USE_GTK" = "xyes" ; then EFFECT_PLUGINS="$EFFECT_PLUGINS ladspa" - GENERAL_PLUGINS="$GENERAL_PLUGINS albumart lyrics-gtk playlist-manager fsearch-tool statusicon" + GENERAL_PLUGINS="$GENERAL_PLUGINS albumart lyrics-gtk playlist-manager search-tool statusicon" GENERAL_PLUGINS="$GENERAL_PLUGINS gtkui skins" VISUALIZATION_PLUGINS="$VISUALIZATION_PLUGINS blur_scope cairo-spectrum vumeter" fi diff --git a/src/filesystem-qt/filesystem-qt.cc b/src/filesystem-qt/filesystem-qt.cc index a359bcd50..d216b69fc 100644 --- a/src/filesystem-qt/filesystem-qt.cc +++ b/src/filesystem-qt/filesystem-qt.cc @@ -1,6 +1,6 @@ /* - * playlist-manager-qt.cc - * Copyright 2015 John Lindgren + * filesystem-qt.cc + * Produced 2025 Hans Dijkema * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -17,13 +17,6 @@ * the use of this software. */ -//#define FS_TEST_VERSION - -#ifdef FS_TEST_VERSION -#define PACKAGE "audacious-test" -#define EXPORT -#endif - #include #include #include @@ -50,13 +43,13 @@ #include #include #include -#ifndef FS_TEST_VERSION #include -#endif #include -//#include "../ui-common/qt-compat.h" +///////////////////////////////////////////////////////////////////////////////////////// +// FilesystemQt Plugin Class +///////////////////////////////////////////////////////////////////////////////////////// class FilesystemQt : public GeneralPlugin { @@ -86,12 +79,14 @@ const char FilesystemQt::about[] = EXPORT FilesystemQt aud_plugin_instance; -#ifdef FS_TEST_VERSION QWidget *pluginWidget() { return (QWidget *) aud_plugin_instance.get_qt_widget(); } -#endif + +///////////////////////////////////////////////////////////////////////////////////////// +// Internal datastructure to hold directories and files. +///////////////////////////////////////////////////////////////////////////////////////// class FilesystemTree { @@ -118,13 +113,12 @@ class FilesystemTree public: FilesystemTree(int kind, const QString & p, int idx, FilesystemTree *parent) { - _sep = "/"; + _sep = "/"; // This might also work on Windows in Qt. _fi = QFileInfo(p); _kind = kind; _parent_index = idx; _parent = parent; _loaded = false; - //qDebug() << _fi << " - " << path() << " - " << _kind; } ~FilesystemTree() { @@ -140,7 +134,7 @@ class FilesystemTree FilesystemTree *entry(int row) { load(); if (row >= _entries.size()) { - qDebug() << "Unexpected: " << row << " - " << _entries.size(); + qDebug() << "Unexpected!: " << row << " - " << _entries.size(); return nullptr; } return _entries[row]; @@ -167,7 +161,10 @@ class FilesystemTree QDir d(_fi.absoluteFilePath()); QStringList filters; - filters << "*.mp3" << "*.flac" << "*.ogg"; + filters << "*.mp3" << "*.flac" << "*.ogg" << + "*.m4a" << "*.ape" << "*.wav" << + "*.aac" << "*.wma" << "*.aiff" << + "*.opus"; // This should probably be done configurable d.setNameFilters(filters); QDir::Filters d_filters = QDir::AllDirs | QDir::NoDot | QDir::NoDotDot | QDir::Files | QDir::Readable; @@ -195,11 +192,15 @@ class FilesystemTree QString FilesystemTree::_sep; +///////////////////////////////////////////////////////////////////////////////////////// +// FilesystemModel interfaces with the QTreeView framework of Qt +///////////////////////////////////////////////////////////////////////////////////////// + class FilesystemModel : public QAbstractItemModel { private: - QString _base_dir; - FilesystemTree *_tree; + QString _base_dir; + FilesystemTree *_tree; public: enum @@ -326,6 +327,10 @@ class FilesystemModel : public QAbstractItemModel } }; +///////////////////////////////////////////////////////////////////////////////////////// +// FilesystemView implements a TreeView for a folder structure +///////////////////////////////////////////////////////////////////////////////////////// + class FilesystemView : public audqt::TreeView { private: @@ -356,9 +361,6 @@ public slots: void playThis(bool checked); void addThis(bool checked); void insertEntries(Playlist &list, bool play); - -protected slots: - void selectionChanged(const QItemSelection &selected, const QItemSelection &deselected); }; void FilesystemView::contextMenuEvent(QContextMenuEvent *evt) @@ -390,29 +392,27 @@ void assembleFiles(FilesystemTree *e, QStringList &l, int & count, int max) void FilesystemView::playThis(bool checked) { - //qDebug() << "REPLACE"; -#ifndef FS_TEST_VERSION auto list = Playlist::active_playlist(); list.remove_all_entries(); insertEntries(list, true); -#endif } void FilesystemView::addThis(bool checked) { -#ifndef FS_TEST_VERSION - //qDebug() << "ADD"; auto list = Playlist::active_playlist(); insertEntries(list, false); -#endif } void FilesystemView::insertEntries(Playlist &list, bool do_play) { + // Get the currently selected files / directories + QItemSelection sel = this->selectionModel()->selection(); QModelIndexList l = sel.indexes(); current_selected = m_model.get(l); + // Assemble the files to a maximum of 'max_files_to_add', which is configurable. + QStringList files; int count = 0; int i; @@ -420,7 +420,9 @@ void FilesystemView::insertEntries(Playlist &list, bool do_play) assembleFiles(current_selected[i], files, count, max_files_to_add); } -#ifndef FS_TEST_VERSION + // Add the files to the current playlist or replace the contents of the playlist. + // if do_play == true, the first added entry will also be played. + for(i = 0; i < files.size(); i++) { QUrl u; u = u.fromLocalFile(files[i]); @@ -428,17 +430,8 @@ void FilesystemView::insertEntries(Playlist &list, bool do_play) list.insert_entry(-1, f.toUtf8(), Tuple(), do_play); if (do_play) { do_play = false; } } -#endif -} - -void FilesystemView::selectionChanged(const QItemSelection &selected, const QItemSelection &deselected) -{ - QTreeView::selectionChanged(selected, deselected); - //QModelIndexList l = selected.indexes(); - //current_selected = m_model.get(l); } - FilesystemView::FilesystemView() { setModel(&m_model); @@ -524,6 +517,10 @@ void FilesystemView::connectConfigButtons(QPushButton *configLib, QPushButton *c } +///////////////////////////////////////////////////////////////////////////////////////// +// Make the Plugin Work. +///////////////////////////////////////////////////////////////////////////////////////// + static QPointer s_filesystem_view; void * FilesystemQt::get_qt_widget() @@ -552,7 +549,6 @@ void * FilesystemQt::get_qt_widget() int FilesystemQt::take_message(const char * code, const void *p, int n) { - qDebug() << code << " - " << n; if (!strcmp(code, "grab focus") && s_filesystem_view) { s_filesystem_view->setFocus(Qt::OtherFocusReason); From 1af1c1046ddd8aefe51af1a5d14dd8694e7846a5 Mon Sep 17 00:00:00 2001 From: Hans Dijkema Date: Wed, 30 Jul 2025 00:54:43 +0200 Subject: [PATCH 3/9] Added *.dsf as filter. --- src/filesystem-qt/filesystem-qt.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/filesystem-qt/filesystem-qt.cc b/src/filesystem-qt/filesystem-qt.cc index d216b69fc..5d6a62428 100644 --- a/src/filesystem-qt/filesystem-qt.cc +++ b/src/filesystem-qt/filesystem-qt.cc @@ -164,7 +164,7 @@ class FilesystemTree filters << "*.mp3" << "*.flac" << "*.ogg" << "*.m4a" << "*.ape" << "*.wav" << "*.aac" << "*.wma" << "*.aiff" << - "*.opus"; // This should probably be done configurable + "*.opus" << "*.dsf"; // This should probably be done configurable d.setNameFilters(filters); QDir::Filters d_filters = QDir::AllDirs | QDir::NoDot | QDir::NoDotDot | QDir::Files | QDir::Readable; From 0981c933767bbcf48641beb78f315e0fead36f5d Mon Sep 17 00:00:00 2001 From: Hans Dijkema Date: Wed, 30 Jul 2025 13:22:06 +0200 Subject: [PATCH 4/9] Made the plugin configuration. --- src/filesystem-qt/filesystem-qt.cc | 449 +++++++++++++++++++++++------ 1 file changed, 354 insertions(+), 95 deletions(-) diff --git a/src/filesystem-qt/filesystem-qt.cc b/src/filesystem-qt/filesystem-qt.cc index 5d6a62428..80ab044ac 100644 --- a/src/filesystem-qt/filesystem-qt.cc +++ b/src/filesystem-qt/filesystem-qt.cc @@ -1,4 +1,4 @@ -/* +/* * filesystem-qt.cc * Produced 2025 Hans Dijkema * @@ -38,6 +38,7 @@ #include #include #include +#include #include #include @@ -45,24 +46,55 @@ #include #include #include - +#include +#include ///////////////////////////////////////////////////////////////////////////////////////// // FilesystemQt Plugin Class ///////////////////////////////////////////////////////////////////////////////////////// +#define CFG_ID "FilesystemQt" +#define CFG_MAX_FILES "max_files_to_add" +#define CFG_MUSIC_LIBRARY "music_library_folder" +#define CFG_MUSIC_EXTS "music_file_extenstions" +#define CFG_DEFAULT_EXTS "mp3|flac|ogg|m4a|ape|wav|aac|aiff|opus|dsf" + +#define PLUGIN_BASENAME "filesystem-qt" + +static void callback_folder(); +static void callback_max_files_to_add(); +static void callback_exts(); + +#define AUD_LOGGING +//#define EXPORT + +#ifdef AUD_LOGGING +#define LOGINFO AUDINFO +#define LOGDBG AUDDBG +#define LOGWARN AUDWARN +#else +#define LOGBASE(kind, ...) fprintf(stderr, "%s:%d (%s) - %s:", __FILE__, __LINE__, __FUNCTION__, kind) +#define LOGINFO(...) { LOGBASE("Info ");fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } +#define LOGDBG(...) { LOGBASE("Debug ");fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } +#define LOGWARN(...) { LOGBASE("Warning");fprintf(stderr, __VA_ARGS__); fprintf(stderr, "\n"); } +#endif + class FilesystemQt : public GeneralPlugin { public: static const char * const defaults[]; static const char about[]; + static const PreferencesWidget widgets[]; + static const PluginPreferences prefs; static constexpr PluginInfo info = {N_("Filesystem Manager"), PACKAGE, about, // about - nullptr, // prefs + &prefs, // prefs PluginQtOnly}; - constexpr FilesystemQt() : GeneralPlugin(info, false) {} + constexpr FilesystemQt() : GeneralPlugin(info, true) + { + } void * get_qt_widget(); int take_message(const char * code, const void *, int); @@ -71,12 +103,28 @@ class FilesystemQt : public GeneralPlugin const char FilesystemQt::about[] = N_("(p) 2025 Hans Dijkema\n\n" "The FilesystemQt plugin gives a dockable widget that can be used\n" - "to browse folders for music files (mp3|flac|ogg)\n" + "to browse folders for music files (mp3|flac|ogg|etc.)\n" "\n" "Right mouse on a folder or file and these can be added to, or replace\n" "the current playlist." ); +const static WidgetVFileEntry dir_entry = { FileSelectMode::Folder }; + +const PreferencesWidget FilesystemQt::widgets[] = { + WidgetLabel(N_("Standard options")), + WidgetLabel("--------------------------------------------------------------------------------------------------------------------------------------------------"), + WidgetFileEntry (N_("Music folder:"), WidgetString(CFG_ID, CFG_MUSIC_LIBRARY, callback_folder), dir_entry), + WidgetSpin(N_("Maximum files to add to playlist:"), WidgetInt(CFG_ID, CFG_MAX_FILES, callback_max_files_to_add), { 10, 500, 1, N_("files") }), + WidgetLabel(""), + WidgetLabel(N_("Advanced options")), + WidgetLabel("--------------------------------------------------------------------------------------------------------------------------------------------------"), + WidgetEntry(N_("Music file extensions to browse:"), WidgetString(CFG_ID, CFG_MUSIC_EXTS, callback_exts)) +}; + +const PluginPreferences FilesystemQt::prefs = {{widgets}}; + + EXPORT FilesystemQt aud_plugin_instance; QWidget *pluginWidget() @@ -94,6 +142,7 @@ class FilesystemTree static const int DIR = 1; static const int FILE = 2; static QString _sep; + static QString _exts; private: QFileInfo _fi; int _kind; @@ -111,9 +160,10 @@ class FilesystemTree _entries.clear(); } public: - FilesystemTree(int kind, const QString & p, int idx, FilesystemTree *parent) + FilesystemTree(int kind, const QString & p, const QString & exts, int idx, FilesystemTree *parent) { - _sep = "/"; // This might also work on Windows in Qt. + _sep = "/"; // This might also work on Windows in Qt. + _exts = exts; _fi = QFileInfo(p); _kind = kind; _parent_index = idx; @@ -139,6 +189,7 @@ class FilesystemTree } return _entries[row]; } + QString validExts() { return _exts; } public: void setPath(const QString &p) @@ -146,6 +197,19 @@ class FilesystemTree _fi = QFileInfo(p); reload(); } + + void setValidExts(const QString &e) + { + _exts = e; + reload(); + } + + void setPathAndExts(const QString &p, const QString &e) + { + _fi = QFileInfo(p); + _exts = e; + reload(); + } public: void reload() { _loaded = false; @@ -161,10 +225,13 @@ class FilesystemTree QDir d(_fi.absoluteFilePath()); QStringList filters; - filters << "*.mp3" << "*.flac" << "*.ogg" << - "*.m4a" << "*.ape" << "*.wav" << - "*.aac" << "*.wma" << "*.aiff" << - "*.opus" << "*.dsf"; // This should probably be done configurable + { + QStringList exts = validExts().split("|"); + int i; + for(i = 0; i < exts.size(); i++) { + filters.append(QString("*.") + exts[i].trimmed().toLower()); + } + } d.setNameFilters(filters); QDir::Filters d_filters = QDir::AllDirs | QDir::NoDot | QDir::NoDotDot | QDir::Files | QDir::Readable; @@ -175,11 +242,14 @@ class FilesystemTree QString p = path(); int i; for(i = 0; i < l.size(); i++) { - QString new_file = p + _sep + l[i]; - QFileInfo f(new_file); - int k = (f.isDir()) ? DIR : FILE; - //qDebug() << i << " - " << new_file << " - " << k; - _entries.append(new FilesystemTree(k, new_file, i, this)); + if (l[i] != "lost+found") { + QString new_file = p + _sep + l[i]; + + QFileInfo f(new_file); + int k = (f.isDir()) ? DIR : FILE; + + _entries.append(new FilesystemTree(k, new_file, _exts, i, this)); + } } } } @@ -190,6 +260,7 @@ class FilesystemTree }; QString FilesystemTree::_sep; +QString FilesystemTree::_exts; ///////////////////////////////////////////////////////////////////////////////////////// @@ -200,7 +271,9 @@ class FilesystemModel : public QAbstractItemModel { private: QString _base_dir; + QString _valid_exts; FilesystemTree *_tree; + bool _in_config; public: enum @@ -209,10 +282,12 @@ class FilesystemModel : public QAbstractItemModel NColumns }; - FilesystemModel() + FilesystemModel(const QString &base_dir, const QString &valid_exts) { - _base_dir = ""; - _tree = new FilesystemTree(FilesystemTree::DIR, _base_dir, -1, nullptr); + _base_dir = base_dir; + _valid_exts = valid_exts; + _in_config = false; + _tree = new FilesystemTree(FilesystemTree::DIR, _base_dir, _valid_exts, -1, nullptr); } ~FilesystemModel() @@ -221,13 +296,35 @@ class FilesystemModel : public QAbstractItemModel } public: + void beginConfig() + { + _in_config = true; + this->beginResetModel(); + } + + void endConfig() + { + _in_config = false; + this->endResetModel(); + } + void setLibraryPath(const QString &library_path) { _base_dir = library_path; if (_base_dir != _tree->path()) { - this->beginResetModel(); + if (!_in_config) { this->beginResetModel(); } _tree->setPath(_base_dir); - this->endResetModel(); + if (!_in_config) { this->endResetModel(); } + } + } + + void setValidExts(const QString &valid_exts) + { + _valid_exts = valid_exts; + if (_valid_exts != _tree->validExts()) { + if (!_in_config) { this->beginResetModel(); } + _tree->setValidExts(_valid_exts); + if (!_in_config) { this->endResetModel(); } } } @@ -340,19 +437,21 @@ class FilesystemView : public audqt::TreeView private: int max_files_to_add; QString library_path; + QString valid_exts; + +public slots: + void config(); public: FilesystemView(); -public slots: - void configMusicLibrary(); - void configMaxFiles(); - public: - void connectConfigButtons(QPushButton *configLib, QPushButton *configMaxFiles); + void setMusicLibrary(const QUrl &folder); + void setMaxFilesToAdd(int max); + void setMusicExts(const QString &exts); private: - FilesystemModel m_model; + FilesystemModel *m_model; protected: void contextMenuEvent(QContextMenuEvent *event); @@ -360,20 +459,26 @@ public slots: public: void playThis(bool checked); void addThis(bool checked); + void openThis(bool checked); void insertEntries(Playlist &list, bool play); }; void FilesystemView::contextMenuEvent(QContextMenuEvent *evt) { QMenu *menu = new QMenu(this); - QString play = QString("Replace current playlist && play"); - QString add = QString("Add to current playlist"); + QString play = QString(N_("Replace current playlist and play")); + QString add = QString(N_("Add to current playlist")); + QString open = QString(N_("Open containing folder")); QAction *action_play = new QAction(play); QAction *action_add = new QAction(add); + QAction *action_open = new QAction(open); connect(action_play, &QAction::triggered, this, &FilesystemView::playThis); connect(action_add, &QAction::triggered, this, &FilesystemView::addThis); + connect(action_open, &QAction::triggered, this, &FilesystemView::openThis); menu->addAction(action_play); menu->addAction(action_add); + menu->addSeparator(); + menu->addAction(action_open); menu->popup(evt->globalPos()); } @@ -403,13 +508,48 @@ void FilesystemView::addThis(bool checked) insertEntries(list, false); } +void FilesystemView::openThis(bool checked) +{ + // Get the currently selected files / directories + + QItemSelection sel = this->selectionModel()->selection(); + QModelIndexList l = sel.indexes(); + current_selected = m_model->get(l); + + if (current_selected.size() > 1) { + QMessageBox::warning(this, + N_("Too many selected entries"), + N_("Cannot determine where to open the filenmanager / explorer\n" + "Please select one entry") + ); + } else if (current_selected.size() == 0) { + QMessageBox::warning(this, + N_("No selected entries"), + N_("Cannot determine where to open the filenmanager / explorer\n" + "Please select one entry") + ); + } else { + FilesystemTree *s = current_selected[0]; + QString where = s->path(); + if (s->kind() == FilesystemTree::FILE) { + QFileInfo fi(s->path()); + where = fi.canonicalPath(); + } + QDir w(where); + if (w.exists()) { + QUrl wu = QUrl().fromLocalFile(where); + QDesktopServices::openUrl(wu); + } + } +} + void FilesystemView::insertEntries(Playlist &list, bool do_play) { // Get the currently selected files / directories QItemSelection sel = this->selectionModel()->selection(); QModelIndexList l = sel.indexes(); - current_selected = m_model.get(l); + current_selected = m_model->get(l); // Assemble the files to a maximum of 'max_files_to_add', which is configurable. @@ -432,11 +572,30 @@ void FilesystemView::insertEntries(Playlist &list, bool do_play) } } -FilesystemView::FilesystemView() +static QString correctExts(QString exts, bool &correct); + +void FilesystemView::config() { - setModel(&m_model); + LOGDBG("configuration called"); + PluginHandle *h = aud_plugin_lookup_basename(PLUGIN_BASENAME); + if (h != nullptr) { + audqt::plugin_prefs(h); + } else { + LOGWARN("Plugin lookup failed for plugin: %s", PLUGIN_BASENAME); + QString msg; + msg = msg.asprintf(N_("Plugin lookup for '%s' failed\n\n" + "Please configure this plugin via the normale plugin configuration way"), + PLUGIN_BASENAME); + QMessageBox::warning(this, + N_("Plugin Lookup Failed"), + msg); + } +} - QSettings s("filesystem", "audacious"); +FilesystemView::FilesystemView() +{ + // Configuration Defaults + LOGDBG("config defaults"); QString std_music_loc; QStringList std_music_locs = QStandardPaths::standardLocations(QStandardPaths::MusicLocation); @@ -445,14 +604,59 @@ FilesystemView::FilesystemView() } else { QStringList l = QStandardPaths::standardLocations(QStandardPaths::HomeLocation); if (l.size() > 0) { std_music_loc = l[0]; } + else { + LOGWARN(N_("No default music location found! Configure it by hand!")); + } } - max_files_to_add = s.value("max_files_to_add", 100).toInt(); - library_path = s.value("library_path", std_music_loc).toString(); - m_model.setLibraryPath(library_path); + const char *default_music_loc = std_music_loc.toUtf8(); + LOGINFO("Standard Music Locatio Found: %s", default_music_loc); + + const char * const cfg_filesystem_defaults [] = { + CFG_MAX_FILES, "100", + CFG_MUSIC_LIBRARY, default_music_loc, + CFG_MUSIC_EXTS, CFG_DEFAULT_EXTS, + nullptr + }; + + aud_config_set_defaults(CFG_ID, cfg_filesystem_defaults); + + // Get configuration settings + LOGDBG("Config settings"); + + max_files_to_add = aud_get_int(CFG_ID, CFG_MAX_FILES); + + const char *aud_music_lib = aud_get_str(CFG_ID, CFG_MUSIC_LIBRARY); + QString path = QString(aud_music_lib); + if (!path.startsWith("file:")) { + library_path = path; + } else { + library_path = QUrl(aud_music_lib).toLocalFile(); + } + + const char *aud_valid_exts = aud_get_str(CFG_ID, CFG_MUSIC_EXTS); + bool correct_exts = false; + valid_exts = correctExts(QString(aud_valid_exts), correct_exts); + + if (!correct_exts) { + LOGWARN("configured music file extensions: %s, are not correct", aud_valid_exts); + LOGWARN("they have been reset to %s", valid_exts.toUtf8().data()); + } + + LOGINFO("max_files_to_add = %d", max_files_to_add); + LOGINFO("library_path = %s", library_path.toUtf8().data()); + LOGINFO("valid_exts = %s", valid_exts.toUtf8().data()); + + // Initialize Model + LOGDBG("Model Init"); + m_model = new FilesystemModel(library_path, valid_exts); + setModel(m_model); + + // Initialize View + LOGDBG("View Init"); setAllColumnsShowFocus(true); - setFrameShape(QFrame::NoFrame); + //setFrameShape(QFrame::NoFrame); horizontalScrollBar()->setEnabled(true); setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); @@ -464,86 +668,59 @@ FilesystemView::FilesystemView() setSelectionMode(ExtendedSelection); } -void FilesystemView::configMusicLibrary() +void FilesystemView::setMusicLibrary(const QUrl &folder) { - QString n_lib_path = QFileDialog::getExistingDirectory(this, N_("Select a folder as Music Library"), library_path, QFileDialog::ShowDirsOnly); - if (n_lib_path != "") { - QSettings s("filesystem", "audacious"); - library_path = n_lib_path; - s.setValue("library_path", library_path); - m_model.setLibraryPath(library_path); - } + library_path = folder.toLocalFile(); + m_model->setLibraryPath(library_path); } -void FilesystemView::configMaxFiles() +void FilesystemView::setMaxFilesToAdd(int m) { - QSettings s("filesystem", "audacious"); - QDialog *dlg = new QDialog(this); - dlg->setModal(true); - dlg->setWindowTitle(N_("Maximum Files to a Playlist")); - QHBoxLayout *hbox = new QHBoxLayout(); - hbox->addWidget(new QLabel(N_("Maximum number of files to add from library to playlist at once:"))); - - QSpinBox *sp = new QSpinBox(); - sp->setMaximum(500); - sp->setMinimum(10); - sp->setValue(s.value("max_files_to_add", max_files_to_add).toInt()); - hbox->addWidget(sp, 1); - - connect(sp, &QSpinBox::valueChanged, [this](int v) { - max_files_to_add = v; - QSettings s("filesystem", "audacious"); - s.setValue("max_files_to_add", max_files_to_add); - }); - - QPushButton *ok = new QPushButton(N_("Close")); - connect(ok, &QPushButton::clicked, dlg, &QDialog::close); - QHBoxLayout *hbox1 = new QHBoxLayout(); - hbox1->addStretch(1); - hbox1->addWidget(ok); - - QVBoxLayout *vbox = new QVBoxLayout(); - vbox->addLayout(hbox); - vbox->addLayout(hbox1); - - dlg->setLayout(vbox); - dlg->exec(); + max_files_to_add = m; } -void FilesystemView::connectConfigButtons(QPushButton *configLib, QPushButton *configMaxFiles) +void FilesystemView::setMusicExts(const QString &exts) { - connect(configLib, &QPushButton::clicked, this, &FilesystemView::configMusicLibrary); - connect(configMaxFiles, &QPushButton::clicked,this, &FilesystemView::configMaxFiles); + valid_exts = exts; + m_model->setValidExts(valid_exts); } - ///////////////////////////////////////////////////////////////////////////////////////// // Make the Plugin Work. ///////////////////////////////////////////////////////////////////////////////////////// static QPointer s_filesystem_view; +static FilesystemView *getMyWidget() +{ + return s_filesystem_view; +} + void * FilesystemQt::get_qt_widget() { s_filesystem_view = new FilesystemView; -#ifndef FS_TEST_VERSION - auto hbox = audqt::make_hbox(nullptr); - hbox->setContentsMargins(audqt::margins.TwoPt); + QWidget *widget = new QWidget(); + QVBoxLayout *vbox = new QVBoxLayout(); + QPushButton *config = new QPushButton(N_("Configure")); + QHBoxLayout *hbox = new QHBoxLayout(); + hbox->addStretch(1); + hbox->addWidget(config); + FilesystemView::connect(config, &QPushButton::clicked, s_filesystem_view, &FilesystemView::config); + + QFrame *frm = new QFrame(); + frm->setFrameStyle(QFrame::NoFrame); + frm->setLineWidth(1); + frm->setLayout(hbox); + hbox->setContentsMargins(0, 0, 0, 0); - QPushButton *fbtn = new QPushButton(N_("Max files to add")); - QPushButton *btn = new QPushButton(N_("Set Music Library Folder")); - s_filesystem_view->connectConfigButtons(btn, fbtn); - hbox->addWidget(fbtn); - hbox->addWidget(btn); + vbox->setContentsMargins(0, 0, 0,0); - auto widget = new QWidget; - auto vbox = audqt::make_vbox(widget, 0); vbox->addWidget(s_filesystem_view, 1); - vbox->addLayout(hbox); -#else - QWidget *widget = s_filesystem_view; -#endif + vbox->addWidget(frm); + + widget->setLayout(vbox); + return widget; } @@ -557,3 +734,85 @@ int FilesystemQt::take_message(const char * code, const void *p, int n) return -1; } + +///////////////////////////////////////////////////////////////////////////////////////// +// Preferences +///////////////////////////////////////////////////////////////////////////////////////// + +static void callback_folder() +{ + const char *c_folder = aud_get_str(CFG_ID, CFG_MUSIC_LIBRARY); + QString folder(c_folder); + QUrl folder_url(folder); + + FilesystemView *view = getMyWidget(); + view->setMusicLibrary(folder_url); +} + +static void callback_max_files_to_add() +{ + int max_files = aud_get_int(CFG_ID, CFG_MAX_FILES); + FilesystemView *view = getMyWidget(); + view->setMaxFilesToAdd(max_files); +} + +static QString correctExts(QString exts, bool &correct) +{ + QRegularExpression re("^\\s*[A-Za-z0-9]+(\\s*[|]\\s*[A-Za-z0-9]*)*\\s*$"); + QRegularExpressionMatch m = re.match(exts); + if (m.hasMatch()) { + QStringList l_exts = exts.split("|"); + + int i; + exts = ""; + for(i = 0; i < l_exts.size(); i++) { + l_exts[i] = l_exts[i].trimmed().toLower(); + } + exts = l_exts.join("|"); + + correct = true; + + return exts; + } else { + correct = false; + return CFG_DEFAULT_EXTS; + } +} + +static void callback_exts() +{ + FilesystemView *view = getMyWidget(); + + const char *c_exts = aud_get_str(CFG_ID, CFG_MUSIC_EXTS); + QString exts(c_exts); + bool correct = false; + QString real_exts = correctExts(exts, correct); + + if (correct) { + LOGDBG("New music file extensions: %s", exts.toUtf8().data()); + view->setMusicExts(real_exts); + } else { + LOGWARN("Music file extensions are not correctly given: %s", exts.toUtf8().data()); + + QRegularExpression re("([^a-zA-Z0-9|]+)"); + exts = exts.replace(re, "\\1"); + LOGDBG("%s", exts.toUtf8().data()); + + QString msg = N_("The given music file extensions:" + "
  • %s
" + "Only characters and numbers are valid (a .. z and 0 .. 9)

" + "The file extensions for browsing will be set to the default:" + "
  • %s
" + ); + QMessageBox box(view); + box.setWindowTitle(N_("Music file extensions wrong")); + box.setTextFormat(Qt::TextFormat::RichText); + box.setText(msg.asprintf(msg.toUtf8(), exts.toUtf8().data(), real_exts.toUtf8().data())); + box.addButton(QMessageBox::StandardButton::Ok); + box.setDefaultButton(QMessageBox::StandardButton::Ok); + box.exec(); + + view->setMusicExts(real_exts); + } +} + From f5d1fda4e7ae774c8eac99de5a3d001240f77367 Mon Sep 17 00:00:00 2001 From: Hans Dijkema Date: Wed, 30 Jul 2025 14:21:06 +0200 Subject: [PATCH 5/9] Booklet opening and cover art recognition --- src/filesystem-qt/filesystem-qt.cc | 136 +++++++++++++++++++++++++++-- 1 file changed, 129 insertions(+), 7 deletions(-) diff --git a/src/filesystem-qt/filesystem-qt.cc b/src/filesystem-qt/filesystem-qt.cc index 80ab044ac..f164a38e3 100644 --- a/src/filesystem-qt/filesystem-qt.cc +++ b/src/filesystem-qt/filesystem-qt.cc @@ -53,17 +53,25 @@ // FilesystemQt Plugin Class ///////////////////////////////////////////////////////////////////////////////////////// -#define CFG_ID "FilesystemQt" -#define CFG_MAX_FILES "max_files_to_add" -#define CFG_MUSIC_LIBRARY "music_library_folder" -#define CFG_MUSIC_EXTS "music_file_extenstions" -#define CFG_DEFAULT_EXTS "mp3|flac|ogg|m4a|ape|wav|aac|aiff|opus|dsf" +#define CFG_ID "FilesystemQt" -#define PLUGIN_BASENAME "filesystem-qt" +#define CFG_MAX_FILES "max_files_to_add" +#define CFG_MUSIC_LIBRARY "music_library_folder" +#define CFG_MUSIC_EXTS "music_file_extenstions" +#define CFG_COVER_FILES "music_cover_files" +#define CFG_BOOKLET_FILE "music_booklet" + +#define CFG_DEFAULT_BOOKLET "booklet.pdf" +#define CFG_DEFAULT_COVERS "cover.jpg|folder.jpg|cover.png|folder.png" +#define CFG_DEFAULT_EXTS "mp3|flac|ogg|m4a|ape|wav|aac|aiff|opus|dsf" + +#define PLUGIN_BASENAME "filesystem-qt" static void callback_folder(); static void callback_max_files_to_add(); static void callback_exts(); +static void callback_booklet(); +static void callback_covers(); #define AUD_LOGGING //#define EXPORT @@ -119,7 +127,9 @@ const PreferencesWidget FilesystemQt::widgets[] = { WidgetLabel(""), WidgetLabel(N_("Advanced options")), WidgetLabel("--------------------------------------------------------------------------------------------------------------------------------------------------"), - WidgetEntry(N_("Music file extensions to browse:"), WidgetString(CFG_ID, CFG_MUSIC_EXTS, callback_exts)) + WidgetEntry(N_("Music file extensions to browse :"), WidgetString(CFG_ID, CFG_MUSIC_EXTS, callback_exts)), + WidgetEntry(N_("Booklet files to recognize :"), WidgetString(CFG_ID, CFG_BOOKLET_FILE, callback_booklet)), + WidgetEntry(N_("Cover art files (e.g. folder.jpg) to recognize:"), WidgetString(CFG_ID, CFG_COVER_FILES, callback_covers)) }; const PluginPreferences FilesystemQt::prefs = {{widgets}}; @@ -438,6 +448,14 @@ class FilesystemView : public audqt::TreeView int max_files_to_add; QString library_path; QString valid_exts; + QString booklet_file; + QString cover_files; + + QString booklet_file_to_open; + QString cover_file_to_open; + +private: + QDir folderForSelection(bool &ok); public slots: void config(); @@ -449,6 +467,8 @@ public slots: void setMusicLibrary(const QUrl &folder); void setMaxFilesToAdd(int max); void setMusicExts(const QString &exts); + void setCoverFiles(const QString &files); + void setBookletFile(const QString &file); private: FilesystemModel *m_model; @@ -479,6 +499,47 @@ void FilesystemView::contextMenuEvent(QContextMenuEvent *evt) menu->addAction(action_add); menu->addSeparator(); menu->addAction(action_open); + + // Check for booklet + bool has_selected; + bool separator_added = false; + QDir d = this->folderForSelection(has_selected); + if (has_selected) { + if (d.exists(booklet_file)) { + booklet_file_to_open = d.filePath(booklet_file); + QAction *action_open_booklet = new QAction(N_("Open booklet")); + connect(action_open_booklet, &QAction::triggered, this, [this]() { + QUrl f; + f = f.fromLocalFile(this->booklet_file_to_open); + QDesktopServices::openUrl(f); + }); + menu->addSeparator(); + separator_added = true; + menu->addAction(action_open_booklet); + } + + QStringList covers = cover_files.split("|"); + QString file = ""; + int i; + for(i = 0; i < covers.size() && file == ""; i++) { + QString cover = covers[i].trimmed(); + if (d.exists(cover)) { + file = d.filePath(cover); + } + } + if (file != "") { + cover_file_to_open = file; + QAction *action_open_cover = new QAction(N_("Open Cover Art")); + connect(action_open_cover, &QAction::triggered, this, [this]() { + QUrl f; + f = f.fromLocalFile(this->cover_file_to_open); + QDesktopServices::openUrl(f); + }); + if (!separator_added) { menu->addSeparator(); } + menu->addAction(action_open_cover); + } + } + menu->popup(evt->globalPos()); } @@ -574,6 +635,29 @@ void FilesystemView::insertEntries(Playlist &list, bool do_play) static QString correctExts(QString exts, bool &correct); +QDir FilesystemView::folderForSelection(bool &ok) +{ + QItemSelection sel = this->selectionModel()->selection(); + QModelIndexList l = sel.indexes(); + current_selected = m_model->get(l); + + if (current_selected.size() == 1) { + ok = true; + + FilesystemTree *s = current_selected[0]; + QString where = s->path(); + if (s->kind() == FilesystemTree::FILE) { + QFileInfo fi(s->path()); + where = fi.canonicalPath(); + } + + return QDir(where); + } else { + ok = false; + return QDir(); + } +} + void FilesystemView::config() { LOGDBG("configuration called"); @@ -616,6 +700,8 @@ FilesystemView::FilesystemView() CFG_MAX_FILES, "100", CFG_MUSIC_LIBRARY, default_music_loc, CFG_MUSIC_EXTS, CFG_DEFAULT_EXTS, + CFG_BOOKLET_FILE, CFG_DEFAULT_BOOKLET, + CFG_COVER_FILES, CFG_DEFAULT_COVERS, nullptr }; @@ -638,6 +724,12 @@ FilesystemView::FilesystemView() bool correct_exts = false; valid_exts = correctExts(QString(aud_valid_exts), correct_exts); + const char *aud_booklet_file = aud_get_str(CFG_ID, CFG_BOOKLET_FILE); + booklet_file = QString(aud_booklet_file); + + const char *aud_conver_files = aud_get_str(CFG_ID, CFG_COVER_FILES); + cover_files = QString(aud_conver_files); + if (!correct_exts) { LOGWARN("configured music file extensions: %s, are not correct", aud_valid_exts); LOGWARN("they have been reset to %s", valid_exts.toUtf8().data()); @@ -646,6 +738,8 @@ FilesystemView::FilesystemView() LOGINFO("max_files_to_add = %d", max_files_to_add); LOGINFO("library_path = %s", library_path.toUtf8().data()); LOGINFO("valid_exts = %s", valid_exts.toUtf8().data()); + LOGINFO("booklet file = %s", booklet_file.toUtf8().data()); + LOGINFO("cover files = %s", cover_files.toUtf8().data()); // Initialize Model LOGDBG("Model Init"); @@ -685,6 +779,16 @@ void FilesystemView::setMusicExts(const QString &exts) m_model->setValidExts(valid_exts); } +void FilesystemView::setCoverFiles(const QString &files) +{ + cover_files = files; +} + +void FilesystemView::setBookletFile(const QString &file) +{ + booklet_file = file; +} + ///////////////////////////////////////////////////////////////////////////////////////// // Make the Plugin Work. ///////////////////////////////////////////////////////////////////////////////////////// @@ -739,6 +843,23 @@ int FilesystemQt::take_message(const char * code, const void *p, int n) // Preferences ///////////////////////////////////////////////////////////////////////////////////////// + +static void callback_booklet() +{ + const char *c_booklet = aud_get_str(CFG_ID, CFG_BOOKLET_FILE); + QString booklet(c_booklet); + FilesystemView *view = getMyWidget(); + view->setBookletFile(booklet); +} + +static void callback_covers() +{ + const char *c_covers = aud_get_str(CFG_ID, CFG_BOOKLET_FILE); + QString covers(c_covers); + FilesystemView *view = getMyWidget(); + view->setCoverFiles(covers); +} + static void callback_folder() { const char *c_folder = aud_get_str(CFG_ID, CFG_MUSIC_LIBRARY); @@ -756,6 +877,7 @@ static void callback_max_files_to_add() view->setMaxFilesToAdd(max_files); } + static QString correctExts(QString exts, bool &correct) { QRegularExpression re("^\\s*[A-Za-z0-9]+(\\s*[|]\\s*[A-Za-z0-9]*)*\\s*$"); From 275ee74cd3d920699fc37c4d56175ab5c5d273fd Mon Sep 17 00:00:00 2001 From: Hans Dijkema Date: Wed, 30 Jul 2025 14:24:16 +0200 Subject: [PATCH 6/9] Preferences window. --- src/filesystem-qt/filesystem-qt.cc | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/filesystem-qt/filesystem-qt.cc b/src/filesystem-qt/filesystem-qt.cc index f164a38e3..9ebe01860 100644 --- a/src/filesystem-qt/filesystem-qt.cc +++ b/src/filesystem-qt/filesystem-qt.cc @@ -127,9 +127,9 @@ const PreferencesWidget FilesystemQt::widgets[] = { WidgetLabel(""), WidgetLabel(N_("Advanced options")), WidgetLabel("--------------------------------------------------------------------------------------------------------------------------------------------------"), - WidgetEntry(N_("Music file extensions to browse :"), WidgetString(CFG_ID, CFG_MUSIC_EXTS, callback_exts)), - WidgetEntry(N_("Booklet files to recognize :"), WidgetString(CFG_ID, CFG_BOOKLET_FILE, callback_booklet)), - WidgetEntry(N_("Cover art files (e.g. folder.jpg) to recognize:"), WidgetString(CFG_ID, CFG_COVER_FILES, callback_covers)) + WidgetEntry(N_("Music file extensions to browse :"), WidgetString(CFG_ID, CFG_MUSIC_EXTS, callback_exts)), + WidgetEntry(N_("Booklet files to recognize :"), WidgetString(CFG_ID, CFG_BOOKLET_FILE, callback_booklet)), + WidgetEntry(N_("Cover art files (e.g. folder.jpg) to recognize :"), WidgetString(CFG_ID, CFG_COVER_FILES, callback_covers)) }; const PluginPreferences FilesystemQt::prefs = {{widgets}}; From 5f60203e21ca61394ccbaf180ad1320e37caf72b Mon Sep 17 00:00:00 2001 From: Hans Dijkema Date: Wed, 30 Jul 2025 14:40:18 +0200 Subject: [PATCH 7/9] Refresh button for the library --- src/filesystem-qt/filesystem-qt.cc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/filesystem-qt/filesystem-qt.cc b/src/filesystem-qt/filesystem-qt.cc index 9ebe01860..87de862b9 100644 --- a/src/filesystem-qt/filesystem-qt.cc +++ b/src/filesystem-qt/filesystem-qt.cc @@ -318,6 +318,13 @@ class FilesystemModel : public QAbstractItemModel this->endResetModel(); } + void refresh() + { + beginConfig(); + _tree = new FilesystemTree(FilesystemTree::DIR, _base_dir, _valid_exts, -1, nullptr); + endConfig(); + } + void setLibraryPath(const QString &library_path) { _base_dir = library_path; @@ -459,6 +466,7 @@ class FilesystemView : public audqt::TreeView public slots: void config(); + void refresh(); public: FilesystemView(); @@ -676,6 +684,11 @@ void FilesystemView::config() } } +void FilesystemView::refresh() +{ + m_model->refresh(); +} + FilesystemView::FilesystemView() { // Configuration Defaults @@ -807,10 +820,13 @@ void * FilesystemQt::get_qt_widget() QWidget *widget = new QWidget(); QVBoxLayout *vbox = new QVBoxLayout(); QPushButton *config = new QPushButton(N_("Configure")); + QPushButton *refresh = new QPushButton(N_("Refresh Library")); QHBoxLayout *hbox = new QHBoxLayout(); hbox->addStretch(1); + hbox->addWidget(refresh); hbox->addWidget(config); FilesystemView::connect(config, &QPushButton::clicked, s_filesystem_view, &FilesystemView::config); + FilesystemView::connect(refresh, &QPushButton::clicked, s_filesystem_view, &FilesystemView::refresh); QFrame *frm = new QFrame(); frm->setFrameStyle(QFrame::NoFrame); From 6ddb5108b54d7db02f34f28e127df0187bdfe33a Mon Sep 17 00:00:00 2001 From: Hans Dijkema Date: Wed, 6 Aug 2025 11:11:38 +0200 Subject: [PATCH 8/9] Fixed a small bug (lost+found had to be filtered out before filling the _entries list) --- src/filesystem-qt/filesystem-qt.cc | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/filesystem-qt/filesystem-qt.cc b/src/filesystem-qt/filesystem-qt.cc index 87de862b9..40943aa32 100644 --- a/src/filesystem-qt/filesystem-qt.cc +++ b/src/filesystem-qt/filesystem-qt.cc @@ -247,19 +247,23 @@ class FilesystemTree QDir::Filters d_filters = QDir::AllDirs | QDir::NoDot | QDir::NoDotDot | QDir::Files | QDir::Readable; d.setFilter(d_filters); - QStringList l = d.entryList(filters, d_filters, QDir::SortFlag::IgnoreCase); + QStringList l_e = d.entryList(filters, d_filters, QDir::SortFlag::IgnoreCase); + QStringList l; + int i; + for(i = 0; i < l_e.size(); i++) { + if (l_e[i] != "lost+found") { + l.append(l_e[i]); + } + } QString p = path(); - int i; for(i = 0; i < l.size(); i++) { - if (l[i] != "lost+found") { - QString new_file = p + _sep + l[i]; + QString new_file = p + _sep + l[i]; - QFileInfo f(new_file); - int k = (f.isDir()) ? DIR : FILE; + QFileInfo f(new_file); + int k = (f.isDir()) ? DIR : FILE; - _entries.append(new FilesystemTree(k, new_file, _exts, i, this)); - } + _entries.append(new FilesystemTree(k, new_file, _exts, i, this)); } } } @@ -537,7 +541,7 @@ void FilesystemView::contextMenuEvent(QContextMenuEvent *evt) } if (file != "") { cover_file_to_open = file; - QAction *action_open_cover = new QAction(N_("Open Cover Art")); + QAction *action_open_cover = new QAction(N_("Open cover art")); connect(action_open_cover, &QAction::triggered, this, [this]() { QUrl f; f = f.fromLocalFile(this->cover_file_to_open); From 20f6eac29b44f4a3087a0d6d94e8c7f309f3c4d5 Mon Sep 17 00:00:00 2001 From: Thomas Lange Date: Sun, 28 Sep 2025 23:46:03 +0200 Subject: [PATCH 9/9] filesystem-qt: Style fixes --- configure.ac | 4 +-- meson.build | 1 + src/filesystem-qt/filesystem-qt.cc | 49 +++++++++++++----------------- src/meson.build | 1 + 4 files changed, 25 insertions(+), 30 deletions(-) diff --git a/configure.ac b/configure.ac index 423bd1ce4..d9e6a1dfe 100644 --- a/configure.ac +++ b/configure.ac @@ -89,7 +89,7 @@ if test "x$USE_GTK" = "xyes" ; then fi if test "x$USE_QT" = "xyes" ; then - GENERAL_PLUGINS="$GENERAL_PLUGINS albumart-qt lyrics-qt playback-history-qt playlist-manager-qt filesystem-qt search-tool-qt song-info-qt statusicon-qt" + GENERAL_PLUGINS="$GENERAL_PLUGINS albumart-qt filesystem-qt lyrics-qt playback-history-qt playlist-manager-qt search-tool-qt song-info-qt statusicon-qt" GENERAL_PLUGINS="$GENERAL_PLUGINS qtui skins-qt" VISUALIZATION_PLUGINS="$VISUALIZATION_PLUGINS blur_scope-qt qt-spectrum vumeter-qt" fi @@ -893,10 +893,10 @@ if test "x$USE_QT" = "xyes" ; then echo " Winamp Classic Interface: yes" echo " Album Art: yes" echo " Blur Scope: yes" + echo " Filesystem View: yes" echo " OpenGL Spectrum Analyzer: $have_qtglspectrum" echo " Playback History: yes" echo " Playlist Manager: yes" - echo " Filesystem View: yes" echo " Search Tool: yes" echo " Song Info: yes" echo " Spectrum Analyzer (2D): yes" diff --git a/meson.build b/meson.build index f92e2b67a..ef9b3bb54 100644 --- a/meson.build +++ b/meson.build @@ -314,6 +314,7 @@ if meson.version().version_compare('>= 0.53') 'Winamp Classic Interface': true, 'Album Art': true, 'Blur Scope': true, + 'Filesystem View': true, 'OpenGL Spectrum Analyzer': get_variable('have_qtglspectrum', false), 'Playback History': true, 'Playlist Manager': true, diff --git a/src/filesystem-qt/filesystem-qt.cc b/src/filesystem-qt/filesystem-qt.cc index 40943aa32..65eaede8a 100644 --- a/src/filesystem-qt/filesystem-qt.cc +++ b/src/filesystem-qt/filesystem-qt.cc @@ -1,6 +1,6 @@ /* * filesystem-qt.cc - * Produced 2025 Hans Dijkema + * Copyright 2025 Hans Dijkema * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: @@ -96,8 +96,8 @@ class FilesystemQt : public GeneralPlugin static const PluginPreferences prefs; static constexpr PluginInfo info = {N_("Filesystem Manager"), PACKAGE, - about, // about - &prefs, // prefs + about, + &prefs, PluginQtOnly}; constexpr FilesystemQt() : GeneralPlugin(info, true) @@ -120,16 +120,13 @@ const char FilesystemQt::about[] = const static WidgetVFileEntry dir_entry = { FileSelectMode::Folder }; const PreferencesWidget FilesystemQt::widgets[] = { - WidgetLabel(N_("Standard options")), - WidgetLabel("--------------------------------------------------------------------------------------------------------------------------------------------------"), - WidgetFileEntry (N_("Music folder:"), WidgetString(CFG_ID, CFG_MUSIC_LIBRARY, callback_folder), dir_entry), + WidgetLabel(N_("Standard options")), + WidgetFileEntry(N_("Music folder:"), WidgetString(CFG_ID, CFG_MUSIC_LIBRARY, callback_folder), dir_entry), WidgetSpin(N_("Maximum files to add to playlist:"), WidgetInt(CFG_ID, CFG_MAX_FILES, callback_max_files_to_add), { 10, 500, 1, N_("files") }), - WidgetLabel(""), - WidgetLabel(N_("Advanced options")), - WidgetLabel("--------------------------------------------------------------------------------------------------------------------------------------------------"), - WidgetEntry(N_("Music file extensions to browse :"), WidgetString(CFG_ID, CFG_MUSIC_EXTS, callback_exts)), - WidgetEntry(N_("Booklet files to recognize :"), WidgetString(CFG_ID, CFG_BOOKLET_FILE, callback_booklet)), - WidgetEntry(N_("Cover art files (e.g. folder.jpg) to recognize :"), WidgetString(CFG_ID, CFG_COVER_FILES, callback_covers)) + WidgetLabel(N_("Advanced options")), + WidgetEntry(N_("Music file extensions to browse:"), WidgetString(CFG_ID, CFG_MUSIC_EXTS, callback_exts)), + WidgetEntry(N_("Booklet files to recognize:"), WidgetString(CFG_ID, CFG_BOOKLET_FILE, callback_booklet)), + WidgetEntry(N_("Cover art files (e.g. folder.jpg) to recognize:"), WidgetString(CFG_ID, CFG_COVER_FILES, callback_covers)) }; const PluginPreferences FilesystemQt::prefs = {{widgets}}; @@ -143,7 +140,7 @@ QWidget *pluginWidget() } ///////////////////////////////////////////////////////////////////////////////////////// -// Internal datastructure to hold directories and files. +// Internal datastructure to hold directories and files. ///////////////////////////////////////////////////////////////////////////////////////// class FilesystemTree @@ -248,13 +245,13 @@ class FilesystemTree d.setFilter(d_filters); QStringList l_e = d.entryList(filters, d_filters, QDir::SortFlag::IgnoreCase); - QStringList l; + QStringList l; int i; - for(i = 0; i < l_e.size(); i++) { - if (l_e[i] != "lost+found") { - l.append(l_e[i]); - } - } + for(i = 0; i < l_e.size(); i++) { + if (l_e[i] != "lost+found") { + l.append(l_e[i]); + } + } QString p = path(); for(i = 0; i < l.size(); i++) { @@ -276,7 +273,6 @@ class FilesystemTree QString FilesystemTree::_sep; QString FilesystemTree::_exts; - ///////////////////////////////////////////////////////////////////////////////////////// // FilesystemModel interfaces with the QTreeView framework of Qt ///////////////////////////////////////////////////////////////////////////////////////// @@ -288,7 +284,7 @@ class FilesystemModel : public QAbstractItemModel QString _valid_exts; FilesystemTree *_tree; bool _in_config; - + public: enum { @@ -303,7 +299,7 @@ class FilesystemModel : public QAbstractItemModel _in_config = false; _tree = new FilesystemTree(FilesystemTree::DIR, _base_dir, _valid_exts, -1, nullptr); } - + ~FilesystemModel() { delete _tree; @@ -633,8 +629,8 @@ void FilesystemView::insertEntries(Playlist &list, bool do_play) assembleFiles(current_selected[i], files, count, max_files_to_add); } - // Add the files to the current playlist or replace the contents of the playlist. - // if do_play == true, the first added entry will also be played. + // Add the files to the current playlist or replace the contents of the playlist. + // if do_play == true, the first added entry will also be played. for(i = 0; i < files.size(); i++) { QUrl u; @@ -767,7 +763,7 @@ FilesystemView::FilesystemView() LOGDBG("View Init"); setAllColumnsShowFocus(true); - //setFrameShape(QFrame::NoFrame); + setFrameShape(QFrame::NoFrame); horizontalScrollBar()->setEnabled(true); setHorizontalScrollBarPolicy(Qt::ScrollBarAsNeeded); @@ -863,7 +859,6 @@ int FilesystemQt::take_message(const char * code, const void *p, int n) // Preferences ///////////////////////////////////////////////////////////////////////////////////////// - static void callback_booklet() { const char *c_booklet = aud_get_str(CFG_ID, CFG_BOOKLET_FILE); @@ -897,7 +892,6 @@ static void callback_max_files_to_add() view->setMaxFilesToAdd(max_files); } - static QString correctExts(QString exts, bool &correct) { QRegularExpression re("^\\s*[A-Za-z0-9]+(\\s*[|]\\s*[A-Za-z0-9]*)*\\s*$"); @@ -957,4 +951,3 @@ static void callback_exts() view->setMusicExts(real_exts); } } - diff --git a/src/meson.build b/src/meson.build index 15a67022d..c9b663394 100644 --- a/src/meson.build +++ b/src/meson.build @@ -81,6 +81,7 @@ endif # Qt-specific plugins if conf.has('USE_QT') subdir('albumart-qt') + subdir('filesystem-qt') subdir('blur_scope-qt') subdir('lyrics-qt') subdir('playback-history-qt')