aboutsummaryrefslogtreecommitdiff
path: root/emms-pbi.el
diff options
context:
space:
mode:
authorforcer <forcer>2005-09-11 20:05:00 +0000
committerforcer <mwolson@gnu.org>2005-09-11 20:05:00 +0000
commitbb65333ef00df02dbf6bd83294b4df49e64ea325 (patch)
tree5435715fe823d566ac5494bc672088522af5a763 /emms-pbi.el
Initial commit (CVS 2005-09-11)
darcs-hash:20050911200506-2189f-48a136015e33465c3cf09940ce935ec2203df463.gz
Diffstat (limited to 'emms-pbi.el')
-rw-r--r--emms-pbi.el473
1 files changed, 473 insertions, 0 deletions
diff --git a/emms-pbi.el b/emms-pbi.el
new file mode 100644
index 0000000..6480ce8
--- /dev/null
+++ b/emms-pbi.el
@@ -0,0 +1,473 @@
+;;; pbi.el --- Playlist-buffer interface for emms.el
+
+;; Copyright (C) 2003 Free Software Foundation, Inc.
+
+;; Author: Ulrik Jensen <terryp@daimi.au.dk>
+;; Keywords:
+
+;; This file 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 2, or (at your option)
+;; any later version.
+
+;; This file 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 GNU Emacs; see the file COPYING. If not, write to the
+;; Free Software Foundation, Inc., 51 Franklin St, Fifth Floor,
+;; Boston, MA 02110-1301 USA
+
+;;; Commentary:
+
+;; This module provices a playlist-buffer interface (pbi) to EMMS.
+
+;; To use it, just add the following to your emms-configuration
+;; (.emacs, for example):
+
+;; (require 'emms-pbi)
+
+;; If you want the playlist to be generated automagically when you
+;; start playing your music, use the following:
+
+;; (emms-pbi 1)
+
+;; Another common usage, is to just load the playlist when you need
+;; it. This can be achieved by starting emms, and then typing M-x
+;; emms-pbi RET.
+
+;; The default look of the playlist depends on the value of
+;; `emms-track-description-function'. This function takes a
+;; track-value and returns a string, that will then be formatted
+;; further, and inserted into the playlist-buffer. If you want a
+;; different function to handle the base-description, but you only
+;; want that function for emms-pbi, you can override
+;; `emms-pbi-track-description-function', which will fall back to
+;; `emms-track-description-function' when nil
+
+;; You'll probably also want to customize the faces, in which case,
+;; you do M-x customize-group RET emms-pbi RET and tweak.
+
+;; To get id3-tags, and ogg-info, you should look at
+;; emms-info.el. This file provides amongst other things, functions
+;; suitable as values of `emms-track-description-function' (or if you
+;; only want the info in emms-pbi, values of
+;; `emms-pbi-track-description-function').
+
+;; If you use a big playlist, and info, you probably don't want info
+;; to load everything right at once. This can be accomplished through
+;; the `emms-info-later-do'-module, which will gradually load the
+;; playlist. A note on how to make it work with the PBI as well, is
+;; included in that file (and also a part of emms-default.el,
+;; currently the 'cvs-setup)
+
+;; Linenumbering (Changed!)
+;;
+;; Prior versions of emms-pbi had their own linenumbering
+;; functions. But these functions were either error prone or damn
+;; slow. And besides there was already a emacs mode that does exactly
+;; the same: setnu.el So we remove the linenumbering functions in
+;; favour of setnu. You can get setnu from
+;; http://www.wonderworks.com/download/setnu.el. To get linenumbers
+;; just put the following code in your ~/.emacs and put setnu.el
+;; somewhere on your loadpath:
+;;
+;; (require 'setnu)
+;; (add-hook 'emms-pbi-after-build-hook (lambda () (setnu-mode 1)))
+
+;;; Code:
+
+(require 'emms)
+(require 'cl)
+
+(defvar emms-pbi-version "0.2 $Revision: 1.37 $"
+ "EMMS pbi version string.")
+;; $Id: emms-pbi.el,v 1.37 2005/07/09 19:14:57 yonirabkin Exp $
+
+;; Customizations
+
+(defgroup emms-pbi nil
+ "*A playlist-buffer user-interface for EMMS."
+ :group 'emms
+ :prefix "emms-pbi-")
+
+(defcustom emms-pbi-playlist-entry-generate-function 'identity
+ "*The function to call for generating a single item of the
+playlist. This will be called with a string argument FILENAME, and
+should return the text to be inserted in the playlist."
+ :type 'function
+ :group 'emms-pbi)
+
+(defcustom emms-pbi-playlist-entry-max-length nil
+ "*The maximum length of an entry in the playlist. If this is nil,
+the entire string provided by `emms-track-description-function'.
+will be used. Beware, the output of that function is cut off to fit
+the max-length before running
+`emms-pbi-playlist-entry-generate-function'."
+ :type '(restricted-sexp :match-alternatives (integerp 'nil))
+ :group 'emms-pbi)
+
+(defcustom emms-pbi-playlist-buffer-name "*Playlist*"
+ "Name of the buffer to use as a playlist-buffer"
+ :type 'string
+ :group 'emms-pbi)
+
+(defcustom emms-pbi-track-description-function nil
+ "Returns a description for the playlist.
+
+Take track as only argument. If `emms-pbi-track-description-function' is nil,
+`emms-track-description-function' is used instead."
+ :type 'function
+ :group 'emms-pbi)
+
+;; Hooks
+
+(defcustom emms-pbi-after-build-hook nil
+ "Hook that is run after the playlist buffer is built.
+That might be usefull to change the playlist buffer before the
+buffer is set read-only."
+ :type 'hook
+ :group 'emms-pbi)
+
+(defcustom emms-pbi-current-line-face-changed-hook nil
+ "Hook that is called when the face of the current line changes."
+ :type 'hook
+ :group 'emms-pbi)
+
+(defcustom emms-pbi-manually-change-song-hook nil
+ "Hook that is called when the song is manually changed."
+ :type 'hook
+ :group 'emms-pbi)
+
+;; Faces
+(defface emms-pbi-song-face
+ '((((class color) (background light))
+ (:foreground "red"))
+ (((class color) (background dark))
+ (:foreground "red")))
+ "Face used for songs"
+ :group 'emms-pbi)
+
+(defface emms-pbi-current-face
+ '((((class color)(background light))
+ (:foreground "blue" :weight bold))
+ (((class color)(background dark))
+ (:foreground "yellow" :weight bold)))
+ "Face used for the currently played song"
+ :group 'emms-pbi)
+
+;; Variables
+(defvar emms-pbi-suspend-hooks nil
+ "When this variable is t, the hooks updating the playlist stop
+reacting.
+
+If you do something with `emms-playlist', and don't want to regenerate
+the entire playlist-buffer, a good idea is to bind
+`emms-pbi-suspend-hooks' to t when you set `emms-playlist'.")
+
+(defvar emms-pbi-current-overlay nil
+ "Overlay moving with the current track.")
+
+(defvar emms-kill-ring ()
+ "Kill-ring for the playlist buffer.")
+
+(defvar emms-pbi-longest-line 0
+ "The length of the longest line yet inserted.")
+
+;; Entry points
+(defun emms-pbi (arg)
+ "Turn on emms-playlist if prefix argument ARG is a positive integer,
+off otherwise."
+ (interactive "p")
+ (if (and arg (> arg 0))
+ (progn
+ (add-hook 'emms-player-started-hook 'emms-pbi-update-current-face)
+ ;; make sure this is appended!
+ (add-hook 'emms-playlist-changed-hook 'emms-pbi-rebuild-playlist-buffer t)
+ ;; build the playlist, if we have a playlist
+ (if (> (length emms-playlist) 0)
+ (progn
+ (emms-pbi-build-playlist-buffer)
+ (switch-to-buffer emms-pbi-playlist-buffer-name))
+ (message "Empty playlist, won't build playlist-buffer!")))
+ (remove-hook 'emms-player-stopped-hook 'emms-pbi-remove-current-face)
+ (remove-hook 'emms-player-starter-hook 'emms-pbi-add-current-face)
+ (remove-hook 'emms-playlist-changed-hook 'emms-pbi-rebuild-playlist-buffer)))
+
+(defun emms-pbi-shorten-entry-to-max-length (entry)
+ "Cut off an entry-text to make sure it's no longer than
+`emms-pbi-playlist-entry-max-length' characters long."
+ (if (and emms-pbi-playlist-entry-max-length
+ (> (length entry) emms-pbi-playlist-entry-max-length))
+ (substring entry 0 emms-pbi-playlist-entry-max-length)
+ entry))
+
+(defun emms-position-vector (elt vector)
+ "Returns the index of elt in vector"
+ (let ((curidx 0)
+ (residx nil))
+ (while (and (< curidx (length vector)) (eq residx nil))
+ (let ((curelt (aref vector curidx)))
+ (when (equal elt curelt)
+ (setq residx curidx)))
+ (setq curidx (1+ curidx)))
+ residx))
+
+;; This function should probably be phased out, since it depends too
+;; much on emms-info. All uses should be replaced by the function
+;; below:
+(defun emms-pbi-entry-info-updated (track info)
+ "Update the track-entry based on the info"
+ (save-excursion
+ (set-buffer emms-pbi-playlist-buffer-name)
+ (let ((inhibit-read-only t)
+ (pos (emms-position-vector track emms-playlist)))
+ ;; find the entry in the playlist, corresponding to TRACK
+ (when
+ (goto-line (1+ pos))
+ ;; update the text of it - by generating it again simply
+ ;; first, find the index of the entry in the playlist.
+ ;; and save the current properties
+ (delete-region (point-at-bol) (point-at-eol))
+ (emms-pbi-insert-entry (emms-playlist-get-track pos))
+ ;; and update them
+ (emms-pbi-add-properties-current-line)))))
+
+(defun emms-pbi-entry-update-track (track)
+ "Update the playlist entry for TRACK."
+ (let ((trackidx
+ (loop for i from 0 for a across (emms-playlist-get-playlist)
+ if (equal a track) return i)))
+ (when (and trackidx (emms-pbi-valid-index-p trackidx))
+ (emms-pbi-entry-update-idx trackidx))))
+
+(defun emms-pbi-entry-update-idx (trackidx)
+ "Update the playlist entry for the track at index TRACKIDX."
+ (save-excursion
+ (set-buffer emms-pbi-playlist-buffer-name)
+ ;; Find the track
+ (when (emms-pbi-valid-index-p trackidx)
+ (let ((lineidx (1+ trackidx)))
+ (let ((inhibit-read-only t))
+ ;; Erase the line
+ (goto-line lineidx)
+ (delete-region (point-at-bol) (point-at-eol))
+ ;; Insert the track and add properties
+ (emms-pbi-insert-entry (emms-playlist-get-track trackidx))
+ (emms-pbi-add-properties-current-line)
+ ;; Make sure the overlay is in place
+ (emms-pbi-update-current-face))))))
+
+(defun emms-pbi-entry-generate (track)
+ "Generate an entry for TRACK in the playlist-buffer.
+
+This uses `emms-pbi-track-description-function', or if that is nil, it defaults
+to `emms-track-description'."
+ (if emms-pbi-track-description-function
+ (funcall emms-pbi-track-description-function track)
+ ;; default to the emms way
+ (funcall emms-track-description-function track)))
+
+(defun emms-pbi-insert-entry (track)
+ "Insert an entry for TRACK in the playlist."
+ (let ((inhibit-read-only t)
+ (line (emms-pbi-shorten-entry-to-max-length
+ (emms-pbi-entry-generate track))))
+ (insert line)
+ (emms-pbi-add-properties-current-line)
+ ;; for the convenience of other modules, keep track of the longest
+ ;; line yet.
+ (setq emms-pbi-longest-line (max emms-pbi-longest-line (length line)))))
+
+(defun emms-pbi-rebuild-playlist-buffer ()
+ "This function rebuilds the playlist-buffer if necessary."
+ (unless emms-pbi-suspend-hooks
+ (emms-pbi-build-playlist-buffer)))
+
+;; Function for building the playlist
+
+(defun emms-pbi-build-playlist-buffer ()
+ "Build a playlist-buffer based on the current playlist."
+ (save-excursion
+ (set-buffer (get-buffer-create emms-pbi-playlist-buffer-name))
+ (let ((playlist-length (length emms-playlist))
+ (idx 0)
+ (inhibit-read-only t))
+ ;; reset the buffer
+ (erase-buffer)
+ ;; insert all elements
+ (while (< idx playlist-length)
+ (emms-pbi-insert-entry (emms-playlist-get-track idx))
+ (insert "\n")
+ (setq idx (1+ idx)))
+ ;; Initialise the buffer variables
+ ;; remove the last line
+ (emms-pbi-update-current-face)
+ (delete-backward-char 1)
+ (run-hooks 'emms-pbi-after-build-hook)
+ (setq buffer-read-only t)
+ (emms-pbi-mode)
+ ;; as the last thing we do, update the current-face.
+ (when emms-player-playing-p
+ (emms-pbi-update-current-face)))))
+
+;; Updating the currently playing face
+(defun emms-pbi-update-current-face ()
+ "Updates the file line with the current-face"
+ (when (get-buffer emms-pbi-playlist-buffer-name)
+ (save-excursion
+ (set-buffer emms-pbi-playlist-buffer-name)
+ (let ((inhibit-read-only t))
+ ;; don't try to `1+' the value `nil'
+ (unless (null emms-playlist-current)
+ (goto-line (1+ emms-playlist-current))
+ (if (overlayp emms-pbi-current-overlay)
+ (move-overlay emms-pbi-current-overlay
+ (point-at-bol) (point-at-eol))
+ (setq emms-pbi-current-overlay
+ (make-overlay (point-at-bol) (point-at-eol)))
+ (overlay-put emms-pbi-current-overlay 'face 'emms-pbi-current-face)))))))
+
+;;Handling faces & properties
+(defun emms-pbi-add-properties-current-line ()
+ "Adds the correct faces and other properties to the current line"
+ ;; Default face
+ (let ((idx (emms-pbi-return-current-line-index)))
+ (add-text-properties (point-at-bol) (point-at-eol)
+ '(face emms-pbi-song-face))
+ (run-hooks 'emms-pbi-current-line-face-changed-hook)))
+
+(defun emms-pbi-play-current-line ()
+ "Play the current line"
+ (interactive)
+ (let ((new-idx (emms-pbi-return-current-line-index)))
+ ;; check boundaries
+ (when (and new-idx (> new-idx -1) (< new-idx (length emms-playlist)))
+ (emms-stop)
+ (emms-playlist-set-current new-idx)
+ (emms-start)
+ (run-hooks 'emms-pbi-manually-change-song-hook))))
+
+(defun emms-pbi-show-current-line ()
+ "Show filename and info for track on current line."
+ (interactive)
+ (let ((idx (emms-pbi-return-current-line-index)))
+ (message "Filename: %s; Info: %s"
+ (emms-track-name
+ (emms-playlist-get-track idx))
+ (emms-playlist-get idx))))
+
+;; Major-mode for the playlist-buffer
+(define-derived-mode emms-pbi-mode fundamental-mode "EMMS playlist"
+ (suppress-keymap emms-pbi-mode-map t)
+ (define-key emms-pbi-mode-map (kbd "n") 'emms-next)
+ (define-key emms-pbi-mode-map (kbd "p") 'emms-previous)
+ (define-key emms-pbi-mode-map (kbd "c") 'emms-pbi-recenter)
+ (define-key emms-pbi-mode-map (kbd "l") 'emms-pbi-recenter)
+ (define-key emms-pbi-mode-map (kbd "C-x C-s") 'emms-pbi-export-playlist)
+ (define-key emms-pbi-mode-map (kbd "C-k") 'emms-pbi-kill-line)
+ (define-key emms-pbi-mode-map (kbd "d") 'emms-pbi-kill-line)
+ (define-key emms-pbi-mode-map (kbd "C-y") 'emms-pbi-yank)
+ (define-key emms-pbi-mode-map (kbd "s") 'emms-stop)
+ (define-key emms-pbi-mode-map (kbd "f") 'emms-pbi-show-current-line)
+ (define-key emms-pbi-mode-map (kbd "RET") 'emms-pbi-play-current-line)
+ (define-key emms-pbi-mode-map (kbd "q") 'bury-buffer)
+ (define-key emms-pbi-mode-map (kbd "<mouse-2>") 'emms-pbi-play-current-line)
+ (define-key emms-pbi-mode-map (kbd "Q") 'emms-pbi-quit)
+ (define-key emms-pbi-mode-map (kbd "?") 'describe-mode))
+
+(defun emms-pbi-quit ()
+ "Stops emms and kill the playlist buffer"
+ (interactive)
+ (emms-stop)
+ (kill-buffer emms-pbi-playlist-buffer-name))
+
+(defun emms-playlist-empty-p ()
+ (= (length emms-playlist) 0))
+
+(defun emms-pbi-kill-line ()
+ "Kill the playlist line the cursor is currently on and update
+ the playlist accordingly."
+ (interactive)
+ (if (emms-playlist-empty-p)
+ (message "One cannot remove what is not there grasshopper")
+ (let ((idx (emms-pbi-return-current-line-index))
+ (inhibit-read-only t))
+ ;; remove from buffer
+ (goto-char (point-at-bol))
+ (kill-line 1)
+ ;; push track onto emms-kill-ring
+ (push (aref emms-playlist idx)
+ emms-kill-ring)
+ ;; now delete the entry from the playlist. - making sure that
+ ;; the entire list isn't regenerated
+ (let ((emms-pbi-suspend-hooks t))
+ (emms-playlist-remove idx))
+ (if (numberp emms-playlist-current)
+ ;; this deals with edge cases, explicit
+ (cond ((and emms-player-playing-p
+ (= idx emms-playlist-current))
+ (emms-stop) (emms-start)
+ (emms-pbi-update-current-face))
+ ((= idx emms-playlist-current)
+ (emms-pbi-update-current-face))
+ ((< idx emms-playlist-current)
+ (setq emms-playlist-current
+ (1- emms-playlist-current))))
+ (emms-stop))))) ;; stop if playlist empty after kill
+
+(defun emms-pbi-yank ()
+ "Yank a filename from `kill-ring' into the playlist."
+ (interactive)
+ (let ((track (pop emms-kill-ring))
+ (inhibit-read-only t)
+ (idx (emms-pbi-return-current-line-index)))
+ (if (and track (emms-player-for track))
+ ;; only insert files that actually exist, and can be played.
+ ;; insert it into the buffer
+ (save-excursion
+ (goto-char (point-at-bol))
+ (insert "\n")
+ (forward-line -1)
+ (goto-char (point-at-bol))
+ (emms-pbi-insert-entry track)
+ ;; insert it into the playlist
+ (let ((emms-pbi-suspend-hooks t))
+ (emms-playlist-add (list track) idx))))
+ (message "No playable track in emms-kill-ring!")))
+
+(defun emms-pbi-return-current-line-index ()
+ "Return the index position in the playlist of the current line."
+ (1- (count-lines (point-min) (point-at-eol))))
+
+(defun emms-pbi-valid-index-p (idx)
+ "Return non-nil if IDX is a valid index in the current playlist."
+ (and idx (> idx -1) (< idx (length emms-playlist))))
+
+(defun emms-pbi-recenter ()
+ "Center on current playing track"
+ (interactive)
+ (let ((line (1+ emms-playlist-current)))
+ (goto-line line)
+ (recenter)))
+
+(defun emms-pbi-export-playlist (file)
+ (interactive "Fsave playlist:")
+ (let ((buffer (find-file-noselect file t)))
+ (set-buffer buffer)
+ (prin1 emms-playlist buffer)
+ (save-buffer)
+ (kill-buffer buffer)))
+
+(defun emms-pbi-open-playlist (file)
+ (interactive "fOpen playlist:")
+ (let ((buffer (find-file-noselect file)))
+ (set-buffer buffer)
+ (beginning-of-buffer)
+ (emms-playlist-set-playlist (read buffer))
+ (kill-buffer buffer)))
+
+(provide 'emms-pbi)
+;;; emms-pbi.el ends here