From c6dbd8f17dac67fc95d4bfa6ef796073a38ff263 Mon Sep 17 00:00:00 2001 From: Damien Elmes Date: Wed, 14 Jun 2006 16:00:00 +0000 Subject: browser: refactor data format, bugfixes; emms: add emms-track-p Subitems are now stored in a tree of "bdata" objects, which is generated when initially displaying the buffer. This makes rendering simpler and also fixes some bugs where tracks were not being sorted correctly in the browser and/or the playlist. Adding items to the playlist now inserts 'group' names when you add a whole album, artist, etc at a time. These names will be thrown away if you run emms-shuffle. The sorting routines will correctly sort the buffer, throwing away the group tags, but then throw an error because the buffer is not the size they expected it to be. Other playlist manipulation routines like next/previous should skip over the group names - if they don't, it's a bug in the playlist code. An example of the new interface is at: http://repose.cx/dump/emms-browser.png darcs-hash:20060614160048-4e3e3-82a8a0d1678b0a2d9fcfc6ca385d5b56963aedbe.gz --- emms-browser.el | 655 +++++++++++++++++++++++++++++++++++--------------------- emms.el | 5 + 2 files changed, 419 insertions(+), 241 deletions(-) diff --git a/emms-browser.el b/emms-browser.el index 7ba603d..b581733 100644 --- a/emms-browser.el +++ b/emms-browser.el @@ -52,19 +52,26 @@ ;; If you just want access to the browser, try M-x ;; emms-browse-by-TYPE, where TYPE is one of artist, album, genre or -;; year. +;; year. These commands can also be used while smart browsing to +;; change the browsing category. ;; If you don't want to activate the code with (emms-devel), you can ;; activate it manually with: ;; (require 'emms-browser) +;; Note this code is very new and is still prone to big changes in the +;; API and breakage. Bug reports are welcome. + ;;; Code: (require 'emms) (require 'emms-cache) (require 'emms-source-file) +(eval-when-compile + (require 'cl)) + ;; -------------------------------------------------- ;; Variables and configuration ;; -------------------------------------------------- @@ -81,6 +88,22 @@ :group 'emms-browser :type 'function) +(defcustom emms-browser-make-name-function + 'emms-browser-make-name-standard + "A function to make names for entries and subentries. +Overriding this function allows you to customise how various elements +are displayed. It is called with two arguments - track and type." + :group 'emms-browser + :type 'function) + +(defcustom emms-browser-insert-track-function + 'emms-browser-insert-track-standard + "A function to insert a track into the playlist. +The default behaviour indents tracks depending on whether you're +adding an album, artist, etc." + :group 'emms-browser + :type 'function) + (defcustom emms-browser-comparison-test 'case-fold "A method for comparing entries in the cache. @@ -123,13 +146,9 @@ Use nil for no sorting." (defvar emms-browser-buffer-name "*EMMS Browser*" "The default buffer name.") -(defvar emms-browser-current-mapping nil +(defvar emms-browser-top-level-hash nil "The current mapping db, eg. artist -> track.") -(make-variable-buffer-local 'emms-browser-current-mapping) - -(defvar emms-browser-current-mapping-type nil - "The current mapping type, eg. 'info-artist") -(make-variable-buffer-local 'emms-browser-current-mapping-type) +(make-variable-buffer-local 'emms-browser-top-level-hash) (defconst emms-browser-mode-map (let ((map (make-sparse-keymap))) @@ -267,10 +286,14 @@ example function is `emms-browse-by-artist'." (bury-buffer)) ;; -------------------------------------------------- -;; Browsing methods - by artist/album/etc +;; Top-level browsing methods - by artist/album/etc ;; -------------------------------------------------- -(defmacro emms-browser-add-category (name track-type &optional expand-func) +;; Since the number of tracks may be rather large, we use a hash to +;; sort the top level elements into various categories. All +;; subelements will be stored in a bdata alist structure. + +(defmacro emms-browser-add-category (name type) "Create an interactive function emms-browse-by-NAME." (let ((funname (intern (concat "emms-browse-by-" name))) (modedesc (concat "Browsing by: " name)) @@ -278,47 +301,42 @@ example function is `emms-browse-by-artist'." `(defun ,funname () ,funcdesc (interactive) - (emms-browser-clear) - (rename-buffer ,modedesc) - (emms-browser-display-by ,track-type ,expand-func) - (goto-char (point-min))))) - -(emms-browser-add-category "artist" 'info-artist 'emms-browser-show-albums) -(emms-browser-add-category "album" 'info-album 'emms-browser-show-titles) -(emms-browser-add-category "genre" 'info-genre 'emms-browser-show-artists) -(emms-browser-add-category "year" 'info-year 'emms-browser-show-artists) - -(defun emms-browser-make-by (field-type) - "Make a mapping with FIELD-TYPE, eg artist -> tracks." - (let ((db (make-hash-table + (let ((hash (emms-browser-make-hash-by ,type))) + (emms-browser-clear) + (rename-buffer ,modedesc) + (emms-browser-render-hash hash ,type) + (setq emms-browser-top-level-hash hash) + (goto-char (point-min)))))) + +(emms-browser-add-category "artist" 'info-artist) +(emms-browser-add-category "album" 'info-album) +(emms-browser-add-category "genre" 'info-genre) +(emms-browser-add-category "year" 'info-year) + +(defun emms-browser-make-hash-by (type) + "Make a hash, mapping with TYPE, eg artist -> tracks." + (let ((hash (make-hash-table :test emms-browser-comparison-test)) field existing-entry) (maphash (lambda (path track) - (setq field (emms-track-get track field-type "misc")) - (setq existing-entry (gethash field db)) + (setq field (emms-track-get track type "misc")) + (setq existing-entry (gethash field hash)) (if existing-entry - (puthash field (cons track existing-entry) db) - (puthash field (list track) db))) + (puthash field (cons track existing-entry) hash) + (puthash field (list track) hash))) emms-cache-db) - db)) - -(defun emms-browser-display-by (field-type &optional expand-func) - "Render a mapping into a browser buffer. -Optional EXPAND-FUNC is a function to call when expanding a -line." - (let ((db (emms-browser-make-by field-type))) - (maphash (lambda (desc data) - (emms-browser-insert-entry desc data expand-func)) - db) - ;; sort - (setq emms-browser-current-mapping db) - ;; FIXME: currently we use a hash for the 'level 1' information, - ;; and an alist for subinfo. that means that this sorting is done - ;; differently to subinfo.. - ;; should we use emms-browser-alpha-sort-function instead? - (emms-with-inhibit-read-only-t - (let ((sort-fold-case t)) - (sort-lines nil (point-min) (point-max)))))) + hash)) + +(defun emms-browser-render-hash (db type) + "Render a mapping (DB) into a browser buffer." + (maphash (lambda (desc data) + ;; reverse the entries so that unsorted tracks are displayed in + ;; ascending order + (emms-browser-insert-top-level-entry desc (nreverse data) type)) + db) + (emms-with-inhibit-read-only-t + (let ((sort-fold-case t)) + (sort-lines nil (point-min) (point-max))))) (defun case-fold-string= (a b) (compare-strings a nil nil b nil nil t)) @@ -329,130 +347,248 @@ line." (define-hash-table-test 'case-fold 'case-fold-string= 'case-fold-string-hash) -;; -------------------------------------------------- -;; Operations on individual lines -;; -------------------------------------------------- - -(defun emms-browser-insert-entry (entry tracks &optional expand-func) - "Add a single ENTRY -> TRACKS mapping to the buffer. -EXPAND-FUNC is an optional func to call when expanding a line." +(defun emms-browser-insert-top-level-entry (entry tracks type) + "Insert a single top level entry into the buffer." (emms-browser-ensure-browser-buffer) (emms-with-inhibit-read-only-t - (insert (emms-propertize entry - 'emms-browser-data tracks - 'emms-browser-level 1 - 'emms-browser-expand-func expand-func - 'face 'emms-browser-tracks-face) "\n"))) + (insert (emms-propertize + entry + 'emms-browser-bdata + (emms-browser-make-bdata-tree + type 1 tracks) + 'face 'emms-browser-tracks-face) "\n"))) -(defun emms-browser-add-tracks () - "Add all the tracks on the current line to the playlist." - (interactive) - (let ((tracks (emms-browser-data-at)) - (count 0) - old-max new-max type name) - (unless tracks - (error "No tracks on current line!")) - (with-current-emms-playlist - (setq old-max (point-max))) - ;; add each of the tracks +;; -------------------------------------------------- +;; Building a subitem tree +;; -------------------------------------------------- + +(defun emms-browser-next-mapping-type (current-mapping) + "Return the next sensible mapping. +Eg. if current-mapping is currently 'info-artist, return 'info-album." + (cond + ((eq current-mapping 'info-artist) 'info-album) + ((eq current-mapping 'info-album) 'info-title) + ((eq current-mapping 'info-genre) 'info-artist) + ((eq current-mapping 'info-year) 'info-artist))) + +(defun emms-browser-make-bdata-tree (type level tracks) + "Build a tree of browser DB elements for tracks." + (emms-browser-make-bdata + (emms-browser-make-bdata-tree-recurse + type level tracks) + ;; with the current hash code, we're guaranteed to have only one + ;; element at the top + (emms-track-get (car tracks) type) + type level)) + +(defun emms-browser-make-bdata-tree-recurse (type level tracks) + "Build a tree of alists based on a list of tracks, TRACKS. +For example, if TYPE is 'info-year, return an alist like: +artist1 -> album1 -> *track* 1.." + (let* ((next-type (emms-browser-next-mapping-type type)) + (next-level (1+ level)) + alist name new-db new-tracks) + ;; if we're at a leaf, the db data is a list of tracks + (if (eq type 'info-title) + tracks + ;; otherwise, make DBs from the sub elements + (setq alist + (emms-browser-make-sorted-alist + next-type tracks)) + (mapcar (lambda (entry) + (setq name (emms-browser-make-name + entry next-type)) + (setq new-tracks (cdr entry)) + (emms-browser-make-bdata + (emms-browser-make-bdata-tree-recurse + next-type next-level new-tracks) + name next-type next-level)) + alist)))) + +(defun emms-browser-make-name (entry type) + "Return a name for ENTRY, used for making a bdata object. +This uses `emms-browser-make-name-function'" + ;; we use cadr because we are guaranteed only one track in entry. + (funcall emms-browser-make-name-function entry type)) + +(defun emms-browser-make-name-standard (entry type) + "Add track numbers to track names. +Apart from tracks, names are displayed without modification." + (if (eq type 'info-title) + (emms-browser-make-name-with-track-number (cadr entry)) + (car entry))) + +(defun emms-browser-make-name-with-track-number (track) + "Concat a track number to the name of track, if one exists." + (let ((tracknum (emms-track-get track 'info-tracknumber))) + (concat + (if (string= tracknum "0") + "" + (concat + (if (eq (length tracknum) 1) + (concat "0" tracknum) + tracknum) + ". ")) + (emms-track-get track 'info-title)))) + +(defun emms-browser-make-bdata (data name type level) + "Return a browser data item from ALIST. +DATA should be a list of DB items, or a list of tracks. +NAME is a name for the DB item. +TYPE is a category the data is organised by, such as 'info-artist. +LEVEL is the number of the sublevel the db item will be placed in." + (list (cons 'type type) + (cons 'level level) + (cons 'name name) + (cons 'data data))) + +(defun emms-browser-make-alist (type tracks) + "Make an alist mapping of TYPE -> TRACKS. +Items with no metadata for TYPE will be placed in 'misc'" + (let (db key existing) (dolist (track tracks) - (setq type (emms-track-get track 'type)) - (setq name (emms-track-get track 'name)) - (cond - ((eq type 'file) - (emms-add-file name)) - ((eq type 'url) - (emms-add-url name))) - (setq count (1+ count))) - (run-mode-hooks 'emms-browser-tracks-added-hook) - (message "Added %d tracks." count))) + (setq key (emms-track-get track type "misc")) + (setq existing (assoc key db)) + (if existing + (setcdr existing (cons track (cdr existing))) + (push (cons key (list track)) db))) + ;; sort the entries we've built + (dolist (item db) + (setcdr item (nreverse (cdr item)))) + db)) -(defun emms-browser-add-tracks-and-play () - "Add all the tracks on the current line, play the first file." - (interactive) - (let (old-pos) - (with-current-emms-playlist - (setq old-pos (point-max))) - (emms-browser-add-tracks) - (with-current-emms-playlist - (goto-char old-pos) - (emms-playlist-select (point))) - ;; FIXME: is there a better way of doing this? - (emms-stop) - (emms-start))) +(defun emms-browser-make-sorted-alist (type tracks) + "Return a sorted alist of TRACKS. +TYPE is the metadata to make the alist by - eg. if it's +'info-artist, an alist of artists will be made." + (emms-browser-sort-alist + (emms-browser-make-alist type tracks) + type)) -(defun emms-browser-data-at (&optional pos) - "Return the tracks at POS (point if not given), or nil if none." - (emms-browser-ensure-browser-buffer) - (save-excursion - ;; move the point to the start of the line, since the trailing new - ;; line is not propertized - (move-beginning-of-line nil) - (emms-with-widened-buffer - (get-text-property (or pos (point)) - 'emms-browser-data)))) +;; -------------------------------------------------- +;; BDATA accessors and predicates +;; -------------------------------------------------- -(defun emms-isearch-buffer () - "Isearch through the buffer." - (interactive) - (goto-char (point-min)) - (when (isearch-forward) - (unless (emms-browser-subitems-visible) - (emms-browser-show-subitems) - (next-line)))) +(defun emms-browser-bdata-level (bdata) + (cdr (assq 'level bdata))) + +(defun emms-browser-bdata-name (bdata) + (cdr (assq 'name bdata))) + +(defun emms-browser-bdata-type (bdata) + (cdr (assq 'type bdata))) + +(defun emms-browser-bdata-data (bdata) + (cdr (assq 'data bdata))) + +(defun emms-browser-bdata-p (obj) + "True if obj is a BDATA object." + (consp (assq 'data obj))) ;; -------------------------------------------------- -;; Expansion/subitem support (experimental) +;; Sorting expanded entries ;; -------------------------------------------------- -(defmacro emms-browser-add-show-category (name field-type &optional - expand-func sort-func) - "Create an interactive function emms-browser-show-FIELD-TYPE. -EXPAND-FUNC is used to further expand subitems if not already -expanded. -SORT-FUNC is called to sort retrieved data." - (let ((fname (intern (concat "emms-browser-show-" name))) - (fdesc (concat "Show " name " under current line"))) - `(defun ,fname () - ,fdesc - (interactive) - (unless (emms-browser-subitems-visible) - (let ((data (emms-browser-make-alist-from-field - ,field-type - (emms-browser-data-at)))) - (when ,sort-func - (setq data (funcall ,sort-func data))) - (emms-browser-insert-subitems data ,expand-func)))))) - -;; -;; create emms-browser-show-* -;; -(emms-browser-add-show-category - "albums" 'info-album - 'emms-browser-show-titles - 'emms-browser-sort-by-name) - -(emms-browser-add-show-category - "artists" 'info-artist - 'emms-browser-show-albums - 'emms-browser-sort-by-name) - -(emms-browser-add-show-category - "titles" 'info-title - nil - 'emms-browser-sort-by-tracks) +(defmacro emms-browser-sort-cadr (sort-func) + "Return a function to sort an alist using SORT-FUNC. +This sorting predicate will compare the cadr of each entry. +SORT-FUNC should be a playlist sorting predicate like +`emms-playlist-sort-by-natural-order'." + `(lambda (a b) + (funcall ,sort-func (cadr a) (cadr b)))) + +(defmacro emms-browser-sort-car (sort-func) + "Return a function to sort an alist using SORT-FUNC. +This sorting predicate will compare the car of each entry. +SORT-FUNC should be a playlist sorting predicate like +`emms-playlist-sort-by-natural-order'." + `(lambda (a b) + (funcall ,sort-func (car a) (car b)))) + +(defun emms-browser-sort-by-track (alist) + "Sort an ALIST by the tracks in each entry. +Uses `emms-browser-track-sort-function'." + (if emms-browser-track-sort-function + (sort alist (emms-browser-sort-cadr + emms-browser-track-sort-function)) + alist)) + +(defun emms-browser-sort-by-name (alist) + "Sort ALIST by keys alphabetically. +Uses `emms-browser-alpha-sort-function'." + (if emms-browser-alpha-sort-function + (sort alist (emms-browser-sort-car + emms-browser-alpha-sort-function)) + alist)) + +(defun emms-browser-sort-alist (alist type) + "Sort ALIST using the sorting function for TYPE." + (let ((sort-func + (cond + ((or + (eq type 'info-album) + (eq type 'info-artist) + (eq type 'info-year) + (eq type 'info-genre)) + 'emms-browser-sort-by-name) + ((eq type 'info-title) + 'emms-browser-sort-by-track) + (t (message "Can't sort unknown mapping!"))))) + (funcall sort-func alist))) + +;; -------------------------------------------------- +;; Subitem operations on the buffer +;; -------------------------------------------------- + +(defun emms-browser-bdata-at-point () + "Return the bdata object at point. +Includes information at point (such as album name), and metadata." + (get-text-property (line-beginning-position) + 'emms-browser-bdata)) + +(defun emms-browser-data-at-point () + "Return the data stored under point. +This will be a list of DB items." + (emms-browser-bdata-data (emms-browser-bdata-at-point))) (defun emms-browser-level-at-point () - "Return the current level at point. -Actually this function returns the value of the first character -on the line, because if point is on a trailing \n it will fail. -Returns 0 if the current line is not an entry." - (let ((val - (get-text-property (line-beginning-position) - 'emms-browser-level))) - (if val - val - 0))) + "Return the current level at point." + (emms-browser-bdata-level (emms-browser-bdata-at-point))) + +(defun emms-browser-expand-one-level () + "Expand the current line by one sublevel." + (interactive) + (let* ((data (emms-browser-data-at-point))) + (save-excursion + (next-line) + (beginning-of-line) + (dolist (data-item data) + (emms-browser-insert-data-item data-item))))) + +(defun emms-browser-insert-data-item (data-item) + "Insert DATA-ITEM into the buffer. +This checks DATA-ITEM's level to determine how much to indent. +The line will have a property emms-browser-bdata storing subitem +information." + (let* ((level (emms-browser-bdata-level data-item)) + (name (emms-browser-bdata-name data-item)) + (indent (emms-browser-make-indent-for-level level))) + (emms-with-inhibit-read-only-t + (insert + (emms-propertize + (concat indent name) + 'emms-browser-bdata data-item + 'face (emms-browser-face-from-level level)) + "\n")))) + +(defun emms-browser-make-indent-for-level (level) + (make-string (* 2 (1- level)) ?\ )) + +(defun emms-browser-face-from-level (level) + "Return a face appropriate for LEVEL." + (intern + (concat "emms-browser-tracks-sub-face-" + (int-to-string (1- level))))) (defun emms-browser-find-entry-more-than-level (level) "Move point to next entry more than LEVEL and return point. @@ -460,14 +596,16 @@ If no entry exits, return nil. Returns point if currently on a an entry more than LEVEL." (let ((old-pos (point)) level-at-point) - (re-search-forward "\n" nil t) - (if (> (emms-browser-level-at-point) level) + (forward-line 1) + (setq level-at-point (emms-browser-level-at-point)) + (if (and level-at-point + (> level-at-point level)) (point) (goto-char old-pos) nil))) (defun emms-browser-subitems-visible () - "True if there are any subentries under point." + "True if there are any subentries visible point." (let ((current-level (emms-browser-level-at-point)) new-level) (save-excursion @@ -475,109 +613,145 @@ Returns point if currently on a an entry more than LEVEL." (when (setq new-level (emms-browser-level-at-point)) (> new-level current-level))))) +(defun emms-browser-subitems-exist () + "True if it's possible to expand the current line." + (not (eq (emms-browser-bdata-type + (emms-browser-bdata-at-point)) + 'info-title))) + +(defun emms-browser-move-up-level () + "Move up one level if possible. +Return true if we were able to move up." + (let ((moved nil) + (continue t) + (current-level (emms-browser-level-at-point))) + (while (and + continue + (zerop (forward-line -1))) + (when (> current-level (emms-browser-level-at-point)) + (setq moved t) + (setq continue nil))) + moved)) + (defun emms-browser-toggle-subitems () "Show or hide (kill) subitems under the current line." (interactive) (if (emms-browser-subitems-visible) (emms-browser-kill-subitems) - (emms-browser-show-subitems))) + (if (emms-browser-subitems-exist) + (emms-browser-show-subitems) + (assert (emms-browser-move-up-level)) + (emms-browser-kill-subitems)))) (defun emms-browser-show-subitems () "Show subitems under the current line." - (let ((func (get-text-property (line-beginning-position) - 'emms-browser-expand-func))) - (if func - (funcall func) - (message "Can't expand further!")))) + (emms-browser-expand-one-level)) (defun emms-browser-kill-subitems () "Remove all subitems under the current line. Stops at the next line at the same level, or EOF." (let ((current-level (emms-browser-level-at-point)) - (kill-whole-line t)) - (save-excursion - (emms-with-inhibit-read-only-t - (while (emms-browser-find-entry-more-than-level current-level) - (kill-line) - (previous-line)))))) - -(defun emms-browser-insert-subitems (subitems &optional expand-func) - "Insert SUBITEMS under the current item. -SUBITEMS is a list of cons cells (desc . data). -emms-browser-level will be set to 1 more than the current level. -Don't add anything if there are already subitems below." - (let ((new-level (1+ (emms-browser-level-at-point))) - desc data) - (save-excursion - (next-line) - (beginning-of-line) - (emms-with-inhibit-read-only-t - (dolist (item subitems) - (setq desc (car item)) - (setq data (cdr item)) - (insert - (emms-propertize (concat (make-string (* 2 (1- new-level)) ?\ ) desc) - 'emms-browser-data data - 'emms-browser-level new-level - 'emms-browser-expand-func expand-func - 'face - (intern - (concat - "emms-browser-tracks-sub-face-" - (int-to-string - (1- new-level))))) - "\n")))))) - -(defun emms-browser-make-alist-from-field (field-type tracks) - "Make an alist mapping of FIELD-TYPE -> TRACKS. -Items with no metadata for FIELD-TYPE will be placed in 'misc'" - (let (db key existing) - (dolist (track tracks) - (setq key (emms-track-get track field-type "misc")) - (setq existing (assoc key db)) - (if existing - (setcdr existing (cons track (cdr existing))) - (push (cons key (list track)) db))) - db)) + (next-line (line-beginning-position 2))) + (emms-with-inhibit-read-only-t + (delete-region next-line + (save-excursion + (while + (emms-browser-find-entry-more-than-level + current-level)) + (line-beginning-position 2)))))) + ;; -------------------------------------------------- -;; Sorting expanded entries +;; Dealing with the playlist (queuing songs, etc) ;; -------------------------------------------------- -(defmacro emms-browser-sort-cadr (sort-func) - "Return a function to sort an alist using SORT-FUNC. -This sorting predicate will compare the cadr of each entry. -SORT-FUNC should be a playlist sorting predicate like -`emms-playlist-sort-by-natural-order'." - `(lambda (a b) - (funcall ,sort-func (cadr a) (cadr b)))) +(defun emms-browser-insert-playlist-group (type group level) + "Insert a group description into the playlist buffer. +Eg. [album] foo bar" + (let ((short-type (substring (symbol-name type) 5))) + (with-current-emms-playlist + (goto-char (point-max)) + (insert + (emms-browser-make-indent-for-level level) + (format "[%s] %s\n" short-type group))))) + +(defun emms-browser-insert-track (track name level) + "Insert a track into the playlist buffer, called NAME. +LEVEL is used to control indentation." + (funcall emms-browser-insert-track-function track name level)) + +(defun emms-browser-insert-track-standard (track name level) + (with-current-emms-playlist + (goto-char (point-max)) + (insert (emms-propertize + (concat + (emms-browser-make-indent-for-level level) + name) + 'face 'emms-playlist-track-face + 'emms-track track) + "\n"))) -(defmacro emms-browser-sort-car (sort-func) - "Return a function to sort an alist using SORT-FUNC. -This sorting predicate will compare the car of each entry. -SORT-FUNC should be a playlist sorting predicate like -`emms-playlist-sort-by-natural-order'." - `(lambda (a b) - (funcall ,sort-func (car a) (car b)))) +(defun emms-browser-add-tracks () + "Add all tracks at point." + (interactive) + (let ((bdata (emms-browser-bdata-at-point))) + (emms-browser-add-bdata-to-playlist + bdata (emms-browser-bdata-level bdata))) + (run-hooks 'emms-browser-tracks-added-hook)) -(defun emms-browser-sort-by-tracks (data) - "Sort an alist DATA by the tracks in each entry. -Uses `emms-browser-track-sort-function'." - (if emms-browser-track-sort-function - (sort data (emms-browser-sort-cadr - emms-browser-track-sort-function)) - data)) +(defun emms-browser-add-tracks-and-play () + "Add all tracks at point, and play the first added track." + (interactive) + (let (old-pos) + (with-current-emms-playlist + (setq old-pos (point-max))) + (emms-browser-add-tracks) + (with-current-emms-playlist + (goto-char old-pos) + (emms-playlist-next) + (emms-playlist-select (point))) + ;; FIXME: is there a better way of doing this? + (emms-stop) + (emms-start))) -(defun emms-browser-sort-by-name (data) - "Sort an alist DATA by keys. -Uses `emms-browser-alpha-sort-function'." - (if emms-browser-alpha-sort-function - (sort data (emms-browser-sort-car - emms-browser-alpha-sort-function)) - data)) +(defun emms-browser-add-bdata-to-playlist (bdata starting-level) + "Add all tracks in BDATA to the playlist." + (let ((type (emms-browser-bdata-type bdata)) + (name (emms-browser-bdata-name bdata)) + (level (emms-browser-bdata-level bdata))) + + ;; adjust the indentation relative to the starting level + (when starting-level + (setq level (- level (1- starting-level)))) + + (unless (eq type 'info-title) + (emms-browser-insert-playlist-group + type name level)) + + (dolist (item (emms-browser-bdata-data bdata)) + (if (not (eq type 'info-title)) + (emms-browser-add-bdata-to-playlist item starting-level) + ;; add full track name as there may not be enough context + (setq name (concat (emms-track-get item 'info-artist) + " - " + ;; track numbers don't make much sense + ;; for individual files + (or (and (> level 1) + name) + (emms-track-get item 'info-title)))) + (emms-browser-insert-track + item name level))))) + +(defun emms-isearch-buffer () + "Isearch through the buffer." + (interactive) + (goto-char (point-min)) + (when (isearch-forward) + (unless (emms-browser-subitems-visible) + (emms-browser-show-subitems)))) ;; -------------------------------------------------- -;; Linked browser and playlist windows (experimental) +;; Linked browser and playlist windows ;; -------------------------------------------------- (defcustom emms-browser-switch-to-playlist-on-add @@ -689,6 +863,5 @@ Returns the playlist window." ;; linked buffer (bury-buffer other-buf))) -;(defun emms (provide 'emms-browser) ;;; emms-browser.el ends here diff --git a/emms.el b/emms.el index e2f2fa3..0bcc686 100644 --- a/emms.el +++ b/emms.el @@ -504,6 +504,11 @@ whenever possible." (run-hook-with-args 'emms-track-initialize-functions track) track)) +(defun emms-track-p (obj) + "True if OBJ is an emms track." + (and (listp obj) + (eq (car obj) '*track*))) + (defun emms-track-type (track) "Return the type of TRACK." (emms-track-get track 'type)) -- cgit v1.2.3