From bb65333ef00df02dbf6bd83294b4df49e64ea325 Mon Sep 17 00:00:00 2001 From: forcer Date: Sun, 11 Sep 2005 20:05:00 +0000 Subject: Initial commit (CVS 2005-09-11) darcs-hash:20050911200506-2189f-48a136015e33465c3cf09940ce935ec2203df463.gz --- emms-pbi.el | 473 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 473 insertions(+) create mode 100644 emms-pbi.el (limited to 'emms-pbi.el') 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 +;; 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 "") '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 -- cgit v1.2.3