Browse Source

MPDel Browser

master
jao 4 months ago
parent
commit
b34de1b0e8
4 changed files with 306 additions and 4 deletions
  1. +29
    -4
      README.org
  2. BIN
      media/mpdel-browser.png
  3. +276
    -0
      mpdel-browser.el
  4. +1
    -0
      mpdel.el

+ 29
- 4
README.org View File

@@ -25,10 +25,10 @@ up…) and to display and control the current playlist as well as your
stored playlists (e.g., "my favorites", "wake me up", "make me dance",
…).

This is a screenshot of the current playlist with the
currently-playing song:
This is a screenshot of the MPDel music browser and the current
playlist buffer with the currently-playing song:

[[file:media/mpdel-playlist.png]]
[[file:media/mpdel-browser.png]]

MPDel can show information about the currently playing song or any
other song:
@@ -143,6 +143,23 @@ point is used if there is no marked song.
Playlist buffers are refreshed automatically when the MPD server
refreshes them.

*** Browser

The MPDel Browser offers an alternative way of navigating through your
music collection, based on your local music directory structure. To
access the browser's top-level buffer, press ~C-x Z :~ (or call the
interactive command ~mpdel-browser-open~). By default, the top level
buffer gives you access to your music directory, as well as the artist
and albums navigators, playlists and available searches. Pressing ~RET~
on any of these buttons will open the corresponding item; in
particular, pressing ~Music directory~ will allow you to browser your
music files following their directory layout (this works also for MPD
servers such as modipy that can connect to remote music services that
export a virtual music directory structure).

In browser buffers, the regular mpdel keybindings also present in the
navigator or playlists are active.

*** Song

Song buffers display information about a song, either the currently
@@ -158,6 +175,14 @@ By default, MPDel just works and you don't have to customize
anything. Nevertheless, there are a few customization options if you
are that kind of person (and you probably are 😄).

*** Browser

It is possible to customize the layout of the browser's top-level
buffer via the variable ~mpdel-browser-top-level-entries~, and to filter
or rearrange how children buffers show their contents with
~mpdel-browser-list-clean-up-function~. Please see the customization
group ~mpdel-browser~ for more details.

*** Communication with MPD

If you have only one MPD server, you can tell libmpdel how to
@@ -170,7 +195,7 @@ with ~M-x libmpdel-connect-profile~.

As discussed above, you might want to configure ~mpdel-prefix-key~ to
control which global keybinding gives you access to MPDel. This prefix
key is active when ~mpdel-mode~ is active.
key is active when ~mpdel-mode~ is active.

There are several keybinding tables (i.e., keymaps) to modify to your
taste:


BIN
media/mpdel-browser.png View File

Before After
Width: 1221  |  Height: 522  |  Size: 158KB

+ 276
- 0
mpdel-browser.el View File

@@ -0,0 +1,276 @@
;;; mpdel-browser.el --- Browsing MPD entities -*- lexical-binding: t; -*-

;; Copyright (c) 2019, 2020 Jose A Ortega Ruiz

