1;;; emms-playlist-mode.el --- Playlist mode for Emms. 2 3;; Copyright (C) 2005, 2006, 2007, 2008, 2009 Free Software Foundation, Inc. 4 5;; Author: Yoni Rabkin <yrk@gnu.org> 6 7;; This file is part of EMMS. 8 9;; EMMS is free software; you can redistribute it and/or modify 10;; it under the terms of the GNU General Public License as published by 11;; the Free Software Foundation; either version 3, or (at your option) 12;; any later version. 13;; 14;; EMMS is distributed in the hope that it will be useful, 15;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17;; GNU General Public License for more details. 18;; 19;; You should have received a copy of the GNU General Public License 20;; along with EMMS; if not, write to the Free Software Foundation, 21;; Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA. 22 23;;; Commentary: 24;;; 25;;; This is a method of displaying and manipulating the different Emms 26;;; playlist buffers. 27;;; 28;;; Emms developer's motto: "When forcer says (require 'jump) we say 29;;; (funcall #'jump height)" 30 31;;; Code: 32 33;;; -------------------------------------------------------- 34;;; Variables 35;;; -------------------------------------------------------- 36 37(require 'emms) 38(condition-case nil 39 (require 'overlay) 40 (error nil)) 41(require 'emms-source-playlist) 42 43(defvar emms-playlist-mode-hook nil 44 "Emms playlist mode hook.") 45 46(defvar emms-playlist-mode-selected-overlay nil 47 "Last selected track. Use for updating the display.") 48 49(defvar emms-playlist-mode-switched-buffer nil 50 "Last buffer visited before calling `emms-playlist-mode-switch-buffer'.") 51 52(defvar emms-playlist-mode-popup-enabled nil 53 "True when the playlist was called as a popup window.") 54 55(defvar emms-playlist-mode-kill-whole-line-p t 56 "When true line kills behave like a typical music player.") 57 58(make-variable-buffer-local 59 'emms-playlist-mode-selected-overlay) 60 61(defgroup emms-playlist-mode nil 62 "*The Emacs Multimedia System playlist mode." 63 :prefix "emms-playlist-mode-" 64 :group 'emms) 65 66(defcustom emms-playlist-mode-open-playlists nil 67 "*Determine whether to open playlists in a new EMMS buffer on RET. 68This is useful if you have a master playlist buffer that is 69composed of other playlists." 70 :type 'boolean 71 :group 'emms-playlist-mode) 72 73(defcustom emms-playlist-mode-window-width 25 74 "*Determine the width of the Emms popup window. 75The value should a positive integer." 76 :type 'integer 77 :group 'emms-playlist-mode) 78 79(defcustom emms-playlist-mode-center-when-go nil 80 "*Determine whether to center on the currently selected track. 81This is true for every invocation of `emms-playlist-mode-go'." 82 :type 'boolean 83 :group 'emms-playlist-mode) 84 85;;; -------------------------------------------------------- 86;;; Faces 87;;; -------------------------------------------------------- 88 89(defface emms-playlist-track-face 90 '((((class color) (background dark)) 91 (:foreground "DarkSeaGreen")) 92 (((class color) (background light)) 93 (:foreground "Blue")) 94 (((type tty) (class mono)) 95 (:inverse-video t)) 96 (t (:background "Blue"))) 97 "Face for the tracks in a playlist buffer." 98 :group 'emms-playlist-mode) 99 100(defface emms-playlist-selected-face 101 '((((class color) (background dark)) 102 (:foreground "SteelBlue3")) 103 (((class color) (background light)) 104 (:background "blue3" :foreground "white")) 105 (((type tty) (class mono)) 106 (:inverse-video t)) 107 (t (:background "blue3"))) 108 "Face for highlighting the selected track." 109 :group 'emms-playlist-mode) 110 111;;; -------------------------------------------------------- 112;;; Keys 113;;; -------------------------------------------------------- 114 115(defvar emms-playlist-mode-map 116 (let ((map (make-sparse-keymap))) 117 (set-keymap-parent map text-mode-map) 118 (define-key map (kbd "C-x C-s") 'emms-playlist-save) 119 (define-key map (kbd "C-y") 'emms-playlist-mode-yank) 120 (define-key map (kbd "C-k") 'emms-playlist-mode-kill-track) 121 (define-key map (kbd "C-w") 'emms-playlist-mode-kill) 122 (define-key map (kbd "C-_") 'emms-playlist-mode-undo) 123 (define-key map (kbd "C-/") 'emms-playlist-mode-undo) 124 (define-key map (kbd "C-x u") 'emms-playlist-mode-undo) 125 (define-key map (kbd "C-n") 'next-line) 126 (define-key map (kbd "C-p") 'previous-line) 127 (define-key map (kbd "C-j") 'emms-playlist-mode-insert-newline) 128 (define-key map (kbd "M-y") 'emms-playlist-mode-yank-pop) 129 (define-key map (kbd "M-<") 'emms-playlist-mode-first) 130 (define-key map (kbd "M->") 'emms-playlist-mode-last) 131 (define-key map (kbd "M-n") 'emms-playlist-mode-next) 132 (define-key map (kbd "M-p") 'emms-playlist-mode-previous) 133 (define-key map (kbd "a") 'emms-playlist-mode-add-contents) 134 (define-key map (kbd "b") 'emms-playlist-set-playlist-buffer) 135 (define-key map (kbd "D") 'emms-playlist-mode-kill-track) 136 (define-key map (kbd "n") 'emms-next) 137 (define-key map (kbd "p") 'emms-previous) 138 (define-key map (kbd "SPC") 'scroll-up) 139 (define-key map (kbd ">") 'emms-seek-forward) 140 (define-key map (kbd "<") 'emms-seek-backward) 141 (define-key map (kbd "P") 'emms-pause) 142 (define-key map (kbd "s") 'emms-stop) 143 (define-key map (kbd "f") 'emms-show) 144 (define-key map (kbd "c") 'emms-playlist-mode-center-current) 145 (define-key map (kbd "q") 'emms-playlist-mode-bury-buffer) 146 (define-key map (kbd "k") 'emms-playlist-mode-current-kill) 147 (define-key map (kbd "?") 'describe-mode) 148 (define-key map (kbd "r") 'emms-random) 149 (define-key map (kbd "C") 'emms-playlist-mode-clear) 150 (define-key map (kbd "d") 'emms-playlist-mode-goto-dired-at-point) 151 (define-key map (kbd "<mouse-2>") 'emms-playlist-mode-play-current-track) 152 (define-key map (kbd "RET") 'emms-playlist-mode-play-smart) 153 map) 154 "Keymap for `emms-playlist-mode'.") 155 156(defmacro emms-playlist-mode-move-wrapper (name fun) 157 "Create a function NAME which is an `interactive' version of FUN. 158 159NAME should be a symbol. 160FUN should be a function." 161 `(defun ,name () 162 ,(format "Interactive wrapper around `%s' for playlist-mode." 163 fun) 164 (interactive) 165 (,fun))) 166 167(emms-playlist-mode-move-wrapper emms-playlist-mode-first 168 emms-playlist-first) 169 170(emms-playlist-mode-move-wrapper emms-playlist-mode-select-next 171 emms-playlist-next) 172 173(emms-playlist-mode-move-wrapper emms-playlist-mode-select-previous 174 emms-playlist-previous) 175 176(defun emms-playlist-mode-bury-buffer () 177 "Wrapper around `bury-buffer' for popup windows." 178 (interactive) 179 (if emms-playlist-mode-popup-enabled 180 (unwind-protect 181 (delete-window) 182 (setq emms-playlist-mode-popup-enabled nil)) 183 (bury-buffer))) 184 185(defun emms-playlist-mode-current-kill () 186 "If the current buffer is an EMMS playlist buffer, kill it. 187Otherwise, kill the current EMMS playlist buffer." 188 (interactive) 189 (if (and emms-playlist-buffer-p 190 (not (eq (current-buffer) emms-playlist-buffer))) 191 (kill-buffer (current-buffer)) 192 (emms-playlist-current-kill))) 193 194(defun emms-playlist-mode-clear () 195 "If the current buffer is an EMMS playlist buffer, clear it. 196Otherwise, clear the current EMMS playlist buffer." 197 (interactive) 198 (if (and emms-playlist-buffer-p 199 (not (eq (current-buffer) emms-playlist-buffer))) 200 (let ((inhibit-read-only t)) 201 (widen) 202 (delete-region (point-min) (point-max))) 203 (emms-playlist-clear))) 204 205(defun emms-playlist-mode-last () 206 "Move to directly after the last track in the current buffer." 207 (interactive) 208 (emms-playlist-ensure-playlist-buffer) 209 (let ((last (condition-case nil 210 (save-excursion 211 (goto-char (point-max)) 212 (point)) 213 (error 214 nil)))) 215 (if last 216 (goto-char last) 217 (error "No last track")))) 218 219(defun emms-playlist-mode-center-current () 220 "Move point to the currently selected track." 221 (interactive) 222 (goto-char (if emms-playlist-mode-selected-overlay 223 (overlay-start emms-playlist-mode-selected-overlay) 224 (point-min)))) 225 226(defun emms-playlist-mode-play-current-track () 227 "Start playing track at point." 228 (interactive) 229 (emms-playlist-set-playlist-buffer (current-buffer)) 230 (unless (emms-playlist-track-at (point)) 231 (emms-playlist-next)) 232 (emms-playlist-select (point)) 233 (when emms-player-playing-p 234 (emms-stop)) 235 (emms-start)) 236 237(defun emms-playlist-mode-play-smart () 238 "Determine the best operation to take on the current track. 239 240If on a playlist, and `emms-playlist-mode-open-playlists' is 241non-nil, load the playlist at point into a new buffer. 242 243Otherwise play the track immediately." 244 (interactive) 245 (save-excursion 246 ;; move to the start of the line, in case the point is on the \n, 247 ;; which isn't propertized 248 (emms-move-beginning-of-line nil) 249 (if (not emms-playlist-mode-open-playlists) 250 (emms-playlist-mode-play-current-track) 251 (unless (emms-playlist-track-at) 252 (emms-playlist-next)) 253 (let* ((track (emms-playlist-track-at)) 254 (name (emms-track-get track 'name)) 255 (type (emms-track-get track 'type))) 256 (if (or (eq type 'playlist) 257 (and (eq type 'file) 258 (string-match "\\.\\(m3u\\|pls\\)\\'" name))) 259 (emms-playlist-mode-load-playlist) 260 (emms-playlist-mode-play-current-track)))))) 261 262(defun emms-playlist-mode-switch-buffer () 263 "Switch to the playlist buffer and then switch back if called again. 264 265This function switches to the current Emms playlist buffer and 266remembers the buffer switched from. When called again the 267function switches back to the remembered buffer." 268 (interactive) 269 (if (eq (current-buffer) 270 emms-playlist-buffer) 271 (switch-to-buffer emms-playlist-mode-switched-buffer) 272 (setq emms-playlist-mode-switched-buffer (current-buffer)) 273 (switch-to-buffer emms-playlist-buffer))) 274 275(defun emms-playlist-mode-insert-newline () 276 "Insert a newline at point." 277 (interactive) 278 (emms-with-inhibit-read-only-t 279 (newline))) 280 281(defun emms-playlist-mode-undo () 282 "Wrapper around `undo'." 283 (interactive) 284 (emms-with-inhibit-read-only-t 285 (undo))) 286 287(defun emms-playlist-mode-add-contents () 288 "Add files in the playlist at point to the current playlist buffer. 289 290If we are in the current playlist, make a new playlist buffer and 291set it as current." 292 (interactive) 293 (save-excursion 294 (emms-move-beginning-of-line nil) 295 (unless (emms-playlist-track-at) 296 (emms-playlist-next)) 297 (let* ((track (emms-playlist-track-at)) 298 (name (emms-track-get track 'name)) 299 (type (emms-track-get track 'type)) 300 (playlist-p (or (eq type 'playlist) 301 (and (eq type 'file) 302 (save-match-data 303 (string-match "\\.\\(m3u\\|pls\\)\\'" 304 name)))))) 305 (emms-playlist-select (point)) 306 (unless (and (buffer-live-p emms-playlist-buffer) 307 (not (eq (current-buffer) emms-playlist-buffer))) 308 (setq emms-playlist-buffer 309 (emms-playlist-set-playlist-buffer (emms-playlist-new)))) 310 (with-current-emms-playlist 311 (goto-char (point-max)) 312 (when playlist-p 313 (insert (emms-track-force-description track) "\n")) 314 (let ((beg (point))) 315 (if playlist-p 316 (emms-add-playlist name) 317 (let ((func (intern (concat "emms-add-" (symbol-name type))))) 318 (if (functionp func) 319 (funcall func name) 320 ;; fallback 321 (emms-add-file name)))) 322 (when playlist-p 323 (goto-char (point-max)) 324 (while (progn 325 (forward-line -1) 326 (>= (point) beg)) 327 (insert " "))) 328 (goto-char (point-min)) 329 (message "Added %s" (symbol-name type))))))) 330 331(defun emms-playlist-mode-goto-dired-at-point () 332 "Visit the track at point in a `dired' buffer." 333 (interactive) 334 (let ((track (emms-playlist-track-at))) 335 (if track 336 (let ((name (emms-track-get track 'name)) 337 (type (emms-track-get track 'type))) 338 (if (eq type 'file) 339 (dired (file-name-directory name)) 340 (error "Can't visit this track type in Dired"))) 341 (error "No track at point")))) 342 343;;; -------------------------------------------------------- 344;;; Killing and yanking 345;;; -------------------------------------------------------- 346 347(defun emms-playlist-mode-between-p (p a b) 348 "Return t if P is a point between points A and B." 349 (and (<= a p) 350 (<= p b))) 351 352;; D 353(defun emms-playlist-mode-kill-entire-track () 354 "Kill track at point, including newline." 355 (interactive) 356 (let ((kill-whole-line t)) 357 (emms-playlist-mode-kill-track))) 358 359;; C-k 360(defun emms-playlist-mode-kill-track () 361 "Kill track at point." 362 (interactive) 363 (emms-with-inhibit-read-only-t 364 (let ((track (emms-playlist-track-at))) 365 (when track 366 (let ((track-region (emms-property-region (point) 367 'emms-track))) 368 (when (and emms-player-playing-p 369 (emms-playlist-selected-track-at-p)) 370 (emms-stop) 371 (delete-overlay emms-playlist-mode-selected-overlay) 372 (setq emms-playlist-mode-selected-overlay nil)))) 373 (let ((kill-whole-line emms-playlist-mode-kill-whole-line-p)) 374 (goto-char (point-at-bol)) 375 (kill-line))))) 376 377;; C-w 378(defun emms-playlist-mode-kill () 379 "Kill from mark to point." 380 (interactive) 381 (emms-with-inhibit-read-only-t 382 ;; Are we killing the playing/selected track? 383 (when (and (markerp emms-playlist-selected-marker) 384 (emms-playlist-mode-between-p 385 (marker-position emms-playlist-selected-marker) 386 (region-beginning) 387 (region-end))) 388 (emms-stop) 389 (delete-overlay emms-playlist-mode-selected-overlay) 390 (setq emms-playlist-mode-selected-overlay nil)) 391 (kill-region (region-beginning) 392 (region-end)))) 393 394(defun emms-playlist-mode-correct-previous-yank () 395 "Fix the previous yank if needed." 396 (when (and (< (point-at-bol) (point)) 397 (< (point) (point-at-eol))) 398 (newline))) 399 400;; C-y 401(defun emms-playlist-mode-yank () 402 "Yank into the playlist buffer." 403 (interactive) 404 (emms-with-inhibit-read-only-t 405 (goto-char (point-at-bol)) 406 (yank) 407 (emms-playlist-mode-correct-previous-yank))) 408 409;; M-y 410(defun emms-playlist-mode-yank-pop () 411 "Cycle through the kill-ring." 412 (interactive) 413 (emms-with-inhibit-read-only-t 414 (yank-pop nil) 415 (emms-playlist-mode-correct-previous-yank))) 416 417;;; -------------------------------------------------------- 418;;; Overlay 419;;; -------------------------------------------------------- 420 421(defun emms-playlist-mode-overlay-selected () 422 "Place an overlay over the currently selected track." 423 (when emms-playlist-selected-marker 424 (save-excursion 425 (goto-char emms-playlist-selected-marker) 426 (let ((reg (emms-property-region (point) 'emms-track))) 427 (if emms-playlist-mode-selected-overlay 428 (move-overlay emms-playlist-mode-selected-overlay 429 (car reg) 430 (cdr reg)) 431 (setq emms-playlist-mode-selected-overlay 432 (make-overlay (car reg) 433 (cdr reg) 434 nil t nil)) 435 (overlay-put emms-playlist-mode-selected-overlay 436 'face 'emms-playlist-selected-face) 437 (overlay-put emms-playlist-mode-selected-overlay 438 'evaporate t)))))) 439 440;;; -------------------------------------------------------- 441;;; Saving/Restoring 442;;; -------------------------------------------------------- 443 444(defun emms-playlist-mode-open-buffer (filename) 445 "Opens a previously saved playlist buffer. 446 447It creates a buffer called \"filename\", and restores the contents 448of the saved playlist inside." 449 (interactive "fFile: ") 450 (let* ((s) 451 (buffer (get-buffer-create filename)) 452 (name (buffer-name buffer))) 453 (with-current-buffer buffer 454 (emms-insert-file-contents filename) 455 (setq s (read (buffer-string)))) 456 (kill-buffer buffer) 457 (with-current-buffer (emms-playlist-new name) 458 (emms-with-inhibit-read-only-t 459 (insert s) 460 (goto-char (point-min)) 461 (emms-walk-tracks 462 (emms-playlist-update-track))) 463 (emms-playlist-first) 464 (emms-playlist-select (point)) 465 (switch-to-buffer (current-buffer))))) 466 467(defun emms-playlist-mode-load-playlist () 468 "Load the playlist into a new EMMS buffer. 469This preserves the current EMMS buffer." 470 (interactive) 471 (let* ((track (emms-playlist-track-at)) 472 (name (emms-track-get track 'name)) 473 (type (emms-track-get track 'type))) 474 (emms-playlist-select (point)) 475 (run-hooks 'emms-player-stopped-hook) 476 (switch-to-buffer 477 (emms-playlist-set-playlist-buffer (emms-playlist-new))) 478 (emms-add-playlist name))) 479 480;;; -------------------------------------------------------- 481;;; Local functions 482;;; -------------------------------------------------------- 483 484(defun emms-playlist-mode-insert-track (track &optional no-newline) 485 "Insert the description of TRACK at point. 486When NO-NEWLINE is non-nil, do not insert a newline after the track." 487 (emms-playlist-ensure-playlist-buffer) 488 (emms-with-inhibit-read-only-t 489 (insert (emms-propertize (emms-track-force-description track) 490 'emms-track track 491 'face 'emms-playlist-track-face)) 492 (when (emms-playlist-selected-track-at-p) 493 (emms-playlist-mode-overlay-selected)) 494 (unless no-newline 495 (insert "\n")))) 496 497(defun emms-playlist-mode-update-track-function () 498 "Update the track display at point." 499 (emms-playlist-ensure-playlist-buffer) 500 (emms-with-inhibit-read-only-t 501 (let ((track-region (emms-property-region (point) 502 'emms-track)) 503 (track (get-text-property (point) 504 'emms-track)) 505 (selectedp (emms-playlist-selected-track-at-p))) 506 (save-excursion 507 (delete-region (car track-region) 508 (cdr track-region)) 509 (when selectedp 510 (delete-overlay emms-playlist-mode-selected-overlay) 511 (setq emms-playlist-mode-selected-overlay nil)) 512 (emms-playlist-mode-insert-track track t)) 513 (when selectedp 514 (emms-playlist-select (point)))))) 515 516;;; -------------------------------------------------------- 517;;; Entry 518;;; -------------------------------------------------------- 519 520(defun emms-playlist-mode-go () 521 "Switch to the current emms-playlist buffer and use emms-playlist-mode." 522 (interactive) 523 (if (or (null emms-playlist-buffer) 524 (not (buffer-live-p emms-playlist-buffer))) 525 (error "No current Emms buffer") 526 (switch-to-buffer emms-playlist-buffer) 527 (when (and (not (eq major-mode 'emms-playlist-mode)) 528 emms-playlist-buffer-p) 529 (emms-playlist-mode)) 530 (when emms-playlist-mode-center-when-go 531 (emms-playlist-mode-center-current)))) 532 533(defun emms () 534 "Switch to the current emms-playlist buffer, use 535emms-playlist-mode and query for a directory tree to add to the 536playlist." 537 (interactive) 538 (if (or (null emms-playlist-buffer) 539 (not (buffer-live-p emms-playlist-buffer))) 540 (call-interactively 'emms-add-file)) 541 (emms-playlist-mode-go)) 542 543(defun emms-playlist-mode-go-popup (&optional window-width) 544 "Popup emms-playlist buffer as a side window. 545 546Default value for WINDOW-WIDTH is `emms-playlist-mode-window-width'. 547WINDOW-WIDTH should be a positive integer." 548 (interactive) 549 (setq emms-playlist-mode-window-width 550 (round (or window-width emms-playlist-mode-window-width))) 551 (split-window-horizontally (- emms-playlist-mode-window-width)) 552 (other-window 1) 553 (emms-playlist-mode-go) 554 (setq emms-playlist-mode-popup-enabled t)) 555 556(defun emms-playlist-mode-next (arg) 557 "Navigate between playlists." 558 (interactive "p") 559 (let ((playlists (emms-playlist-buffer-list)) 560 bufs idx) 561 (if playlists 562 ;; if not in playlist mode, switch to emms-playlist-buffer 563 (if (not (member (current-buffer) playlists)) 564 (switch-to-buffer (if (and emms-playlist-buffer 565 (buffer-live-p emms-playlist-buffer)) 566 emms-playlist-buffer 567 (car playlists))) 568 (setq bufs (member (current-buffer) playlists)) 569 (setq idx 570 (+ (- (length playlists) (length bufs)) 571 (if (> arg 0) 1 -1))) 572 (switch-to-buffer (nth (mod idx (length playlists)) playlists))) 573 (message "No playlist found!")))) 574(defun emms-playlist-mode-previous (arg) 575 (interactive "p") 576 (emms-playlist-mode-next (- arg))) 577 578(defun emms-playlist-mode-startup () 579 "Instigate emms-playlist-mode on the current buffer." 580 ;; when there is neither a current emms track or a playing one... 581 (when (not (or emms-playlist-selected-marker 582 emms-player-playing-p)) 583 ;; ...then stop the player. 584 (emms-stop) 585 ;; why select the first track? 586 (when emms-playlist-buffer-p 587 (emms-playlist-select-first))) 588 ;; when there is a selected track. 589 (when emms-playlist-selected-marker 590 (emms-playlist-mode-overlay-selected)) 591 (emms-with-inhibit-read-only-t 592 (add-text-properties (point-min) 593 (point-max) 594 '(face emms-playlist-track-face))) 595 (setq buffer-read-only t) 596 (setq truncate-lines t) 597 (setq buffer-undo-list nil)) 598 599;;;###autoload 600(defun emms-playlist-mode () 601 "A major mode for Emms playlists. 602\\{emms-playlist-mode-map}" 603 (interactive) 604 (let ((val emms-playlist-buffer-p)) 605 (kill-all-local-variables) 606 (setq emms-playlist-buffer-p val)) 607 608 (use-local-map emms-playlist-mode-map) 609 (setq major-mode 'emms-playlist-mode 610 mode-name "EMMS") 611 612 (setq emms-playlist-insert-track-function 613 'emms-playlist-mode-insert-track) 614 (setq emms-playlist-update-track-function 615 'emms-playlist-mode-update-track-function) 616 (add-hook 'emms-playlist-selection-changed-hook 617 'emms-playlist-mode-overlay-selected) 618 619 (emms-playlist-mode-startup) 620 621 (run-hooks 'emms-playlist-mode-hook)) 622 623(provide 'emms-playlist-mode) 624 625;;; emms-playlist-mode.el ends here 626