Download
(defconst screencast-message-buffer-name "*Screencast Messages*"
"The name of the buffer to put messages from the screencast in")
(defconst screencast-version 1.1 "The version number of the screencast-mode")
(defconst screencast-speed-relation-speech-type 18.0
"When this is correctly adjusted, speech and typing should end
at the same time. Lower values means faster speech.")
(defvar screencast-pause-length 2 "The length of a pause ('p) in the screencast")
(defvar screencast-pause-char-length 0.12
"The time between each typed character in the function `screencast-insert-with-delay'")
(defvar screencast-pause-command-length 3
"The time between the announcement of the function call, and the call itself.")
(defvar screencast-speech nil "If non-nil, slowly typed strings are read aloud")
(defvar screencast-speed 1.0 "How fast the screencast should be. Higher values equals higher speed. This can not be changed _during_ the screencast.")
(defvar screencast-dont-print-list '(
progn
let
flet
save-excursion
save-window-excursion
i
screencast-producer-insert-with-delay
screencast-producer-set-last-command
screencast-producer-set-last-char
screencast-producer-new-buffer
screencast-producer-show-region
screencast-producer-blink-regions)
"A list of lists of function names which aren't printed as
being evaluated in the messages, this includes all producer
functions by default")
(defvar screencast-producer-blink-time 0.5
"The time a blink lasts.")
(defvar screencast-producer-nopause nil
"Variable to be used for producer functions if they are using
pauses, they should deactivate the pause if this variable is non-nil.")
(defvar screencast-producer-command-buffer nil
"Variable to be used for producer functions if they are using
need the current command-buffer.")
(defvar screencast-producer-step-number 0
"Variable to be used for producer functions if they need to
know the current step number.
This is a _COPY_ of the value the screencast uses!")
(defvar screencast-producer-beginat 0
"Variable to be used for producer functions if they need to
know where the screencast is supposed to be using pauses at")
(defvar screencast-mode-map
(let ((map (make-sparse-keymap)))
(define-key map (kbd "RET") 'screencast-goto-step)
map)
"Keymap for `screencast-mode'."
)
(define-derived-mode screencast-mode nil "screencast"
"Major mode for viewing screencasts."
(auto-fill-mode 1)
)
(defun screencast-producer-insert-with-delay (string)
"Screencast producer function. _i_nserts STRING with a delay between each character.
See `screencast-insert-with-delay' for more details."
(let ((screencast-speech nil))
(screencast-insert-with-delay string screencast-producer-nopause)))
(defalias 'i 'screencast-producer-insert-with-delay
"Short name for `screencast-producer-insert-with-delay'.
This is chosen as it improves readability a lot in the screencast-source.")
(defun screencast-producer-set-last-command (f last)
"Sets the last-command to LAST before evaluating F.
Also prints the info about F, like it would have done normally."
(screencast-producer-show-command (car f))
(eval-with-last f last)
)
(defun eval-with-last (f last)
(eval (list 'progn
'(setq last-command last)
'(setq this-command last)
f)))
(defun screencast-producer-set-last-char (char f)
"Sets the last-command to CHAR before evaluating F.
Also prints the info about F, like it would have done normally."
(screencast-producer-show-command (car f))
(eval (list 'progn
'(setq last-command-char (string-to-char char))
f)))
(defun screencast-producer-show-command (command)
"Shows the COMMAND, and how it can be called in the message-buffer."
(pop-to-buffer (get-buffer screencast-message-buffer-name))
(screencast-show-command command
screencast-producer-step-number
screencast-producer-command-buffer)
(pop-to-buffer (get-buffer screencast-producer-command-buffer)))
(defun screencast-producer-new-buffer (list command-buffer-name)
"Screencast producer function. Creates an new screencast with
COMMAND-BUFFER-NAME as the command-buffer. The message-buffer
remains the same. Once the inner screencast ends, the original
command-buffer regains its status.
IMPORTANT:
You are responsible for killing the `COMMAND-BUFFER'
before the outermost screencast ends, otherwise you'll receive
the modified buffer the next time you run the outermost
screencast."
(pop-to-buffer (get-buffer screencast-message-buffer-name))
(screencast-internal list
(get-buffer command-buffer-name)
screencast-producer-beginat)
)
(defun screencast-producer-show-region (beg end)
"Marks the currently active region as if transient mark mode was on."
(unless screencast-producer-nopause
(let ((overlay (make-overlay beg end)))
(overlay-put overlay 'face (cons beg end))
(run-with-timer screencast-pause-length nil 'delete-overlay overlay)
)
(sit-for screencast-pause-length)
)
)
(defun screencast-producer-blink-regions (regions)
"The REGIONS will blink.
A region is a pair: (beg . end)."
(unless screencast-producer-nopause
(dotimes (n 5)
(dolist (region regions)
(let ((overlay (make-overlay (car region) (cdr region))))
(overlay-put overlay 'face 'region)
(run-with-timer screencast-producer-blink-time nil 'delete-overlay overlay))
)
(sit-for (* 2 screencast-producer-blink-time))
)))
(defun make-region-clickable (beg end action &optional key)
"Makes the chosen region clickable, executing chosen action.
Default key is [mouse-1]."
(let ((map (make-sparse-keymap))
(keyc (if key
key
[mouse-1]))
)
(define-key map keyc action)
(put-text-property
beg
end
'keymap map))
)
(defun screencast-fontify-step-region ()
"Fontifies regions with step-references.
To be called immediately after functions which put step-numbers
in the message-buffer. Will fontify from the beginning of the
line with the step number to the end of the buffer."
(save-excursion
(goto-char (point-max))
(let ((beg (search-backward-regexp "^Step [[:digit:]]+:" (point-min))))
(screencast-put-shadow-and-make-clickable beg (point-max))
)))
(defun screencast-put-shadow-and-make-clickable (beg end)
"The region between BEG and END becomes shadowed and clickable.
`screencast-goto-step' is evalled when clicked"
(add-text-properties beg (- end 0)
(list
'face 'shadow
'mouse-face 'highlight
'help-echo "mouse-1: continue from this step"
))
(make-region-clickable beg (- end 0) 'screencast-goto-step))
(defun screencast-get-step ()
"Returns the step-number of a step-reference region.
If not in step-reference region, returns nil"
(if
(save-excursion
(goto-char (line-end-position))
(or
(search-backward-regexp "^Step [[:digit:]]+:" (line-beginning-position) t)
(search-backward-regexp "^ Callable with:" (line-beginning-position) t)))
(save-excursion
(search-backward-regexp "^Step \\([[:digit:]]+\\):")
(let ((beg (match-beginning 1))
(end (match-end 1)))
(string-to-number
(buffer-substring-no-properties beg end))))
nil))
(defun repeat-string (s n)
(apply 'concat (make-list n s)))
(defun screencast-make-break (nopause)
(screencast-newline-only-once)
(newline)
(screencast-line)
(newline)
(screencast-pause-maybe nopause)
(screencast-pause-maybe nopause)
)
(defun screencast-pause-maybe (nopause &optional length)
"Pauses the program, unless NOPAUSE is non-nil.
If length is nil, a default pause LENGTH is used."
(unless nopause
(let ((l (if length
length
screencast-pause-length)))
(sit-for l))))
(defun n-first (n list)
"The n first elements of a list."
(loop for x in list repeat n collect x))
(defun buffer-recreate (buffer-name)
"Kills the buffer with BUFFER-NAME, and recreates it."
(let ((buffer (get-buffer buffer-name)))
(when buffer
(when (buffer-file-name buffer)
(save-excursion
(set-buffer buffer)
(unless (let ((start (substring-no-properties buffer-name 0 1)))
(or (string= start " ") (string= start "*")))
(save-buffer)))
(kill-buffer buffer-name)))
(get-buffer-create buffer-name))
)
(defun screencast-goto-step (&optional arg)
"Restarts the screencast at the chosen ARG step. Default is the first step."
(interactive "p")
(let ((step (if (not (= 1 arg))
arg
(screencast-get-step)))
(list (get-text-property (point-min) 'screencast-list))
(name (get-text-property (point-min) 'screencast-command-buffer-name)))
(screencast list name
-1
(if step
(- step 1)
0))
))
(defun screencast-newline-only-once ()
"Inserts a newline at point if, and only if the current line is nonempty."
(unless (= (line-beginning-position) (line-end-position))
(newline))
)
(defun screencast-make-region-clickable (beg end action &optional key)
"Makes the chosen region clickable, executing chosen action.
Default key is [mouse-1]."
(let ((map (make-sparse-keymap))
(keyc (if key
key
[mouse-1]))
)
(define-key map keyc action)
(put-text-property
beg
end
'keymap map))
)
(defun screencast-show-command (com step command-buffer)
"Inserts the STEP number and key-binding for a command, COM."
(screencast-newline-only-once)
(insert "Step " (number-to-string step) ": `" (symbol-name com) "'")
(newline)
(insert " Callable with: ")
(insert (where-is-return com command-buffer))
(screencast-fontify-step-region)
(newline)
)
(defun screencast-line (&optional length)
(let ((l (if length
length
25)))
(screencast-newline-only-once)
(insert (repeat-string "-" l))
(center-line)
(newline)
))
(defun screencast-header ()
(screencast-newline-only-once)
(newline)
(screencast-line 50)
(newline))
(defun screencast-speech-start (string nopause)
"Starts the speech-synthesizer with STRING, unless NOPAUSE is nonnil.
Also requires `screencast-speech' to be non-nil.
The speech speed depends on the typing speed (`screencast-speed-relation-speech-type')."
(when (and (not nopause) screencast-speech)
(let* ((duration (concat
"-b \"(Parameter.set 'Duration_Stretch "
(number-to-string (* screencast-pause-char-length
screencast-speed-relation-speech-type)) ")\""))
(tosay (replace-regexp-in-string "'" "'\"'\"'" string))
(say (concat "-b '(SayText \"" tosay "\")'"))
)
(save-window-excursion
(shell-command
(concat "festival " duration " " say "&"))
))))
(defun screencast-speech-wait-for (nopause)
"Blocks until the speech synthesizer is done speaking."
(when (and (not nopause) screencast-speech)
(shell-command "while [ `pgrep festival` ] ; do sleep 0.1; done;")
(sit-for 0.1))
)
(defun screencast-insert-with-delay (string &optional nopause)
"Inserts STRING with a delay between each character.
If NOPAUSE is non-nil, the delay will be 0.
The pause between each character is given by `screencast-pause-char-length'."
(let ((string (screencast-strip-newlines-and-normalize-whitespace string)))
(screencast-speech-start string nopause)
(let ((l (string-to-list string)))
(dolist (c l)
(insert c)
(when (and (= (- (line-end-position) (line-beginning-position)) fill-column))
(search-backward " ")
(insert "\n ")
(end-of-line)
)
(screencast-pause-maybe nopause screencast-pause-char-length)))
(screencast-speech-wait-for nopause)
)
)
(defun screencast-strip-newlines-and-normalize-whitespace (string)
"Replaces all newlines and tabs in STRING by a single
whitespace, also collapses multiple whitespaces."
(replace-regexp-in-string "[ ]+" " " (replace-regexp-in-string "\n" " " string)))
(defalias 'screencast 'screencast-producer-screencast "Renaming for simplicity")
(defun screencast-producer-screencast (list command-buffer-name
version &optional beginat init)
"Prints and evaluates a list, LIST, of strings and functions in a tempo humans can follow.
The strings in LIST is printed to the screencast-message-buffer.
Functions are evaluated in the buffer named COMMAND-BUFFER-NAME.
VERSION is the version of screencast-mode the screencast is
written for, older versions of screencast-mode might not support
everything in newer screencasts.
The first BEGINAT elements of the list will be done without
delays.
INIT is a list of functions to be evaluated in the message-buffer
prior to the first message"
(when (> version screencast-version)
(error
(concat "The version of the screencast (" (number-to-string
version) ") is newer than the version of the screencast-mode
itself (" (number-to-string screencast-version) "). You might still be able
to run the screencast successfully though, just change the
screencasts version number to try it out.")))
(let* (
(screencast-pause-length (/ screencast-pause-length
screencast-speed))
(screencast-pause-char-length (/
screencast-pause-char-length
screencast-speed))
(screencast-pause-command-length (/
screencast-pause-command-length
screencast-speed))
(message-buffer (buffer-recreate screencast-message-buffer-name))
(command-buffer (if (string= command-buffer-name
screencast-message-buffer-name)
message-buffer
(buffer-recreate command-buffer-name)))
(screencast-step-number 0)
(beginat (if beginat
beginat
0)))
(delete-other-windows)
(split-window-horizontally)
(switch-to-buffer message-buffer)
(pop-to-buffer message-buffer)
(display-buffer command-buffer)
(screencast-mode)
(toggle-read-only 0)
(dolist (f init)
(eval f))
(screencast-internal list command-buffer beginat)
(add-text-properties (point-min) (point-max)
(list 'screencast-list list
'screencast-command-buffer-name command-buffer-name))
(toggle-read-only 1)
)
)
(defun screencast-internal (list command-buffer beginat)
"The internal version of screencast, refer to the documentation string
there."
(setq screencast-producer-command-buffer command-buffer)
(setq screencast-producer-beginat beginat)
(save-excursion
(set-buffer command-buffer)
(unless (buffer-file-name)
(set-visited-file-name (buffer-name))
))
(dolist (c list)
(let ((nopause
(if (>= screencast-step-number beginat)
nil
t)))
(setq screencast-producer-nopause nopause)
(setq screencast-producer-step-number screencast-step-number)
(cond
((symbolp c)
(cond
((eq 's c)
(screencast-newline-only-once)
(insert "Step " (number-to-string screencast-step-number) ":")
(screencast-fontify-step-region)
)
((eq 'l c)
(screencast-line))
((eq 'n c)
(newline))
((eq 'p c)
(screencast-pause-maybe nopause))
((eq 'b c)
(screencast-make-break nopause)
)
(t
(error (concat "Screencast-internal encountered an error: Unknown symbol: " (symbol-name c)))))
)
((listp c)
(progn
(unless (member (car c) screencast-dont-print-list)
(screencast-show-command (car c) screencast-step-number command-buffer)
)
(unless nopause
(screencast-pause-maybe nopause screencast-pause-command-length)
)
(if (member (car c) '(let flet))
(progn
(eval (list (car c)
(cadr c)
'(screencast-internal (cddr c) command-buffer beginat)))
)
(progn
(pop-to-buffer command-buffer)
(eval c)
(pop-to-buffer screencast-message-buffer-name)))
(pop-to-buffer screencast-message-buffer-name)
))
((stringp c)
(screencast-insert-with-delay c nopause))
(t
(error (concat "I don't know what to do with element:" c)))
)
(setq screencast-step-number (+ 1 screencast-step-number))
)
)
)
(defun where-is-return (definition buffer)
"A modification of where-is, which returns the message-string instead of printing it.
Also skips the removes name from the output.
BUFFER is the buffer to call where-is in."
(save-excursion
(set-buffer buffer)
(let ((func (indirect-function definition))
(defs nil)
(return-string ""))
(mapatoms (lambda (symbol)
(and (fboundp symbol)
(not (eq symbol definition))
(eq func (condition-case ()
(indirect-function symbol)
(error symbol)))
(push symbol defs))))
(dolist (symbol (cons definition defs))
(let* ((remapped (command-remapping symbol))
(keys (where-is-internal
symbol overriding-local-map nil nil remapped))
(keys (mapconcat 'key-description keys ", "))
string)
(setq string
(if t
(if (> (length keys) 0)
(if remapped
(format "%s (%s) (remapped from %s)"
keys remapped symbol)
(format "%s" keys))
(format "M-x %s RET" symbol))
(if (> (length keys) 0)
(if remapped
(format "%s is remapped to %s which is on %s"
symbol remapped keys)
(format "%s is on %s" symbol keys))
(if (eq symbol definition)
(format "%s is not on any key" symbol)))))
(when string
(unless (eq symbol definition)
(setq return-string (concat return-string ";\n its alias ")))
(setq return-string (concat return-string string)))))
return-string)))
(defconst screencast-screencast-text-producer
'(
"Hello, this is the screencast for creating your own
screencasts." n
"If you create a list (first argument) of strings, each
string will be typed to the message buffer (this buffer), at
a human-readable pace." n
"If you put a 'p in the list, a pause will be inserted. " p
p p p p "See?" p p
l
"(The above line was inserted instantly with the symbol 'l)"
n
"(Blank lines can be inserted using the 'n symbol, newlines
in strings are removed)" n n n
"All of the above is combined in the symbol 'b, which creates
a break in the screencast. This could be used between two
different sections for instance." b
"You can also put functions in the list, these will be
evaluated in the command-buffer (second argument)." n
"The function is written as a list, with the function name
first, and the arguments after that, e.g. '(backward-char
2)." n n p
"Each time a function is evaluated, a message is displayed in
the message buffer, using the where-is function." n
"In addition to this a step-number is displayed, this
step-number corresponds to the functions position in the
list." n
"Let's try out some functions:" n
"((insert \"THIS IS AN INSERTION\n\") will be evaluated)" p
(insert "THIS IS AN INSERTION\n")
"You can call the special function `screencast-producer-insert-with-delay', aliased to `i' to insert with delay in the command-buffer."n
(i
"this is also an insertion, but it is done at typing speed")
"Hmm.." p p "let's delete the line we just typed in the
command buffer [[(kill-whole-line 1)]]"
(kill-whole-line 1)
"Notice the keybindings which are displayed." b
"The fourth (optional) argument given to the screencast
function is the step-number to start using pauses, and output
to the message buffer at, e.g. it is a fast-forward. Which is
_very_ nice when producing a screencast." n
"These step-numbers can also be printed separately in the
message-buffer using the 's symbol in the list." n s n
"See?" b
"Once you have finished a screencast and want it published,
you can record it as a video (.ogv) using
`screencast-record'."n
"As a part of the recording - the
font-size (`screencast-record-font') is changed, as well as
the fill-column variable (`screencast-record-fill-column')
for improved readability on a video."n
"As a consequence, you should _never_ use fill-paragraph and
the like, to get a nicely formatted source-file."n
"But the Emacs community will benefit the most if you publish
the screencast file itself - so please do!"n
"You can publish it at
http://www.emacswiki.org/emacs/ScreencastSources" b
"This screencast should cover the basic options for creating
a screencast, and can be seen in the constant
`screencast-screencast-text-producer'."n
"A screencast covering the more advanced functions of
screencast is available in the function
`screencast-screencast-producer-advanced'." b
"Happy screencasting!" )
"The text the screencast-screencast-producer is based upon")
(defconst screencast-screencast-text-user '(
"Hello, welcome to the screencast for viewing screencasts in
screencast mode."n
"Screencasts are like movies, they type some explanatory
text (like this), and executes functions in order to show you
the capabilities of different tools in Emacs."n
"Once a screencast has finished, you can move the cursor to
an executed function and press RET or MOUSE-1 to review the
screencast from that step."n
"Alternatively you can use the numeric prefix argument to
pinpoint the step to begin at."n
"If no prefix argument is given, and point isn't at an
executed function, the screencast is restarted from the first
step." ))
(defconst screencast-screencast-text-producer-advanced
'(
"This screencast covers the advanced functions of screencast-mode."n
"Please read the documentation for the functions as well."n
"Regarding the functions and variables in this file:"n
"You, as a producer, are supposed to be using the functions starting with `screencast-producer-' (and `screencast' itself ofcourse), they are tailored for ease of use. The others are for internal use - and there's no guarantee they are stable throughout versions."
b
"It is possible to use multiple command-buffers:"
(screencast-producer-new-buffer
'((i "I'm a new command-buffer"))
"new-command-buffer")
(progn (kill-buffer "new-command-buffer"))
"It is done via the function `screencast-producer-new-buffer' which takes a list and a buffer - almost like the screencast function itself. "
b
"If you don't want to document everything you do, for instance moving the cursor, you can put the functions you want to \"hide\" inside a `progn'."
b
"If you need temporary variables or functions (for instance when you need to override a function which uses the mini-buffer), you can just put in a `let' or `flet'"
b
"If you need to modify the last-command-char (for self-insert-commands) or the last-command (for continued killing) there's also support for that:"n
"Use `screencast-producer-set-last-char' or `screencast-producer-set-last-command'"
"The text the screencast-screencast-producer-advanced is based upon"))
(defun screencast-screencast-producer-advanced(&optional arg)
"Displays the screencast for creating advanced screencasts."
(interactive "P")
(apply (if arg
'screencast-record
'screencast)
screencast-screencast-text-producer-advanced "screencast-screencast-producer" 1.1 ()))
(defun screencast-screencast-producer(&optional arg)
"Displays the screencast for creating screencasts."
(interactive "P")
(apply (if arg
'screencast-record
'screencast)
screencast-screencast-text-producer "screencast-screencast-producer" 1 ()))
(defun screencast-screencast-user(&optional arg)
"Displays the screencast for using screencasts."
(interactive "P")
(apply (if arg
'screencast-record
'screencast)
screencast-screencast-text-user "screencast-screencast-user" 1 ()))
(provide 'screencast)