;; Author: Jose A Ortega Ruiz <jao@gnu.org>
;; Keywords: multimedia
;; Url: https://gitlab.petton.fr/mpdel/mpdel
;; Package-requires: ((emacs "25.1"))
;; Version: 1.0.0

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; Executing `mpdel-browser-open' opens the entities browser, letting
;; users to navigate local directories or virtual collections such as
;; playlists offered by the MPD server, as well as search and jump to
;; the "all artists" and "all albums" colections.

;;; Code:

(require 'libmpdel-directory)
(require 'mpdel-core)
(require 'mpdel-tablist)
(require 'mpdel-playlist)

;;; Customization

(defgroup mpdel-browser nil
"Display and navigate listings of MPD entities."
:group 'libmpdel)

(defface mpdel-browser-directory-face
'((t . (:inherit italic)))
"Face for directories in browser.")

(defcustom mpdel-browser-top-level-entries
'(directories empty-line
albums artists empty-line
stored-playlists current-playlist empty-line
search-album search-title search-artist search-filter)
"A list of the entries to show in the browser's top level buffer.

Each entry is shown as a selectable line with the entry's
description; selecting it with the keyboard or mouse will list
its contents in a new buffer."
:type '(repeat (choice (const :tag "Directories" directories)
(const :tag "All albums" albums)
(const :tag "All artists" artists)
(const :tag "Stored playlists" stored-playlists)
(const :tag "Current playlist" current-playlist)
(const :tag "Search by artist" search-artist)
(const :tag "Search by album" search-album)
(const :tag "Search by title" search-title)
(const :tag "Search using filter" search-filter)
(const :tag "Separator" empty-line))))

(defcustom mpdel-browser-list-clean-up-function #'identity
"Function called with the list of entries to be displayed, for clean-up.

The function is called with the list of retreived entries, and
should return a new list of entries, possibly modified and
re-ordered. Use cases include elimination of duplicates (some
backends accumulate renamed songs in their listings) or custom
orderings."
:type 'function)

;;; Formatting of directories in a tablist

(defvar mpdel-browser--song-format (vector (list "Directory / Title" 30 t)
(list "#" 6 nil)
(list "Album" 30 t)
(list "Disk" 4 t)
(list "Date" 5 t)
(list "Artist" 0 t)))

(defun mpdel-browser--directory-format (parent)
"Return the navigel column format for a directory that is a child of PARENT."
(vector (list (or (libmpdel--directory-path parent) "") 60 t)))

(defvar mpdel-browser--retrieving-format (vector (list "Retrieving ..." 60 t))
"Format of the tablist before the children of an entity are known.")

(defun mpdel-browser--includes-songs-p (children)
"Check whether there is any song among CHILDREN."
(cl-some #'libmpdel-song-p children))

(defun mpdel-browser--format (parent-directory children)
"Return format for a directory, given its PARENT-DIRECTORY and its CHILDREN."
(if (mpdel-browser--includes-songs-p children)
mpdel-browser--song-format
(mpdel-browser--directory-format parent-directory)))

(defun mpdel-browser--directory-columns (directory)
"Return a column for DIRECTORY containing its name."
(vector (propertize (or directory "") 'face 'mpdel-browser-directory-face)
"" "" "" "" ""))

(navigel-method mpdel navigel-tablist-format-children ((directory libmpdel-directory) children)
(mpdel-browser--format directory children))

(navigel-method mpdel navigel-tablist-format-children ((_e (eql directories)) _c)
(vector (list "Directories" 60 t)))

(navigel-method mpdel navigel-tablist-format ((_e libmpdel-directory))
mpdel-browser--retrieving-format)

(navigel-method mpdel navigel-tablist-format ((_e (eql directories)))
mpdel-browser--retrieving-format)

(navigel-method mpdel navigel-entity-to-columns ((directory libmpdel-directory))
(mpdel-browser--directory-columns (libmpdel-entity-name directory)))

(navigel-method mpdel navigel-entity-to-columns ((_e (eql directories)))
(vector "Music directory"))

;;; Browser buffers

(defun mpdel-browser--buffer-name (entity)
"Return the name of a browser buffer displaying ENTITY."
(format "* %s *"
(cond ((stringp entity) entity)
((libmpdel-directory-p entity)
(file-name-nondirectory (or (libmpdel--directory-path entity) "")))
(t (libmpdel-entity-name entity)))))

(navigel-method mpdel navigel-buffer-name ((_e (eql directories)))
(mpdel-browser--buffer-name 'directories))

(navigel-method mpdel navigel-buffer-name ((entity libmpdel-directory))
(mpdel-browser--buffer-name entity))

(navigel-method mpdel navigel-entity-buffer ((_e (eql directories)))
(mpdel-browser--buffer-name 'directories))

(navigel-method mpdel navigel-entity-buffer ((entity libmpdel-directory))
(mpdel-browser--buffer-name entity))

(navigel-method mpdel navigel-children
(entity callback &context (major-mode mpdel-browser-mode))
(libmpdel-list entity
(lambda (c)
(funcall callback
(funcall mpdel-browser-list-clean-up-function c)))))

;;; Browser top level
(cl-defmethod libmpdel-entity-name ((_e (eql empty-line)))
"The empty line has an empty name."
"")

(navigel-method mpdel navigel-open ((_e (eql empty-line)) _t)
nil)

(defmacro mpdel-browser--defsearch (thing)
"An utility macro for defining methods associated with a search for THING."
(let* ((entity (intern (format "search-%s" thing)))
(name (format "Search by %s" thing))
(prompt (format "%s: " name))
(type (symbol-name thing)))
`(progn
(cl-defmethod libmpdel-entity-name ((_e (eql ,entity))) ,name)
(navigel-method mpdel navigel-open ((_e (eql ,entity)) _t)
(let ((what (read-from-minibuffer ,prompt)))
(navigel-open (libmpdel-search-criteria-create :type ,type
:what what)
nil))))))

