aboutsummaryrefslogtreecommitdiff
path: root/emms-tageditor.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-tageditor.el
Initial commit (CVS 2005-09-11)
darcs-hash:20050911200506-2189f-48a136015e33465c3cf09940ce935ec2203df463.gz
Diffstat (limited to 'emms-tageditor.el')
-rw-r--r--emms-tageditor.el455
1 files changed, 455 insertions, 0 deletions
diff --git a/emms-tageditor.el b/emms-tageditor.el
new file mode 100644
index 0000000..f093d85
--- /dev/null
+++ b/emms-tageditor.el
@@ -0,0 +1,455 @@
+;;; emms-tageditor.el --- Info-editor for EMMS
+
+;; Copyright (C) 2004 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 file provides an info-editor for EMMS. It also provides, as an
+;; option, functions for integrating this with the
+;; playlist-buffer-interface as well as the mark-module for the pbi.
+
+;; To activate the basic editor, use this in your EMMS-configuration:
+
+;; (require 'emms-tageditor)
+
+;; And call M-x emms-tageditor-edit-current RET, when you find a song
+;; with a tag that needs to be changed.
+
+;; To use the pbi-functionality, add the following to your
+;; configuration:
+
+;; (emms-tageditor-pbi-mode 1)
+
+;; Which will add 'e' as a key, to edit the track under the point in
+;; the pbi.
+
+;; To use the extended pbi-functionality, with `emms-pbi-mark', use:
+
+;; (emms-tageditor-pbi-mark-mode 1)
+
+;; Which likewise will bind 'E' to edit all the tracks marked as a
+;; whole, in a way that allows you to set the "album"-tag of each
+;; track, or f.ex. to match the filename to a regexp, and set a group
+;; from that regexp as the title-tag, or any other.
+
+;;; Code:
+
+(require 'emms)
+(require 'emms-info)
+(require 'emms-pbi)
+(require 'emms-pbi-mark)
+(require 'widget)
+(require 'wid-edit)
+
+;; Custom
+(defgroup emms-tageditor nil
+ "*A wizard-style tageditor for EMMS."
+ :group 'emms
+ :prefix "emms-tageditor-")
+
+(defcustom emms-tageditor-buffer-name "*emms-tageditor*"
+ "The name of the buffer used for tag-editing."
+ :type 'string
+ :group 'emms-tageditor)
+
+;; Variables
+(defvar emms-tageditor-current-tracks []
+ "A vector of the tracks currently being edited.")
+
+(defvar emms-tageditor-current-infos []
+ "A vector of the info-ojects currently being edited.")
+
+(defvar emms-tageditor-message nil
+ "A message to show with the form, if non-nil.")
+
+(defvar emms-tageditor-widgets nil
+ "A hash map of the widgets on the screen.")
+
+;; Hashtable interface
+(defun emms-tageditor-get-widget (trackidx fieldname)
+ "Return the widget of FIELDNAME, for the track with TRACKIDX"
+ (let ((symbol (emms-tageditor-get-widget-id trackidx fieldname)))
+ (gethash symbol emms-tageditor-widgets)))
+
+(defun emms-tageditor-set-widget (trackidx fieldname widget)
+ "Save a reference to WIDGET, as FIELDNAME of the track with TRACKIDX."
+ (let ((symbol (emms-tageditor-get-widget-id trackidx fieldname)))
+ (puthash symbol widget emms-tageditor-widgets)))
+
+;; Helper function for the hashtable
+(defun emms-tageditor-get-widget-id (trackidx fieldname)
+ "Get a symbol ID of the FIELDNAME widget of TRACKIDX."
+ (intern (concat "-trackidx-" (number-to-string trackidx)
+ "-" (symbol-name fieldname))))
+
+(defun emms-tageditor-read-tag (trackidx)
+ "Read the form for a single track, and parse it into an info-object."
+ (let ((info (aref emms-tageditor-current-infos trackidx))
+ (track (aref emms-tageditor-current-tracks trackidx))
+ (tags '(title artist album note)))
+ (while tags
+ (let* ((tag (car tags))
+ (infotag (intern (concat "emms-info-" (symbol-name tag)))))
+ (eval `(setf (,infotag ,info)
+ ,(widget-value (emms-tageditor-get-widget
+ trackidx
+ tag)))))
+ (setq tags (cdr tags)))
+ info))
+
+;; Parsing the form
+(defun emms-tageditor-read-tags (tracks)
+ "Create a new list of info-object from the form, and return it."
+ (mapcar 'emms-tageditor-read-tag tracks))
+
+;; Event-handling
+(defun emms-tageditor-save (widget &rest ignore)
+ "Save the info of a single tag."
+ ;; This function only saves a single form. Figure out which track
+ ;; this is bound to, by extracting the trackidx from the
+ (let* ((trackidx (widget-get widget :trackidx))
+ (track (aref emms-tageditor-current-tracks trackidx)))
+ ;; Let's make sure we have an info-source capable of writing tags.
+ (if (funcall (emms-info-method-for track) 'set)
+ (progn
+ ;; we have an info-method for it, let's set it.
+ (emms-info-set track
+ (emms-tageditor-read-tag trackidx))
+ (when (and (featurep 'emms-pbi)
+ (get-buffer emms-pbi-playlist-buffer-name))
+ ;; If a playlist is available, it's info might need to be updated
+ ;; for this track.
+ (emms-pbi-entry-update-track track)
+ (when (= (length emms-tageditor-current-tracks) 1)
+ ;; pressing save should kill the buffer when only one track is
+ ;; being edited.
+ (emms-tageditor-cleanup))))
+ ;; if the above returned nil, no function to save info for this
+ ;; track has been made! signal an error and escape!
+ (message (format (concat "Track %s doesn't have an associated info-method "
+ " capable of saving data")
+ (emms-track-name track))))))
+
+(defun emms-tageditor-save-all ()
+ "Save all entries currently being edited."
+ ;; Loop through all forms, and save them
+ (let ((idx 0))
+ (while (< idx (length emms-tageditor-current-tracks))
+ (emms-tageditor-save (emms-tageditor-get-widget idx 'save))
+ (setq idx (1+ idx))))
+ ;; Always cleanup when saving everything
+ (emms-tageditor-cleanup))
+
+(defun emms-tageditor-cancel (&rest ignore)
+ (emms-tageditor-cleanup))
+
+(defun emms-tageditor-create-string (rep times)
+ "Concat TIMES occurances of REP into a string and return it."
+ (if (> times 1)
+ (concat (emms-tageditor-create-string rep (1- times)) rep)
+ rep))
+
+;; Setting up the form, and destroying it
+(defun emms-tageditor-create-widgets (trackidx info)
+ "Create widgets for a single track-form"
+ (let ((inhibit-read-only t)
+ (track (aref emms-tageditor-current-tracks trackidx))
+ (info (aref emms-tageditor-current-infos trackidx)))
+ (goto-char (point-min))
+ (widget-insert (format "Editing tag for track: %s (%s)\n"
+ (emms-track-name track)
+ (symbol-name
+ (emms-track-type track))))
+ (widget-insert (concat "----------------------------------------"
+ "----------------------------------------"
+ "\n"))
+ ;; Insert the tags
+ (let ((tags '(title artist album note)))
+ (while tags
+ (let* ((tag (car tags))
+ (info-tag (intern (concat "emms-info-" (symbol-name tag))))
+ (tag-name
+ (concat (upcase-initials (symbol-name tag)) ":"
+ (emms-tageditor-create-string " "
+ (- 10 (1+ (length (symbol-name tag))))))))
+ (widget-insert tag-name)
+ (eval `(emms-tageditor-set-widget
+ trackidx (quote ,tag)
+ (widget-create 'editable-field
+ :size 69
+ :trackidx trackidx
+ :value (,info-tag ,info))))
+ (when (not (= (length tags) 1))
+ (widget-insert "\n")))
+ (setq tags (cdr tags))))
+ ;; Insert the rest
+ (widget-insert (concat "\n"
+ "----------------------------------------"
+ "----------------------------------------"
+ "\n"))
+ (emms-tageditor-set-widget
+ trackidx 'save
+ (widget-create 'push-button
+ :notify 'emms-tageditor-save
+ :trackidx trackidx
+ :help-echo "Save changes to this tag"
+ "Save"))
+ (widget-insert " ")
+ (widget-create 'push-button
+ :notify 'emms-tageditor-cancel
+ :help-echo "Cancel changes"
+ "Cancel")))
+
+(defun emms-tageditor-cleanup ()
+ "Clean up and exit the tageditor."
+ ;; delete all widgets
+ (let ((idx 0))
+ (while (< idx (length emms-tageditor-current-tracks))
+ (when emms-tageditor-widgets
+ (let ((tags '(title artist album note save)))
+ (while tags
+ (let ((tag (car tags)))
+ (eval `(widget-delete (emms-tageditor-get-widget idx tag))))
+ (setq tags (cdr tags)))))
+ ;; continue idx loop
+ (setq idx (1+ idx))))
+ ;; kill the buffer & delete the hashmap
+ (setq emms-tageditor-widgets nil)
+ (kill-buffer (get-buffer-create emms-tageditor-buffer-name)))
+
+(defun emms-tageditor-replace-regexp (regexp rep string &optional fixedcase literal subexp start)
+ "Compatibility wrapper for replace-regexp-in-string/replace-in-string."
+ (if (featurep 'xemacs)
+ (replace-in-string regexp rep string fixedcase literal subexp start)
+ (replace-regexp-in-string regexp rep string fixedcase literal subexp start)))
+
+(defun emms-tageditor-replace-create-replacement (replace-with trackidx)
+ (let ((info (aref emms-tageditor-current-infos trackidx))
+ (track (aref emms-tageditor-current-tracks trackidx)))
+ (setq replace-with (emms-tageditor-replace-regexp "$TITLE" (emms-info-title info) replace-with))
+ (setq replace-with (emms-tageditor-replace-regexp "$ALBUM" (emms-info-album info) replace-with))
+ (setq replace-with (emms-tageditor-replace-regexp "$ARTIST" (emms-info-artist info) replace-with))
+ (setq replace-with (emms-tageditor-replace-regexp "$NOTE" (emms-info-note info) replace-with))
+ (setq replace-with (emms-tageditor-replace-regexp "$TRACKNAME" (emms-track-name track) replace-with)))
+ replace-with)
+
+(defun emms-tageditor-replace-tag (field regexp replace-with)
+ "Replace REGEXP with REPLACE-WITH in all fields of type FIELD."
+ (let ((idx 0))
+ (while (< idx (length emms-tageditor-current-tracks))
+ ;; Find the widget for the current track
+ (let ((widget (emms-tageditor-get-widget idx field)))
+ (let* ((str (widget-value widget))
+ (str (emms-tageditor-replace-regexp regexp replace-with str)))
+ (if (string= "$SET" regexp)
+ (widget-value-set
+ widget
+ (emms-tageditor-replace-create-replacement replace-with idx))
+ (widget-value-set
+ widget
+ (emms-tageditor-replace-create-replacement str idx)))))
+ (setq idx (1+ idx)))))
+
+(defun emms-tageditor-replace-tags (&optional field regexp replace-with)
+ "Replace REGEXP with REPLACE-WITH in the widgets matching FIELD."
+ (interactive)
+ (setq field (or field (intern (completing-read
+ "Select which tags to replace in: "
+ '(("all" . all) ("title" . title)
+ ("artist" . artist) ("album" . album)
+ ("note" . note))
+ nil t "title"))))
+ (setq regexp (or regexp (read-from-minibuffer "Regexp to replace: ")))
+ (setq replace-with (or replace-with (read-from-minibuffer (concat "Replace regexp " regexp " with: "))))
+ ;; Having all input, let's continue to act on it.
+ (when (and field regexp replace-with)
+ ;; two cases, 'all or something else
+ (if (equal field 'all)
+ (progn
+ ;; We need a sweep-search of all tag-fields
+ (let ((tags '(title artist album note)))
+ (while tags
+ (emms-tageditor-replace-tag (car tags) regexp replace-with)
+ (setq tags (cdr tags)))))
+ ;; only search the field called field
+ (emms-tageditor-replace-tag field regexp replace-with))
+ ;; we've probably changed some widget values, so we need to make
+ ;; them count.
+ (widget-setup)))
+
+;; Setting up the buffer
+(defun emms-tageditor-edit (tracks &optional infos)
+ "Open an editor for the vector TRACKS.
+
+Optionally, use the vector INFOS as the default info for each track,
+and use the function SAVEFUNCTION as the event-handler for each
+save-button."
+ ;; Save variables
+ (setq emms-tageditor-current-tracks tracks)
+ (if infos
+ (setq emms-tageditor-current-infos infos)
+ ;; Otherwise, create the vector of infos by loading them.
+ (setq emms-tageditor-current-infos
+ (make-vector (length emms-tageditor-current-tracks)
+ nil))
+ (let ((idx 0))
+ (while (< idx (length emms-tageditor-current-tracks))
+ (setf (aref emms-tageditor-current-infos idx)
+ ;; should we allow cache here?
+ (emms-info-get (aref emms-tageditor-current-tracks idx)))
+ (setq idx (1+ idx)))))
+ ;; Kill the buffer, then recreate it. Otherwise, everything will be
+ ;; in one big widget.
+ (kill-buffer (get-buffer-create emms-tageditor-buffer-name))
+ (switch-to-buffer (get-buffer-create emms-tageditor-buffer-name))
+ ;; Initialise buffer
+ (kill-all-local-variables)
+ (widget-minor-mode 1)
+ ;; Setup widget hashmap,
+ (setq emms-tageditor-widgets (make-hash-table :test 'equal))
+ ;; and create the widgets
+ (let ((idx 0))
+ (while (< idx (length emms-tageditor-current-tracks))
+ (emms-tageditor-create-widgets idx
+ (aref emms-tageditor-current-infos idx))
+ (widget-insert "\n\n")
+ (setq idx (1+ idx)))
+ ;; Create the save _all_ widget?
+ ;; setup the help-message
+ (when emms-tageditor-message
+ (goto-char (point-max))
+ (widget-insert (concat "\n"
+ "........................................"
+ "........................................"
+ "\n"))
+ (widget-insert emms-tageditor-message)
+ (setq emms-tageditor-message nil))
+ (use-local-map widget-keymap)
+ ;; Bind some additional keys
+ (widget-setup)
+ (local-set-key (kbd "C-x C-s") (lambda () (interactive) (emms-tageditor-save-all)))
+ (local-set-key (kbd "C-c C-r") 'emms-tageditor-replace-tags)
+ (local-set-key (kbd "ESC") (lambda () (interactive) (emms-tageditor-cancel)))))
+
+;; Entry function
+(defun emms-tageditor-edit-current ()
+ "Edit the info of the currently playing track"
+ (interactive)
+ (emms-tageditor-edit (vconcat (list (emms-playlist-current-track)))))
+
+;; Integrating with emms-pbi
+(defvar emms-tageditor-pbi-mark-message
+ "When editing multiple files, some things works a bit
+differently. First of all, to save *all* changes made to tracks, use
+C-x C-s.
+
+The changes to each individual track, can be saved by using the
+corresponding Save-buttons.
+
+To utilize the full power of this mode of editing, you should use
+M-x emms-tageditor-replace-tags RET, bound to C-c C-r.
+
+When using `emms-tageditor-replace-tags', you have the following
+special keyword available:
+
+For matching:
+
+ $SET -- Attempting to replace this value with anything, will tell
+ the function to simply override the previous value.
+
+For what to replace with:
+
+ $TRACKNAME -- The trackname (for file-type tracks, the full filename)
+ $TITLE -- The (saved) title of this track.
+ $ARTIST -- Likewise, with the artist
+ $ALBUM -- Likewise, with the album
+ $ALBUM -- Likewise, with the note.
+
+NOT IMPLEMENTED YET:
+If the power of that function doesn't fit your needs, you can use M-x
+emms-tageditor-toggle-read-only RET, bound to C-c C-t. This function
+will make the buffer read-only, which means you can use the regular
+editing functions on the entire buffer. This means that doing an M-x
+replace-regexp RET, won't halt if it matches any of the text outsie
+widgets, as it would otherwise.")
+
+(defun emms-tageditor-pbi-mode (&optional arg)
+ "Register the intergration with the playlist-buffer interface for EMMS.
+
+Turn the registration on, if and only if ARG is a positive integer,
+off otherwise."
+ (interactive "p")
+ (if (not (featurep 'emms-pbi))
+ (message "You need `emms-pbi' loaded to use this!")
+ (if (and (numberp arg) (< 0 arg))
+ (add-hook 'emms-pbi-mode-hook 'emms-tageditor-pbi-register)
+ (remove-hook 'emms-pbi-mode-hook 'emms-tageditor-pbi-register))))
+
+(defun emms-tageditor-pbi-register ()
+ "Register keybindings for the playlist-buffer interface.
+
+Should be run in `emms-pbi-mode-hook'."
+ (local-set-key (kbd "e")
+ 'emms-tageditor-pbi-edit-current-line))
+
+(defun emms-tageditor-pbi-edit-current-line ()
+ "Edit the track under point."
+ (interactive)
+ (if (not (featurep 'emms-pbi))
+ (message "You need `emms-pbi' loaded to use this!")
+ ;; Fetch track under current line
+ (let ((curidx (emms-pbi-return-current-line-index)))
+ (when (emms-pbi-valid-index-p curidx)
+ (other-window 1)
+ (emms-tageditor-edit (vconcat (list (emms-playlist-get-track curidx))))))))
+
+;; Integrating with pbi-mark
+(defun emms-tageditor-pbi-mark-mode (&optional arg)
+ "Register the intergration with the playlist-buffer-marks for EMMS.
+
+Turn the integration on, if and only if ARG is a positive integer, off
+otherwise."
+ (interactive "p")
+ (if (not (featurep 'emms-pbi-mark))
+ (message "You need `emms-pbi-mark' loaded to use this!")
+ (if (and (numberp arg) (< 0 arg))
+ (add-hook 'emms-pbi-mode-hook 'emms-tageditor-pbi-mark-register)
+ (remove-hook 'emms-pbi-mode-hook 'emms-tageditor-pbi-mark-register))))
+
+(defun emms-tageditor-pbi-mark-register ()
+ "Register keybindings for the playlist-buffer interface marking functions.
+
+Should be run in `emms-pbi-mode-hook'."
+ (local-set-key (kbd "E")
+ 'emms-tageditor-pbi-mark-edit-marked-entries))
+
+(defun emms-tageditor-pbi-mark-edit-marked-entries ()
+ "Edit all marked entries as one, using a special editor."
+ (interactive)
+ (if (not (featurep 'emms-pbi-mark))
+ (message "You need `emms-pbi-mark' loaded to use this!")
+ (other-window 1)
+ (setq emms-tageditor-message emms-tageditor-pbi-mark-message)
+ (emms-tageditor-edit (vconcat (emms-pbi-mark-get-marked)))
+ (goto-char (point-min))))
+
+(provide 'emms-tageditor)
+;;; emms-tageditor.el ends here