(mpdel-browser--defsearch album)
(mpdel-browser--defsearch artist)
(mpdel-browser--defsearch title)
(mpdel-browser--defsearch filter)

(cl-defmethod libmpdel-entity-name ((_e (eql browser)))
"The name of the top level browser entity."
"Browser")

(cl-defmethod libmpdel-list ((_e (eql browser)) callback)
"Listing of the top level browser, passed to CALLBACK.

This listing is constructed using `mpdel-browser-top-level-entries'."
(funcall callback mpdel-browser-top-level-entries))

(cl-defmethod libmpdel-entity-parent ((_e (eql directories)))
"The new parent of directories is the browser."
'browser)

(navigel-method mpdel navigel-buffer-name ((_e (eql browser)))
(format "* MPDel - %s:%d *" libmpdel-hostname libmpdel-port))

(navigel-method mpdel navigel-entity-buffer ((_e (eql browser)))
(navigel-buffer-name 'browser))

(navigel-method mpdel navigel-tablist-format ((_e (eql browser)))
(vector (list "MPDel Browser" 60 t)))

(navigel-method mpdel navigel-entity-to-columns ((_e (eql browser)))
(vector "Top level"))

(navigel-method mpdel navigel-parent ((_e (eql artists))) 'browser)

(navigel-method mpdel navigel-parent ((_e (eql albums))) 'browser)

(navigel-method mpdel navigel-parent ((_e (eql directories))) 'browser)

(navigel-method mpdel navigel-parent ((_e (eql stored-playlists))) 'browser)

(navigel-method mpdel navigel-parent ((_e (eql current-playlist))) 'browser)

(navigel-method mpdel navigel-children ((_e (eql directories)) callback)
(libmpdel-list 'directories
(lambda (children)
(funcall callback (cons 'browser children)))))

;;;###autoload
(defun mpdel-browser-open ()
"Open the top level MPDel browser buffer."
(interactive)
(mpdel-core-open 'browser))

;;; Major mode

(define-derived-mode mpdel-browser-mode mpdel-tablist-mode "MPDel Browser"
"Mode for browsing directories and their contents")

(navigel-method mpdel navigel-entity-tablist-mode ((_e (eql browser)))
(mpdel-browser-mode))

(navigel-method mpdel navigel-entity-tablist-mode ((_e (eql directories)))
(mpdel-browser-mode))

(navigel-method mpdel navigel-entity-tablist-mode ((_e libmpdel-directory))
(mpdel-browser-mode))

(navigel-method mpdel navigel-entity-tablist-mode ((_e (eql stored-playlists)))
(mpdel-browser-mode))

(defun mpdel-browser--entry-is-parent-directory-p (entity)
"Check whether the given ENTITY is a parent directory."
(let ((name (when (libmpdel-directory-p entity)
(libmpdel-entity-name entity))))
;; Browser buffers showing children of 'directories or the point
;; to their parent via a tablist entry called "Music directory",
;; while real directory children use the conventional "..".
(or (eql 'directories entity)
(eql 'browser entity)
(string= ".." name)
(string= "Music directory" name))))

(cl-defmethod navigel-parent-to-open (entity &context (major-mode mpdel-browser-mode))
"Find parent of ENTITY when in a buffer with MAJOR-MODE mpdel-browser-mode."
(cons (car (cl-find-if #'mpdel-browser--entry-is-parent-directory-p
tabulated-list-entries
:key #'car-safe))
entity))

(define-key mpdel-core-map (kbd ":") #'mpdel-browser-open)

(provide 'mpdel-browser)
;;; mpdel-browser.el ends here

+ 1
- 0
mpdel.el View File

@@ -34,6 +34,7 @@
(require 'mpdel-song)
(require 'mpdel-playlist)
(require 'mpdel-tablist)
(require 'mpdel-browser)

;;; Customization


Loading…
Cancel
Save