]> code.delx.au - gnu-emacs/blob - lisp/strokes.el
85258cf62170242149cdead20bc31f02d24ee65b
[gnu-emacs] / lisp / strokes.el
1 ;;; strokes.el --- control Emacs through mouse strokes
2
3 ;; Copyright (C) 1997, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
4 ;; 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
5
6 ;; Author: David Bakhash <cadet@alum.mit.edu>
7 ;; Maintainer: FSF
8 ;; Keywords: lisp, mouse, extensions
9
10 ;; This file is part of GNU Emacs.
11
12 ;; GNU Emacs is free software: you can redistribute it and/or modify
13 ;; it under the terms of the GNU General Public License as published by
14 ;; the Free Software Foundation, either version 3 of the License, or
15 ;; (at your option) any later version.
16
17 ;; GNU Emacs is distributed in the hope that it will be useful,
18 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 ;; GNU General Public License for more details.
21
22 ;; You should have received a copy of the GNU General Public License
23 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
24
25 ;;; Commentary:
26
27 ;; This is the strokes package. It is intended to allow the user to
28 ;; control Emacs by means of mouse strokes. Once strokes is loaded, you
29 ;; can always get help be invoking `strokes-help':
30
31 ;; > M-x strokes-help
32
33 ;; and you can learn how to use the package. A mouse stroke, for now,
34 ;; can be defined as holding the shift key and the middle button, for
35 ;; instance, and then moving the mouse in whatever pattern you wish,
36 ;; which you have set Emacs to understand as mapping to a given
37 ;; command. For example, you may wish the have a mouse stroke that
38 ;; looks like a capital `C' which means `copy-region-as-kill'. Treat
39 ;; strokes just like you do key bindings. For example, Emacs sets key
40 ;; bindings globally with the `global-set-key' command. Likewise, you
41 ;; can do
42
43 ;; > M-x strokes-global-set-stroke
44
45 ;; to interactively program in a stroke. It would be wise to set the
46 ;; first one to this very command, so that from then on, you invoke
47 ;; `strokes-global-set-stroke' with a stroke. Likewise, there may
48 ;; eventually be a `strokes-local-set-stroke' command, also analogous
49 ;; to `local-set-key'.
50
51 ;; You can always unset the last stroke definition with the command
52
53 ;; > M-x strokes-unset-last-stroke
54
55 ;; and the last stroke that was added to `strokes-global-map' will be
56 ;; removed.
57
58 ;; Other analogies between strokes and key bindings are as follows:
59
60 ;; 1) To describe a stroke binding, you can type
61
62 ;; > M-x strokes-describe-stroke
63
64 ;; analogous to `describe-key'. It's also wise to have a stroke,
65 ;; like an `h', for help, or a `?', mapped to `describe-stroke'.
66
67 ;; 2) stroke bindings are set internally through the Lisp function
68 ;; `strokes-define-stroke', similar to the `define-key' function.
69 ;; some examples for a 3x3 stroke grid would be
70
71 ;; (strokes-define-stroke c-mode-stroke-map
72 ;; '((0 . 0) (1 . 1) (2 . 2))
73 ;; 'kill-region)
74 ;; (strokes-define-stroke strokes-global-map
75 ;; '((0 . 0) (0 . 1) (0 . 2) (1 . 2) (2 . 2))
76 ;; 'list-buffers)
77
78 ;; however, if you would probably just have the user enter in the
79 ;; stroke interactively and then set the stroke to whatever he/she
80 ;; entered. The Lisp function to interactively read a stroke is
81 ;; `strokes-read-stroke'. This is especially helpful when you're
82 ;; on a fast computer that can handle a 9x9 stroke grid.
83
84 ;; NOTE: only global stroke bindings are currently implemented,
85 ;; however mode- and buffer-local stroke bindings may eventually
86 ;; be implemented in a future version.
87
88 ;; The important variables to be aware of for this package are listed
89 ;; below. They can all be altered through the customizing package via
90
91 ;; > M-x customize
92
93 ;; and customizing the group named `strokes'. You can also read
94 ;; documentation on the variables there.
95
96 ;; `strokes-minimum-match-score' (determines the threshold of error that
97 ;; makes a stroke acceptable or unacceptable. If your strokes aren't
98 ;; matching, then you should raise this variable.
99
100 ;; `strokes-grid-resolution' (determines the grid dimensions that you use
101 ;; when defining/reading strokes. The finer the grid your computer can
102 ;; handle, the more you can do, but even a 3x3 grid is pretty cool.)
103 ;; The default value (9) should be fine for most decent computers.
104 ;; NOTE: This variable should not be set to a number less than 3.
105
106 ;; `strokes-display-strokes-buffer' will allow you to hide the strokes
107 ;; buffer when doing simple strokes. This is a speedup for slow
108 ;; computers as well as people who don't want to see their strokes.
109
110 ;; If you find that your mouse is accelerating too fast, you can
111 ;; execute an X command to slow it down. A good possibility is
112
113 ;; % xset m 5/4 8
114
115 ;; which seems, heuristically, to work okay, without much disruption.
116
117 ;; Whenever you load in the strokes package, you will be able to save
118 ;; what you've done upon exiting Emacs. You can also do
119
120 ;; > M-x strokes-prompt-user-save-strokes
121
122 ;; and it will save your strokes in ~/.strokes, or you may wish to change
123 ;; this by setting the variable `strokes-file'.
124
125 ;; Note that internally, all of the routines that are part of this
126 ;; package are able to deal with complex strokes, as they are a superset
127 ;; of simple strokes. However, the default of this package will map
128 ;; S-mouse-2 to the command `strokes-do-stroke', and M-mouse-2 to
129 ;; `strokes-do-complex-stroke'. Complex strokes are terminated
130 ;; with mouse button 3.
131
132 ;; You can also toggle between strokes mode by simple typing
133
134 ;; > M-x strokes-mode
135
136 ;; I hope that, with the help of others, this package will be useful
137 ;; in entering in pictographic-like language text using the mouse
138 ;; (i.e. Korean). Japanese and Chinese are a bit trickier, but I'm
139 ;; sure that with help it can be done. The next version will allow
140 ;; the user to enter strokes which "remove the pencil from the paper"
141 ;; so to speak, so one character can have multiple strokes.
142
143 ;; NOTE (Oct 7, 2006): The URLs below seem to be invalid!!!
144
145 ;; You can read more about strokes at:
146
147 ;; http://www.mit.edu/people/cadet/strokes-help.html
148
149 ;; If you're interested in using strokes for writing English into Emacs
150 ;; using strokes, then you'll want to read about it on the web page above
151 ;; or just download from http://www.mit.edu/people/cadet/strokes-abc.el,
152 ;; which is nothing but a file with some helper commands for inserting
153 ;; alphanumerics and punctuation.
154
155 ;; Great thanks to Rob Ristroph for his generosity in letting me use
156 ;; his PC to develop this, Jason Johnson for his help in algorithms,
157 ;; Euna Kim for her help in Korean, and massive thanks to the helpful
158 ;; guys on the help instance on athena (zeno, jered, amu, gsstark,
159 ;; ghudson, etc) Special thanks to Steve Baur, Kyle Jones, and Hrvoje
160 ;; Niksic for all their help. And special thanks to Dave Gillespie
161 ;; for all the elisp help--he is responsible for helping me use the cl
162 ;; macros at (near) max speed.
163
164 ;; Tasks: (what I'm getting ready for future version)...
165 ;; 2) use 'strokes-read-complex-stroke for Korean, etc.
166 ;; 4) buffer-local 'strokes-local-map, and mode-stroke-maps would be nice
167 ;; 6) add some hooks, like `strokes-read-stroke-hook'
168 ;; 7) See what people think of the factory settings. Should I change
169 ;; them? They're all pretty arbitrary in a way. I guess they
170 ;; should be minimal, but computers are getting lots faster, and
171 ;; if I choose the defaults too conservatively, then strokes will
172 ;; surely disappoint some people on decent machines (until they
173 ;; figure out M-x customize). I need feedback.
174 ;; Other: I always have the most beta version of strokes, so if you
175 ;; want it just let me know.
176
177 ;; Fixme: Use pbm instead of xpm for pixmaps to work generally.
178
179 ;;; Code:
180
181 ;;; Requirements and provisions...
182
183 (autoload 'mail-position-on-field "sendmail")
184 (eval-when-compile (require 'cl))
185
186 ;;; Constants...
187
188 (defconst strokes-lift :strokes-lift
189 "Symbol representing a stroke lift event for complex strokes.
190 Complex strokes are those which contain two or more simple strokes.")
191
192 (defconst strokes-xpm-header "/* XPM */
193 static char * stroke_xpm[] = {
194 /* width height ncolors cpp [x_hot y_hot] */
195 \"33 33 9 1 26 23\",
196 /* colors */
197 \" c none s none\",
198 \"* c #000000 s foreground\",
199 \"R c #FFFF00000000\",
200 \"O c #FFFF80000000\",
201 \"Y c #FFFFFFFF0000\",
202 \"G c #0000FFFF0000\",
203 \"B c #00000000FFFF\",
204 \"P c #FFFF0000FFFF\",
205 \". c #45458B8B0000\",
206 /* pixels */\n"
207 "The header to all xpm buffers created by strokes.")
208
209 ;;; user variables...
210
211 (defgroup strokes nil
212 "Control Emacs through mouse strokes."
213 :link '(emacs-commentary-link "strokes")
214 :group 'mouse)
215
216 (defcustom strokes-modeline-string " Strokes"
217 "Modeline identification when Strokes mode is on \(default is \" Strokes\"\)."
218 :type 'string
219 :group 'strokes)
220
221 (defcustom strokes-character ?@
222 "Character used when drawing strokes in the strokes buffer.
223 \(The default is `@', which works well.\)"
224 :type 'character
225 :group 'strokes)
226
227 (defcustom strokes-minimum-match-score 1000
228 "Minimum score for a stroke to be considered a possible match.
229 Setting this variable to 0 would require a perfectly precise match.
230 The default value is 1000, but it's mostly dependent on how precisely
231 you manage to replicate your user-defined strokes. It also depends on
232 the value of `strokes-grid-resolution', since a higher grid resolution
233 will correspond to more sample points, and thus more distance
234 measurements. Usually, this is not a problem since you first set
235 `strokes-grid-resolution' based on what your computer seems to be able
236 to handle (though the defaults are usually more than sufficient), and
237 then you can set `strokes-minimum-match-score' to something that works
238 for you. The only purpose of this variable is to insure that if you
239 do a bogus stroke that really doesn't match any of the predefined
240 ones, then strokes should NOT pick the one that came closest."
241 :type 'integer
242 :group 'strokes)
243
244 (defcustom strokes-grid-resolution 9
245 "Integer defining dimensions of the stroke grid.
246 The grid is a square grid, where `strokes-grid-resolution' defaults to
247 `9', making a 9x9 grid whose coordinates go from (0 . 0) on the top
248 left to ((strokes-grid-resolution - 1) . (strokes-grid-resolution - 1))
249 on the bottom right. The greater the resolution, the more intricate
250 your strokes can be.
251 NOTE: This variable should be odd and MUST NOT be less than 3 and need
252 not be greater than 33, which is the resolution of the pixmaps.
253 WARNING: Changing the value of this variable will gravely affect the
254 strokes you have already programmed in. You should try to
255 figure out what it should be based on your needs and on how
256 quick the particular platform(s) you're operating on, and
257 only then start programming in your custom strokes."
258 :type 'integer
259 :group 'strokes)
260
261 (defcustom strokes-file (convert-standard-filename "~/.strokes")
262 "File containing saved strokes for Strokes mode (default is ~/.strokes)."
263 :type 'file
264 :group 'strokes)
265
266 (defvar strokes-buffer-name " *strokes*"
267 "The name of the buffer that the strokes take place in.")
268
269 (defcustom strokes-use-strokes-buffer t
270 "If non-nil, the strokes buffer is used and strokes are displayed.
271 If nil, strokes will be read the same, however the user will not be
272 able to see the strokes. This be helpful for people who don't like
273 the delay in switching to the strokes buffer."
274 :type 'boolean
275 :group 'strokes)
276
277 ;;; internal variables...
278
279 (defvar strokes-window-configuration nil
280 "The special window configuration used when entering strokes.
281 This is set properly in the function `strokes-update-window-configuration'.")
282
283 (defvar strokes-last-stroke nil
284 "Last stroke entered by the user.
285 Its value gets set every time the function
286 `strokes-fill-stroke' gets called,
287 since that is the best time to set the variable.")
288
289 (defvar strokes-global-map '()
290 "Association list of strokes and their definitions.
291 Each entry is (STROKE . COMMAND) where STROKE is itself a list of
292 coordinates (X . Y) where X and Y are lists of positions on the
293 normalized stroke grid, with the top left at (0 . 0). COMMAND is the
294 corresponding interactive function.")
295
296 (defvar strokes-load-hook nil
297 "Functions to be called when Strokes is loaded.")
298
299 ;;; ### NOT IMPLEMENTED YET ###
300 ;;(defvar edit-strokes-menu
301 ;; '("Edit-Strokes"
302 ;; ["Add stroke..." strokes-global-set-stroke t]
303 ;; ["Delete stroke..." strokes-edit-delete-stroke t]
304 ;; ["Change stroke" strokes-smaller t]
305 ;; ["Change definition" strokes-larger t]
306 ;; ["[Re]List Strokes chronologically" strokes-list-strokes t]
307 ;; ["[Re]List Strokes alphabetically" strokes-list-strokes t]
308 ;; ["Quit" strokes-edit-quit t]
309 ;; ))
310
311 ;;; Macros...
312
313 ;; unused
314 ;; (defmacro strokes-while-inhibiting-garbage-collector (&rest forms)
315 ;; "Execute FORMS without interference from the garbage collector."
316 ;; `(let ((gc-cons-threshold 134217727))
317 ;; ,@forms))
318
319 (defsubst strokes-click-p (stroke)
320 "Non-nil if STROKE is really click."
321 (< (length stroke) 2))
322
323 ;;; old, but worked pretty good (just in case)...
324 ;;(defmacro strokes-define-stroke (stroke-map stroke def)
325 ;; "Add STROKE to STROKE-MAP alist with given command DEF"
326 ;; (list 'if (list '< (list 'length stroke) 2)
327 ;; (list 'error
328 ;; "That's a click, not a stroke. See `strokes-click-command'")
329 ;; (list 'setq stroke-map (list 'cons (list 'cons stroke def)
330 ;; (list 'remassoc stroke stroke-map)))))
331
332 (defsubst strokes-remassoc (key list)
333 (let (elt)
334 (while (setq elt (assoc key list))
335 (setq list (delete elt list))))
336 list)
337
338 (defmacro strokes-define-stroke (stroke-map stroke def)
339 "Add STROKE to STROKE-MAP alist with given command DEF."
340 `(if (strokes-click-p ,stroke)
341 (error "That's a click, not a stroke")
342 (setq ,stroke-map (cons (cons ,stroke ,def)
343 (strokes-remassoc ,stroke ,stroke-map)))))
344
345 (defsubst strokes-square (x)
346 "Return the square of the number X."
347 (* x x))
348
349 (defsubst strokes-distance-squared (p1 p2)
350 "Gets the distance (squared) between to points P1 and P2.
351 P1 and P2 are cons cells in the form (X . Y)."
352 (let ((x1 (car p1))
353 (y1 (cdr p1))
354 (x2 (car p2))
355 (y2 (cdr p2)))
356 (+ (strokes-square (- x2 x1))
357 (strokes-square (- y2 y1)))))
358
359 ;;; Functions...
360
361 (defsubst strokes-mouse-event-p (event)
362 (and (consp event) (symbolp (car event))
363 (or (eq (car event) 'mouse-movement)
364 (memq 'click (get (car event) 'event-symbol-elements))
365 (memq 'down (get (car event) 'event-symbol-elements))
366 (memq 'drag (get (car event) 'event-symbol-elements)))))
367
368 (defsubst strokes-button-press-event-p (event)
369 (and (consp event) (symbolp (car event))
370 (memq 'down (get (car event) 'event-symbol-elements))))
371
372 (defsubst strokes-button-release-event-p (event)
373 (and (consp event) (symbolp (car event))
374 (or (memq 'click (get (car event) 'event-symbol-elements))
375 (memq 'drag (get (car event) 'event-symbol-elements)))))
376
377 (defun strokes-event-closest-point-1 (window &optional line)
378 "Return position of start of line LINE in WINDOW.
379 If LINE is nil, return the last position visible in WINDOW."
380 (let* ((total (- (window-height window)
381 (if (window-minibuffer-p window)
382 0 1)))
383 (distance (or line total)))
384 (save-excursion
385 (goto-char (window-start window))
386 (if (= (vertical-motion distance) distance)
387 (if (not line)
388 (forward-char -1)))
389 (point))))
390
391 (defun strokes-event-closest-point (event &optional start-window)
392 "Return the nearest position to where EVENT ended its motion.
393 This is computed for the window where EVENT's motion started,
394 or for window START-WINDOW if that is specified."
395 (or start-window (setq start-window (posn-window (event-start event))))
396 (if (eq start-window (posn-window (event-end event)))
397 (if (eq (posn-point (event-end event)) 'vertical-line)
398 (strokes-event-closest-point-1 start-window
399 (cdr (posn-col-row (event-end event))))
400 (if (eq (posn-point (event-end event)) 'mode-line)
401 (strokes-event-closest-point-1 start-window)
402 (posn-point (event-end event))))
403 ;; EVENT ended in some other window.
404 (let* ((end-w (posn-window (event-end event)))
405 (end-w-top)
406 (w-top (nth 1 (window-edges start-window))))
407 (setq end-w-top
408 (if (windowp end-w)
409 (nth 1 (window-edges end-w))
410 (/ (cdr (posn-x-y (event-end event)))
411 (frame-char-height end-w))))
412 (if (>= end-w-top w-top)
413 (strokes-event-closest-point-1 start-window)
414 (window-start start-window)))))
415
416 (defun strokes-lift-p (object)
417 "Return non-nil if OBJECT is a stroke-lift."
418 (eq object strokes-lift))
419
420 (defun strokes-unset-last-stroke ()
421 "Undo the last stroke definition."
422 (interactive)
423 (let ((command (cdar strokes-global-map)))
424 (if (y-or-n-p
425 (format "Really delete last stroke definition, defined to `%s'? "
426 command))
427 (progn
428 (setq strokes-global-map (cdr strokes-global-map))
429 (message "That stroke has been deleted"))
430 (message "Nothing done"))))
431
432 ;;;###autoload
433 (defun strokes-global-set-stroke (stroke command)
434 "Interactively give STROKE the global binding as COMMAND.
435 Operated just like `global-set-key', except for strokes.
436 COMMAND is a symbol naming an interactively-callable function. STROKE
437 is a list of sampled positions on the stroke grid as described in the
438 documentation for the `strokes-define-stroke' function.
439
440 See also `strokes-global-set-stroke-string'."
441 (interactive
442 (list
443 (and (or strokes-mode (strokes-mode t))
444 (strokes-read-complex-stroke
445 "Draw with mouse button 1 (or 2). End with button 3..."))
446 (read-command "Command to map stroke to: ")))
447 (strokes-define-stroke strokes-global-map stroke command))
448
449 (defun strokes-global-set-stroke-string (stroke string)
450 "Interactively give STROKE the global binding as STRING.
451 Operated just like `global-set-key', except for strokes. STRING
452 is a string to be inserted by the stroke. STROKE is a list of
453 sampled positions on the stroke grid as described in the
454 documentation for the `strokes-define-stroke' function.
455
456 Compare `strokes-global-set-stroke'."
457 (interactive
458 (list
459 (and (or strokes-mode (strokes-mode t))
460 (strokes-read-complex-stroke
461 "Draw with mouse button 1 (or 2). End with button 3..."))
462 (read-string "String to map stroke to: ")))
463 (strokes-define-stroke strokes-global-map stroke string))
464
465 ;;(defun global-unset-stroke (stroke); FINISH THIS DEFUN!
466 ;; "delete all strokes matching STROKE from `strokes-global-map',
467 ;; letting the user input
468 ;; the stroke with the mouse"
469 ;; (interactive
470 ;; (list
471 ;; (strokes-read-stroke "Enter the stroke you want to delete...")))
472 ;; (strokes-define-stroke 'strokes-global-map stroke command))
473
474 (defun strokes-get-grid-position (stroke-extent position &optional grid-resolution)
475 "Map POSITION to a new grid position.
476 Do so based on its STROKE-EXTENT and GRID-RESOLUTION.
477 STROKE-EXTENT as a list \(\(XMIN . YMIN\) \(XMAX . YMAX\)\).
478 If POSITION is a `strokes-lift', then it is itself returned.
479 Optional GRID-RESOLUTION may be used in place of `strokes-grid-resolution'.
480 The grid is a square whose dimension is [0,GRID-RESOLUTION)."
481 (cond ((consp position) ; actual pixel location
482 (let ((grid-resolution (or grid-resolution strokes-grid-resolution))
483 (x (car position))
484 (y (cdr position))
485 (xmin (caar stroke-extent))
486 (ymin (cdar stroke-extent))
487 ;; the `1+' is there to insure that the
488 ;; formula evaluates correctly at the boundaries
489 (xmax (1+ (car (cadr stroke-extent))))
490 (ymax (1+ (cdr (cadr stroke-extent)))))
491 (cons (floor (* grid-resolution
492 (/ (float (- x xmin))
493 (- xmax xmin))))
494 (floor (* grid-resolution
495 (/ (float (- y ymin))
496 (- ymax ymin)))))))
497 ((strokes-lift-p position) ; stroke lift
498 strokes-lift)))
499
500 (defun strokes-get-stroke-extent (pixel-positions)
501 "From a list of absolute PIXEL-POSITIONS, return absolute spatial extent.
502 The return value is a list ((XMIN . YMIN) (XMAX . YMAX))."
503 (if pixel-positions
504 (let ((xmin (caar pixel-positions))
505 (xmax (caar pixel-positions))
506 (ymin (cdar pixel-positions))
507 (ymax (cdar pixel-positions))
508 (rest (cdr pixel-positions)))
509 (while rest
510 (if (consp (car rest))
511 (let ((x (caar rest))
512 (y (cdar rest)))
513 (if (< x xmin)
514 (setq xmin x))
515 (if (> x xmax)
516 (setq xmax x))
517 (if (< y ymin)
518 (setq ymin y))
519 (if (> y ymax)
520 (setq ymax y))))
521 (setq rest (cdr rest)))
522 (let ((delta-x (- xmax xmin))
523 (delta-y (- ymax ymin)))
524 (if (> delta-x delta-y)
525 (setq ymin (- ymin
526 (/ (- delta-x delta-y)
527 2))
528 ymax (+ ymax
529 (/ (- delta-x delta-y)
530 2)))
531 (setq xmin (- xmin
532 (/ (- delta-y delta-x)
533 2))
534 xmax (+ xmax
535 (/ (- delta-y delta-x)
536 2))))
537 (list (cons xmin ymin)
538 (cons xmax ymax))))
539 nil))
540
541 (defun strokes-eliminate-consecutive-redundancies (entries)
542 "Return a list with no consecutive redundant entries."
543 ;; defun a grande vitesse grace a Dave G.
544 (loop for element on entries
545 if (not (equal (car element) (cadr element)))
546 collect (car element)))
547 ;; (loop for element on entries
548 ;; nconc (if (not (equal (car el) (cadr el)))
549 ;; (list (car el)))))
550 ;; yet another (orig) way of doing it...
551 ;; (if entries
552 ;; (let* ((current (car entries))
553 ;; (rest (cdr entries))
554 ;; (non-redundant-list (list current))
555 ;; (next nil))
556 ;; (while rest
557 ;; (setq next (car rest))
558 ;; (if (equal current next)
559 ;; (setq rest (cdr rest))
560 ;; (setq non-redundant-list (cons next non-redundant-list)
561 ;; current next
562 ;; rest (cdr rest))))
563 ;; (nreverse non-redundant-list))
564 ;; nil))
565
566 (defun strokes-renormalize-to-grid (positions &optional grid-resolution)
567 "Map POSITIONS to a new grid whose dimensions are based on GRID-RESOLUTION.
568 POSITIONS is a list of positions and stroke-lifts.
569 Optional GRID-RESOLUTION may be used in place of `strokes-grid-resolution'.
570 The grid is a square whose dimension is [0,GRID-RESOLUTION)."
571 (or grid-resolution (setq grid-resolution strokes-grid-resolution))
572 (let ((stroke-extent (strokes-get-stroke-extent positions)))
573 (mapcar (function
574 (lambda (pos)
575 (strokes-get-grid-position stroke-extent pos grid-resolution)))
576 positions)))
577
578 (defun strokes-fill-stroke (unfilled-stroke &optional force)
579 "Fill in missing grid locations in the list of UNFILLED-STROKE.
580 If FORCE is non-nil, then fill the stroke even if it's `stroke-click'.
581 NOTE: This is where the global variable `strokes-last-stroke' is set."
582 (setq strokes-last-stroke ; this is global
583 (if (and (strokes-click-p unfilled-stroke)
584 (not force))
585 unfilled-stroke
586 (loop for grid-locs on unfilled-stroke
587 nconc (let* ((current (car grid-locs))
588 (current-is-a-point-p (consp current))
589 (next (cadr grid-locs))
590 (next-is-a-point-p (consp next))
591 (both-are-points-p (and current-is-a-point-p
592 next-is-a-point-p))
593 (x1 (and current-is-a-point-p
594 (car current)))
595 (y1 (and current-is-a-point-p
596 (cdr current)))
597 (x2 (and next-is-a-point-p
598 (car next)))
599 (y2 (and next-is-a-point-p
600 (cdr next)))
601 (delta-x (and both-are-points-p
602 (- x2 x1)))
603 (delta-y (and both-are-points-p
604 (- y2 y1)))
605 (slope (and both-are-points-p
606 (if (zerop delta-x)
607 nil ; undefined vertical slope
608 (/ (float delta-y)
609 delta-x)))))
610 (cond ((not both-are-points-p)
611 (list current))
612 ((null slope) ; undefined vertical slope
613 (if (>= delta-y 0)
614 (loop for y from y1 below y2
615 collect (cons x1 y))
616 (loop for y from y1 above y2
617 collect (cons x1 y))))
618 ((zerop slope) ; (= y1 y2)
619 (if (>= delta-x 0)
620 (loop for x from x1 below x2
621 collect (cons x y1))
622 (loop for x from x1 above x2
623 collect (cons x y1))))
624 ((>= (abs delta-x) (abs delta-y))
625 (if (> delta-x 0)
626 (loop for x from x1 below x2
627 collect (cons x
628 (+ y1
629 (round (* slope
630 (- x x1))))))
631 (loop for x from x1 above x2
632 collect (cons x
633 (+ y1
634 (round (* slope
635 (- x x1))))))))
636 (t ; (< (abs delta-x) (abs delta-y))
637 (if (> delta-y 0)
638 (loop for y from y1 below y2
639 collect (cons (+ x1
640 (round (/ (- y y1)
641 slope)))
642 y))
643 (loop for y from y1 above y2
644 collect (cons (+ x1
645 (round (/ (- y y1)
646 slope)))
647 y))))))))))
648
649 (defun strokes-rate-stroke (stroke1 stroke2)
650 "Rates STROKE1 with STROKE2 and return a score based on a distance metric.
651 Note: the rating is an error rating, and therefore, a return of 0
652 represents a perfect match. Also note that the order of stroke
653 arguments is order-independent for the algorithm used here."
654 (if (and stroke1 stroke2)
655 (let ((rest1 (cdr stroke1))
656 (rest2 (cdr stroke2))
657 (err (strokes-distance-squared (car stroke1)
658 (car stroke2))))
659 (while (and rest1 rest2)
660 (while (and (consp (car rest1))
661 (consp (car rest2)))
662 (setq err (+ err
663 (strokes-distance-squared (car rest1)
664 (car rest2)))
665 stroke1 rest1
666 stroke2 rest2
667 rest1 (cdr stroke1)
668 rest2 (cdr stroke2)))
669 (cond ((and (strokes-lift-p (car rest1))
670 (strokes-lift-p (car rest2)))
671 (setq rest1 (cdr rest1)
672 rest2 (cdr rest2)))
673 ((strokes-lift-p (car rest2))
674 (while (consp (car rest1))
675 (setq err (+ err
676 (strokes-distance-squared (car rest1)
677 (car stroke2)))
678 rest1 (cdr rest1))))
679 ((strokes-lift-p (car rest1))
680 (while (consp (car rest2))
681 (setq err (+ err
682 (strokes-distance-squared (car stroke1)
683 (car rest2)))
684 rest2 (cdr rest2))))))
685 (if (null rest2)
686 (while (consp (car rest1))
687 (setq err (+ err
688 (strokes-distance-squared (car rest1)
689 (car stroke2)))
690 rest1 (cdr rest1))))
691 (if (null rest1)
692 (while (consp (car rest2))
693 (setq err (+ err
694 (strokes-distance-squared (car stroke1)
695 (car rest2)))
696 rest2 (cdr rest2))))
697 (if (or (strokes-lift-p (car rest1))
698 (strokes-lift-p (car rest2)))
699 (setq err nil)
700 err))
701 nil))
702
703 (defun strokes-match-stroke (stroke stroke-map)
704 "Find the best matching command of STROKE in STROKE-MAP.
705 Returns the corresponding match as (COMMAND . SCORE)."
706 (if (and stroke stroke-map)
707 (let ((score (strokes-rate-stroke stroke (caar stroke-map)))
708 (command (cdar stroke-map))
709 (map (cdr stroke-map)))
710 (while map
711 (let ((newscore (strokes-rate-stroke stroke (caar map))))
712 (if (or (and newscore score (< newscore score))
713 (and newscore (null score)))
714 (setq score newscore
715 command (cdar map)))
716 (setq map (cdr map))))
717 (if score
718 (cons command score)
719 nil))
720 nil))
721
722 ;;;###autoload
723 (defun strokes-read-stroke (&optional prompt event)
724 "Read a simple stroke (interactively) and return the stroke.
725 Optional PROMPT in minibuffer displays before and during stroke reading.
726 This function will display the stroke interactively as it is being
727 entered in the strokes buffer if the variable
728 `strokes-use-strokes-buffer' is non-nil.
729 Optional EVENT is acceptable as the starting event of the stroke."
730 (save-excursion
731 (let ((pix-locs nil)
732 (grid-locs nil)
733 (safe-to-draw-p nil))
734 (if strokes-use-strokes-buffer
735 ;; switch to the strokes buffer and
736 ;; display the stroke as it's being read
737 (save-window-excursion
738 (set-window-configuration strokes-window-configuration)
739 ;; The frame has been resized, so we need to refill the
740 ;; strokes buffer so that the strokes canvas is the whole
741 ;; visible buffer.
742 (unless (> 1 (abs (- (line-end-position) (window-width))))
743 (strokes-fill-current-buffer-with-whitespace))
744 (when prompt
745 (message "%s" prompt)
746 (setq event (read-event))
747 (or (strokes-button-press-event-p event)
748 (error "You must draw with the mouse")))
749 (unwind-protect
750 (track-mouse
751 (or event (setq event (read-event)
752 safe-to-draw-p t))
753 (while (not (strokes-button-release-event-p event))
754 (if (strokes-mouse-event-p event)
755 (let ((point (strokes-event-closest-point event)))
756 (if (and point safe-to-draw-p)
757 ;; we can draw that point
758 (progn
759 (goto-char point)
760 (subst-char-in-region point (1+ point)
761 ?\s strokes-character))
762 ;; otherwise, we can start drawing the next time...
763 (setq safe-to-draw-p t))
764 (push (cdr (mouse-pixel-position))
765 pix-locs)))
766 (setq event (read-event)))))
767 ;; protected
768 ;; clean up strokes buffer and then bury it.
769 (when (equal (buffer-name) strokes-buffer-name)
770 (subst-char-in-region (point-min) (point-max)
771 strokes-character ?\s)
772 (goto-char (point-min))
773 (bury-buffer))))
774 ;; Otherwise, don't use strokes buffer and read stroke silently
775 (when prompt
776 (message "%s" prompt)
777 (setq event (read-event))
778 (or (strokes-button-press-event-p event)
779 (error "You must draw with the mouse")))
780 (track-mouse
781 (or event (setq event (read-event)))
782 (while (not (strokes-button-release-event-p event))
783 (if (strokes-mouse-event-p event)
784 (push (cdr (mouse-pixel-position))
785 pix-locs))
786 (setq event (read-event))))
787 (setq grid-locs (strokes-renormalize-to-grid (nreverse pix-locs)))
788 (strokes-fill-stroke
789 (strokes-eliminate-consecutive-redundancies grid-locs)))))
790
791 ;;;###autoload
792 (defun strokes-read-complex-stroke (&optional prompt event)
793 "Read a complex stroke (interactively) and return the stroke.
794 Optional PROMPT in minibuffer displays before and during stroke reading.
795 Note that a complex stroke allows the user to pen-up and pen-down. This
796 is implemented by allowing the user to paint with button 1 or button 2 and
797 then complete the stroke with button 3.
798 Optional EVENT is acceptable as the starting event of the stroke."
799 (save-excursion
800 (save-window-excursion
801 (set-window-configuration strokes-window-configuration)
802 (let ((pix-locs nil)
803 (grid-locs nil))
804 (if prompt
805 (while (not (strokes-button-press-event-p event))
806 (message "%s" prompt)
807 (setq event (read-event))))
808 (unwind-protect
809 (track-mouse
810 (or event (setq event (read-event)))
811 (while (not (and (strokes-button-press-event-p event)
812 (eq 'mouse-3
813 (car (get (car event)
814 'event-symbol-elements)))))
815 (while (not (strokes-button-release-event-p event))
816 (if (strokes-mouse-event-p event)
817 (let ((point (strokes-event-closest-point event)))
818 (when point
819 (goto-char point)
820 (subst-char-in-region point (1+ point)
821 ?\s strokes-character))
822 (push (cdr (mouse-pixel-position))
823 pix-locs)))
824 (setq event (read-event)))
825 (push strokes-lift pix-locs)
826 (while (not (strokes-button-press-event-p event))
827 (setq event (read-event))))
828 ;; ### KLUDGE! ### sit and wait
829 ;; for some useless event to
830 ;; happen to fix the minibuffer bug.
831 (while (not (strokes-button-release-event-p (read-event))))
832 (setq pix-locs (nreverse (cdr pix-locs))
833 grid-locs (strokes-renormalize-to-grid pix-locs))
834 (strokes-fill-stroke
835 (strokes-eliminate-consecutive-redundancies grid-locs)))
836 ;; protected
837 (when (equal (buffer-name) strokes-buffer-name)
838 (subst-char-in-region (point-min) (point-max)
839 strokes-character ?\s)
840 (goto-char (point-min))
841 (bury-buffer)))))))
842
843 (defun strokes-execute-stroke (stroke)
844 "Given STROKE, execute the command which corresponds to it.
845 The command will be executed provided one exists for that stroke,
846 based on the variable `strokes-minimum-match-score'.
847 If no stroke matches, nothing is done and return value is nil."
848 (let* ((match (strokes-match-stroke stroke strokes-global-map))
849 (command (car match))
850 (score (cdr match)))
851 (cond ((and match (<= score strokes-minimum-match-score))
852 (message "%s" command)
853 (command-execute command))
854 ((null strokes-global-map)
855 (if (file-exists-p strokes-file)
856 (and (y-or-n-p
857 (format "No strokes loaded. Load `%s'? "
858 strokes-file))
859 (strokes-load-user-strokes))
860 (error "No strokes defined; use `strokes-global-set-stroke'")))
861 (t
862 (error
863 "No stroke matches; see variable `strokes-minimum-match-score'")
864 nil))))
865
866 ;;;###autoload
867 (defun strokes-do-stroke (event)
868 "Read a simple stroke from the user and then execute its command.
869 This must be bound to a mouse event."
870 (interactive "e")
871 (or strokes-mode (strokes-mode t))
872 (strokes-execute-stroke (strokes-read-stroke nil event)))
873
874 ;;;###autoload
875 (defun strokes-do-complex-stroke (event)
876 "Read a complex stroke from the user and then execute its command.
877 This must be bound to a mouse event."
878 (interactive "e")
879 (or strokes-mode (strokes-mode t))
880 (strokes-execute-stroke (strokes-read-complex-stroke nil event)))
881
882 ;;;###autoload
883 (defun strokes-describe-stroke (stroke)
884 "Displays the command which STROKE maps to, reading STROKE interactively."
885 (interactive
886 (list
887 (strokes-read-complex-stroke
888 "Enter stroke to describe; end with button 3...")))
889 (let* ((match (strokes-match-stroke stroke strokes-global-map))
890 (command (car match))
891 (score (cdr match)))
892 (if (and match
893 (<= score strokes-minimum-match-score))
894 (message "That stroke maps to `%s'" command)
895 (message "That stroke is undefined"))
896 (sleep-for 1))) ; helpful for recursive edits
897
898 ;;;###autoload
899 (defun strokes-help ()
900 "Get instruction on using the Strokes package."
901 (interactive)
902 (with-output-to-temp-buffer "*Help with Strokes*"
903 (princ
904 (substitute-command-keys
905 "This is help for the strokes package.
906
907 ------------------------------------------------------------
908
909 ** Strokes...
910
911 The strokes package allows you to define strokes, made with
912 the mouse or other pointer device, that Emacs can interpret as
913 corresponding to commands, and then executes the commands. It does
914 character recognition, so you don't have to worry about getting it
915 right every time.
916
917 Strokes also allows you to compose documents graphically. You can
918 fully edit documents in Chinese, Japanese, etc. based on Emacs
919 strokes. Once you've done so, you can ASCII compress-and-encode them
920 and then safely save them for later use, send letters to friends
921 \(using Emacs, of course). Strokes will later decode these documents,
922 extracting the strokes for editing use once again, so the editing
923 cycle can continue.
924
925 Strokes are easy to program and fun to use. To start strokes going,
926 you'll want to put the following line in your .emacs file as mentioned
927 in the commentary to strokes.el.
928
929 This will load strokes when and only when you start Emacs on a window
930 system, with a mouse or other pointer device defined.
931
932 To toggle strokes-mode, you just do
933
934 > M-x strokes-mode
935
936 ** Strokes for controlling the behavior of Emacs...
937
938 When you're ready to start defining strokes, just use the command
939
940 > M-x strokes-global-set-stroke
941
942 You will see a ` *strokes*' buffer which is waiting for you to enter in
943 your stroke. When you enter in the stroke, you draw with button 1 or
944 button 2, and then end with button 3. Next, you enter in the command
945 which will be executed when that stroke is invoked. Simple as that.
946 For now, try to define a stroke to copy a region. This is a popular
947 edit command, so type
948
949 > M-x strokes-global-set-stroke
950
951 Then, in the ` *strokes*' buffer, draw the letter `C' (for `copy')
952 and then, when it asks you to enter the command to map that to, type
953
954 > copy-region-as-kill
955
956 That's about as hard as it gets.
957 Remember: paint with button 1 or button 2 and then end with button 3.
958
959 If ever you want to know what a certain strokes maps to, then do
960
961 > M-x strokes-describe-stroke
962
963 and you can enter in any arbitrary stroke. Remember: The strokes
964 package lets you program in simple and complex (multi-lift) strokes.
965 The only difference is how you *invoke* the two. You will most likely
966 use simple strokes, as complex strokes were developed for
967 Chinese/Japanese/Korean. So the shifted middle mouse button (S-mouse-2) will
968 invoke the command `strokes-do-stroke'.
969
970 If ever you define a stroke which you don't like, then you can unset
971 it with the command
972
973 > M-x strokes-unset-last-stroke
974
975 You can always get an idea of what your current strokes look like with
976 the command
977
978 > M-x strokes-list-strokes
979
980 Your strokes will be displayed in alphabetical order (based on command
981 names) and the beginning of each simple stroke will be marked by a
982 color dot. Since you may have several simple strokes in a complex
983 stroke, the dot colors are arranged in the rainbow color sequence,
984 `ROYGBIV'. If you want a listing of your strokes from most recent
985 down, then use a prefix argument:
986
987 > C-u M-x strokes-list-strokes
988
989 Your strokes are stored as you enter them. They get saved in a file
990 called ~/.strokes, along with other strokes configuration variables.
991 You can change this location by setting the variable `strokes-file'.
992 You will be prompted to save them when you exit Emacs, or you can save
993 them with
994
995 > M-x strokes-prompt-user-save-strokes
996
997 Your strokes get loaded automatically when you enable `strokes-mode'.
998 You can also load in your user-defined strokes with
999
1000 > M-x strokes-load-user-strokes
1001
1002 ** Strokes for pictographic editing...
1003
1004 If you'd like to create graphical files with strokes, you'll have to
1005 be running a version of Emacs with XPM support. You use the binding
1006 to `strokes-compose-complex-stroke' to start drawing your strokes.
1007 These are just complex strokes, and thus continue drawing with mouse-1
1008 or mouse-2 and end with mouse-3. Then the stroke image gets inserted
1009 into the buffer. You treat it somewhat like any other character,
1010 which you can copy, paste, delete, move, etc. When all is done, you
1011 may want to send the file, or save it. This is done with
1012
1013 > M-x strokes-encode-buffer
1014
1015 Likewise, to decode the strokes from a strokes-encoded buffer you do
1016
1017 > M-x strokes-decode-buffer
1018
1019 ** A few more important things...
1020
1021 o The command `strokes-do-complex-stroke' is invoked with M-mouse-2,
1022 so that you can execute complex strokes (i.e. with more than one lift)
1023 if preferred.
1024
1025 o Strokes are a bit computer-dependent in that they depend somewhat on
1026 the speed of the computer you're working on. This means that you
1027 may have to tweak some variables. You can read about them in the
1028 commentary of `strokes.el'. Better to just use \\[apropos] and read their
1029 docstrings. All variables/functions start with `strokes'. The one
1030 variable which many people wanted to see was
1031 `strokes-use-strokes-buffer' which allows the user to use strokes
1032 silently--without displaying the strokes. All variables can be set
1033 by customizing the group `strokes' via \\[customize-group]."))
1034 (set-buffer standard-output)
1035 (help-mode)
1036 (help-print-return-message)))
1037
1038 (defalias 'strokes-report-bug 'report-emacs-bug)
1039
1040 (defsubst strokes-fill-current-buffer-with-whitespace ()
1041 "Erase the contents of the current buffer and fill it with whitespace."
1042 (erase-buffer)
1043 (loop repeat (frame-height) do
1044 (insert-char ?\s (1- (frame-width)))
1045 (newline))
1046 (goto-char (point-min)))
1047
1048 (defun strokes-window-configuration-changed-p ()
1049 "Non-nil if the `strokes-window-configuration' frame properties changed.
1050 This is based on the last time `strokes-window-configuration' was updated."
1051 (compare-window-configurations (current-window-configuration)
1052 strokes-window-configuration))
1053
1054 (defun strokes-update-window-configuration ()
1055 "Ensure that `strokes-window-configuration' is up-to-date."
1056 (interactive)
1057 (let ((current-window (selected-window)))
1058 (cond ((or (window-minibuffer-p current-window)
1059 (window-dedicated-p current-window))
1060 ;; don't try to update strokes window configuration
1061 ;; if window is dedicated or a minibuffer
1062 nil)
1063 ((or (called-interactively-p 'interactive)
1064 (not (buffer-live-p (get-buffer strokes-buffer-name)))
1065 (null strokes-window-configuration))
1066 ;; create `strokes-window-configuration' from scratch...
1067 (save-excursion
1068 (save-window-excursion
1069 (set-buffer (get-buffer-create strokes-buffer-name))
1070 (set-window-buffer current-window strokes-buffer-name)
1071 (delete-other-windows)
1072 (fundamental-mode)
1073 (auto-save-mode 0)
1074 (font-lock-mode 0)
1075 (abbrev-mode 0)
1076 (buffer-disable-undo (current-buffer))
1077 (setq truncate-lines nil)
1078 (strokes-fill-current-buffer-with-whitespace)
1079 (setq strokes-window-configuration (current-window-configuration))
1080 (bury-buffer))))
1081 ((strokes-window-configuration-changed-p) ; simple update
1082 ;; update the strokes-window-configuration for this
1083 ;; specific frame...
1084 (save-excursion
1085 (save-window-excursion
1086 (set-window-buffer current-window strokes-buffer-name)
1087 (delete-other-windows)
1088 (strokes-fill-current-buffer-with-whitespace)
1089 (setq strokes-window-configuration (current-window-configuration))
1090 (bury-buffer)))))))
1091
1092 ;;;###autoload
1093 (defun strokes-load-user-strokes ()
1094 "Load user-defined strokes from file named by `strokes-file'."
1095 (interactive)
1096 (cond ((and (file-exists-p strokes-file)
1097 (file-readable-p strokes-file))
1098 (load-file strokes-file))
1099 ((called-interactively-p 'interactive)
1100 (error "Trouble loading user-defined strokes; nothing done"))
1101 (t
1102 (message "No user-defined strokes, sorry"))))
1103
1104 (defun strokes-prompt-user-save-strokes ()
1105 "Save user-defined strokes to file named by `strokes-file'."
1106 (interactive)
1107 (save-excursion
1108 (let ((current strokes-global-map))
1109 (unwind-protect
1110 (progn
1111 (setq strokes-global-map nil)
1112 (strokes-load-user-strokes)
1113 (if (and (not (equal current strokes-global-map))
1114 (or (called-interactively-p 'interactive)
1115 (yes-or-no-p "Save your strokes? ")))
1116 (progn
1117 (require 'pp) ; pretty-print variables
1118 (message "Saving strokes in %s..." strokes-file)
1119 (get-buffer-create "*saved-strokes*")
1120 (set-buffer "*saved-strokes*")
1121 (erase-buffer)
1122 (emacs-lisp-mode)
1123 (goto-char (point-min))
1124 (insert
1125 ";; -*- emacs-lisp -*-\n")
1126 (insert (format ";;; saved strokes for %s, as of %s\n\n"
1127 (user-full-name)
1128 (format-time-string "%B %e, %Y" nil)))
1129 (message "Saving strokes in %s..." strokes-file)
1130 (insert (format "(setq strokes-global-map\n'%s)"
1131 (pp current)))
1132 (message "Saving strokes in %s..." strokes-file)
1133 (indent-region (point-min) (point-max) nil)
1134 (write-region (point-min)
1135 (point-max)
1136 strokes-file))
1137 (message "(no changes need to be saved)")))
1138 ;; protected
1139 (if (get-buffer "*saved-strokes*")
1140 (kill-buffer (get-buffer "*saved-strokes*")))
1141 (setq strokes-global-map current)))))
1142
1143 (defun strokes-toggle-strokes-buffer (&optional arg)
1144 "Toggle the use of the strokes buffer.
1145 In other words, toggle the variable `strokes-use-strokes-buffer'.
1146 With ARG, use strokes buffer if and only if ARG is positive or true.
1147 Returns value of `strokes-use-strokes-buffer'."
1148 (interactive "P")
1149 (setq strokes-use-strokes-buffer
1150 (if arg (> (prefix-numeric-value arg) 0)
1151 (not strokes-use-strokes-buffer))))
1152
1153 (defun strokes-xpm-for-stroke (&optional stroke bufname b/w-only)
1154 "Create an XPM pixmap for the given STROKE in buffer ` *strokes-xpm*'.
1155 If STROKE is not supplied, then `strokes-last-stroke' will be used.
1156 Optional BUFNAME to name something else.
1157 The pixmap will contain time information via rainbow dot colors
1158 where each individual strokes begins.
1159 Optional B/W-ONLY non-nil will create a mono pixmap, not intended
1160 for trying to figure out the order of strokes, but rather for reading
1161 the stroke as a character in some language."
1162 (interactive)
1163 (save-excursion
1164 (let ((buf (get-buffer-create (or bufname " *strokes-xpm*")))
1165 (stroke (strokes-eliminate-consecutive-redundancies
1166 (strokes-fill-stroke
1167 (strokes-renormalize-to-grid (or stroke
1168 strokes-last-stroke)
1169 31))))
1170 (lift-flag t)
1171 (rainbow-chars (list ?R ?O ?Y ?G ?B ?P))) ; ROYGBIV w/o indigo
1172 (set-buffer buf)
1173 (erase-buffer)
1174 (insert strokes-xpm-header)
1175 (loop repeat 33 do
1176 (insert ?\")
1177 (insert-char ?\s 33)
1178 (insert "\",")
1179 (newline)
1180 finally
1181 (forward-line -1)
1182 (end-of-line)
1183 (insert "}\n"))
1184 (loop for point in stroke
1185 for x = (car-safe point)
1186 for y = (cdr-safe point) do
1187 (cond ((consp point)
1188 ;; draw a point, and possibly a starting-point
1189 (if (and lift-flag (not b/w-only))
1190 ;; mark starting point with the appropriate color
1191 (let ((char (or (car rainbow-chars) ?\.)))
1192 (loop for i from 0 to 2 do
1193 (loop for j from 0 to 2 do
1194 (goto-char (point-min))
1195 (forward-line (+ 15 i y))
1196 (forward-char (+ 1 j x))
1197 (delete-char 1)
1198 (insert char)))
1199 (setq rainbow-chars (cdr rainbow-chars)
1200 lift-flag nil))
1201 ;; Otherwise, just plot the point...
1202 (goto-char (point-min))
1203 (forward-line (+ 16 y))
1204 (forward-char (+ 2 x))
1205 (subst-char-in-region (point) (1+ (point)) ?\s ?\*)))
1206 ((strokes-lift-p point)
1207 ;; a lift--tell the loop to X out the next point...
1208 (setq lift-flag t))))
1209 (when (called-interactively-p 'interactive)
1210 (pop-to-buffer " *strokes-xpm*")
1211 ;; (xpm-mode 1)
1212 (goto-char (point-min))
1213 (put-image (create-image (buffer-string) 'xpm t :ascent 100)
1214 (line-end-position))))))
1215
1216 ;;; Strokes Edit stuff... ### NOT IMPLEMENTED YET ###
1217
1218 ;;(defun strokes-edit-quit ()
1219 ;; (interactive)
1220 ;; (or (one-window-p t 0)
1221 ;; (delete-window))
1222 ;; (kill-buffer "*Strokes List*"))
1223
1224 ;;(define-derived-mode edit-strokes-mode list-mode
1225 ;; "Edit-Strokes"
1226 ;; "Major mode for `edit-strokes' and `list-strokes' buffers.
1227
1228 ;;Editing commands:
1229
1230 ;;\\{edit-strokes-mode-map}"
1231 ;; (setq truncate-lines nil
1232 ;; auto-show-mode nil ; don't want problems here either
1233 ;; mode-popup-menu edit-strokes-menu) ; what about extent-specific stuff?
1234 ;; (and (featurep 'menubar)
1235 ;; current-menubar
1236 ;; (set (make-local-variable 'current-menubar)
1237 ;; (copy-sequence current-menubar))
1238 ;; (add-submenu nil edit-strokes-menu)))
1239
1240 ;;(let ((map edit-strokes-mode-map))
1241 ;; (define-key map "<" 'beginning-of-buffer)
1242 ;; (define-key map ">" 'end-of-buffer)
1243 ;; ;; (define-key map "c" 'strokes-copy-other-face)
1244 ;; ;; (define-key map "C" 'strokes-copy-this-face)
1245 ;; ;; (define-key map "s" 'strokes-smaller)
1246 ;; ;; (define-key map "l" 'strokes-larger)
1247 ;; ;; (define-key map "b" 'strokes-bold)
1248 ;; ;; (define-key map "i" 'strokes-italic)
1249 ;; (define-key map "e" 'strokes-list-edit)
1250 ;; ;; (define-key map "f" 'strokes-font)
1251 ;; ;; (define-key map "u" 'strokes-underline)
1252 ;; ;; (define-key map "t" 'strokes-truefont)
1253 ;; ;; (define-key map "F" 'strokes-foreground)
1254 ;; ;; (define-key map "B" 'strokes-background)
1255 ;; ;; (define-key map "D" 'strokes-doc-string)
1256 ;; (define-key map "a" 'strokes-global-set-stroke)
1257 ;; (define-key map "d" 'strokes-list-delete-stroke)
1258 ;; ;; (define-key map "n" 'strokes-list-next)
1259 ;; ;; (define-key map "p" 'strokes-list-prev)
1260 ;; ;; (define-key map " " 'strokes-list-next)
1261 ;; ;; (define-key map "\C-?" 'strokes-list-prev)
1262 ;; (define-key map "g" 'strokes-list-strokes) ; refresh display
1263 ;; (define-key map "q" 'strokes-edit-quit)
1264 ;; (define-key map [(control c) (control c)] 'bury-buffer))
1265
1266 ;;;;;###autoload
1267 ;;(defun strokes-edit-strokes (&optional chronological strokes-map)
1268 ;; ;; ### DEAL WITH THE 2nd ARGUMENT ISSUE! ###
1269 ;; "Edit strokes in a pop-up buffer containing strokes and their definitions.
1270 ;;If STROKES-MAP is not given, `strokes-global-map' will be used instead.
1271
1272 ;;Editing commands:
1273
1274 ;;\\{edit-faces-mode-map}"
1275 ;; (interactive "P")
1276 ;; (pop-to-buffer (get-buffer-create "*Strokes List*"))
1277 ;; (reset-buffer (current-buffer)) ; handy function from minibuf.el
1278 ;; (setq strokes-map (or strokes-map
1279 ;; strokes-global-map
1280 ;; (progn
1281 ;; (strokes-load-user-strokes)
1282 ;; strokes-global-map)))
1283 ;; (or chronological
1284 ;; (setq strokes-map (sort (copy-sequence strokes-map)
1285 ;; 'strokes-alphabetic-lessp)))
1286 ;; ;; (push-window-configuration)
1287 ;; (insert
1288 ;; "Command Stroke\n"
1289 ;; "------- ------")
1290 ;; (loop for def in strokes-map
1291 ;; for i from 0 to (1- (length strokes-map)) do
1292 ;; (let ((stroke (car def))
1293 ;; (command-name (symbol-name (cdr def))))
1294 ;; (strokes-xpm-for-stroke stroke " *strokes-xpm*")
1295 ;; (newline 2)
1296 ;; (insert-char ?\s 45)
1297 ;; (beginning-of-line)
1298 ;; (insert command-name)
1299 ;; (beginning-of-line)
1300 ;; (forward-char 45)
1301 ;; (set (intern (format "strokes-list-annotation-%d" i))
1302 ;; (make-annotation (make-glyph
1303 ;; (list
1304 ;; (vector 'xpm
1305 ;; :data (buffer-substring
1306 ;; (point-min " *strokes-xpm*")
1307 ;; (point-max " *strokes-xpm*")
1308 ;; " *strokes-xpm*"))
1309 ;; [string :data "[Stroke]"]))
1310 ;; (point) 'text))
1311 ;; (set-annotation-data (symbol-value (intern (format "strokes-list-annotation-%d" i)))
1312 ;; def))
1313 ;; finally do (kill-region (1+ (point)) (point-max)))
1314 ;; (edit-strokes-mode)
1315 ;; (goto-char (point-min)))
1316
1317 ;;;;;###autoload
1318 ;;(defalias 'edit-strokes 'strokes-edit-strokes)
1319
1320 (defvar view-mode-map)
1321
1322 ;;;###autoload
1323 (defun strokes-list-strokes (&optional chronological strokes-map)
1324 "Pop up a buffer containing an alphabetical listing of strokes in STROKES-MAP.
1325 With CHRONOLOGICAL prefix arg \(\\[universal-argument]\) list strokes
1326 chronologically by command name.
1327 If STROKES-MAP is not given, `strokes-global-map' will be used instead."
1328 (interactive "P")
1329 (setq strokes-map (or strokes-map
1330 strokes-global-map
1331 (progn
1332 (strokes-load-user-strokes)
1333 strokes-global-map)))
1334 (if (not chronological)
1335 ;; then alphabetize the strokes based on command names...
1336 (setq strokes-map (sort (copy-sequence strokes-map)
1337 (function strokes-alphabetic-lessp))))
1338 (let ((config (current-window-configuration)))
1339 (set-buffer (get-buffer-create "*Strokes List*"))
1340 (setq buffer-read-only nil)
1341 (erase-buffer)
1342 (insert
1343 "Command Stroke\n"
1344 "------- ------")
1345 (loop for def in strokes-map do
1346 (let ((stroke (car def))
1347 (command-name (if (symbolp (cdr def))
1348 (symbol-name (cdr def))
1349 (prin1-to-string (cdr def)))))
1350 (strokes-xpm-for-stroke stroke " *strokes-xpm*")
1351 (newline 2)
1352 (insert-char ?\s 45)
1353 (beginning-of-line)
1354 (insert command-name)
1355 (beginning-of-line)
1356 (forward-char 45)
1357 (insert-image
1358 (create-image (with-current-buffer " *strokes-xpm*"
1359 (buffer-string))
1360 'xpm t
1361 :color-symbols
1362 `(("foreground"
1363 . ,(frame-parameter nil 'foreground-color))))))
1364 finally do (unless (eobp)
1365 (kill-region (1+ (point)) (point-max))))
1366 (view-buffer "*Strokes List*" nil)
1367 (set (make-local-variable 'view-mode-map)
1368 (let ((map (copy-keymap view-mode-map)))
1369 (define-key map "q" `(lambda ()
1370 (interactive)
1371 (View-quit)
1372 (set-window-configuration ,config)))
1373 map))
1374 (goto-char (point-min))))
1375
1376 (defun strokes-alphabetic-lessp (stroke1 stroke2)
1377 "Return t if STROKE1's command name precedes STROKE2's in lexicographic order."
1378 (let ((command-name-1 (symbol-name (cdr stroke1)))
1379 (command-name-2 (symbol-name (cdr stroke2))))
1380 (string-lessp command-name-1 command-name-2)))
1381
1382 (defvar strokes-mode-map
1383 (let ((map (make-sparse-keymap)))
1384 (define-key map [(shift down-mouse-2)] 'strokes-do-stroke)
1385 (define-key map [(meta down-mouse-2)] 'strokes-do-complex-stroke)
1386 map))
1387
1388 ;;;###autoload
1389 (define-minor-mode strokes-mode
1390 "Toggle Strokes global minor mode.\\<strokes-mode-map>
1391 With ARG, turn strokes on if and only if ARG is positive.
1392 Strokes are pictographic mouse gestures which invoke commands.
1393 Strokes are invoked with \\[strokes-do-stroke]. You can define
1394 new strokes with \\[strokes-global-set-stroke]. See also
1395 \\[strokes-do-complex-stroke] for `complex' strokes.
1396
1397 To use strokes for pictographic editing, such as Chinese/Japanese, use
1398 \\[strokes-compose-complex-stroke], which draws strokes and inserts them.
1399 Encode/decode your strokes with \\[strokes-encode-buffer],
1400 \\[strokes-decode-buffer].
1401
1402 \\{strokes-mode-map}"
1403 nil strokes-modeline-string strokes-mode-map
1404 :group 'strokes :global t
1405 (cond ((not (display-mouse-p))
1406 (error "Can't use Strokes without a mouse"))
1407 (strokes-mode ; turn on strokes
1408 (and (file-exists-p strokes-file)
1409 (null strokes-global-map)
1410 (strokes-load-user-strokes))
1411 (add-hook 'kill-emacs-query-functions
1412 'strokes-prompt-user-save-strokes)
1413 (add-hook 'select-frame-hook
1414 'strokes-update-window-configuration)
1415 (strokes-update-window-configuration))
1416 (t ; turn off strokes
1417 (if (get-buffer strokes-buffer-name)
1418 (kill-buffer (get-buffer strokes-buffer-name)))
1419 (remove-hook 'select-frame-hook
1420 'strokes-update-window-configuration))))
1421
1422
1423 ;;;; strokes-xpm stuff (later may be separate)...
1424
1425 ;; This is the stuff that will eventually be used for composing letters in
1426 ;; any language, compression, decompression, graphics, editing, etc.
1427
1428 (defface strokes-char '((t (:background "lightgray")))
1429 "Face for strokes characters."
1430 :version "21.1"
1431 :group 'strokes)
1432
1433 (put 'strokes 'char-table-extra-slots 0)
1434 (defconst strokes-char-table (make-char-table 'strokes) ;
1435 "The table which stores values for the character keys.")
1436 (aset strokes-char-table ?0 0)
1437 (aset strokes-char-table ?1 1)
1438 (aset strokes-char-table ?2 2)
1439 (aset strokes-char-table ?3 3)
1440 (aset strokes-char-table ?4 4)
1441 (aset strokes-char-table ?5 5)
1442 (aset strokes-char-table ?6 6)
1443 (aset strokes-char-table ?7 7)
1444 (aset strokes-char-table ?8 8)
1445 (aset strokes-char-table ?9 9)
1446 (aset strokes-char-table ?a 10)
1447 (aset strokes-char-table ?b 11)
1448 (aset strokes-char-table ?c 12)
1449 (aset strokes-char-table ?d 13)
1450 (aset strokes-char-table ?e 14)
1451 (aset strokes-char-table ?f 15)
1452 (aset strokes-char-table ?g 16)
1453 (aset strokes-char-table ?h 17)
1454 (aset strokes-char-table ?i 18)
1455 (aset strokes-char-table ?j 19)
1456 (aset strokes-char-table ?k 20)
1457 (aset strokes-char-table ?l 21)
1458 (aset strokes-char-table ?m 22)
1459 (aset strokes-char-table ?n 23)
1460 (aset strokes-char-table ?o 24)
1461 (aset strokes-char-table ?p 25)
1462 (aset strokes-char-table ?q 26)
1463 (aset strokes-char-table ?r 27)
1464 (aset strokes-char-table ?s 28)
1465 (aset strokes-char-table ?t 29)
1466 (aset strokes-char-table ?u 30)
1467 (aset strokes-char-table ?v 31)
1468 (aset strokes-char-table ?w 32)
1469 (aset strokes-char-table ?x 33)
1470 (aset strokes-char-table ?y 34)
1471 (aset strokes-char-table ?z 35)
1472 (aset strokes-char-table ?A 36)
1473 (aset strokes-char-table ?B 37)
1474 (aset strokes-char-table ?C 38)
1475 (aset strokes-char-table ?D 39)
1476 (aset strokes-char-table ?E 40)
1477 (aset strokes-char-table ?F 41)
1478 (aset strokes-char-table ?G 42)
1479 (aset strokes-char-table ?H 43)
1480 (aset strokes-char-table ?I 44)
1481 (aset strokes-char-table ?J 45)
1482 (aset strokes-char-table ?K 46)
1483 (aset strokes-char-table ?L 47)
1484 (aset strokes-char-table ?M 48)
1485 (aset strokes-char-table ?N 49)
1486 (aset strokes-char-table ?O 50)
1487 (aset strokes-char-table ?P 51)
1488 (aset strokes-char-table ?Q 52)
1489 (aset strokes-char-table ?R 53)
1490 (aset strokes-char-table ?S 54)
1491 (aset strokes-char-table ?T 55)
1492 (aset strokes-char-table ?U 56)
1493 (aset strokes-char-table ?V 57)
1494 (aset strokes-char-table ?W 58)
1495 (aset strokes-char-table ?X 59)
1496 (aset strokes-char-table ?Y 60)
1497 (aset strokes-char-table ?Z 61)
1498
1499 (defconst strokes-base64-chars
1500 ;; I wanted to make this a vector of individual like (vector ?0
1501 ;; ?1 ?2 ...), but `concat' refuses to accept single
1502 ;; characters.
1503 (vector "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"
1504 "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o"
1505 "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" "A" "B" "C" "D"
1506 "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S"
1507 "T" "U" "V" "W" "X" "Y" "Z")
1508 ;; (vector [?0] [?1] [?2] [?3] [?4] [?5] [?6] [?7] [?8] [?9]
1509 ;; [?a] [?b] [?c] [?d] [?e] [?f] [?g] [?h] [?i] [?j]
1510 ;; [?k] [?l] [?m] [?n] [?o] [?p] [?q] [?r] [?s] [?t]
1511 ;; [?u] [?v] [?w] [?x] [?y] [?z]
1512 ;; [?A] [?B] [?C] [?D] [?E] [?F] [?G] [?H] [?I] [?J]
1513 ;; [?K] [?L] [?M] [?N] [?O] [?P] [?Q] [?R] [?S] [?T]
1514 ;; [?U] [?V] [?W] [?X] [?Y] [?Z])
1515 "Character vector for fast lookup of base-64 encoding of numbers in [0,61].")
1516
1517 (defsubst strokes-xpm-char-on-p (char)
1518 "Non-nil if CHAR represents an `on' bit in the XPM."
1519 (eq char ?*))
1520
1521 (defsubst strokes-xpm-char-bit-p (char)
1522 "Non-nil if CHAR represents an `on' or `off' bit in the XPM."
1523 (or (eq char ?\s)
1524 (eq char ?*)))
1525
1526 ;;(defsubst strokes-xor (a b) ### Should I make this an inline function? ###
1527 ;; "T if one and only one of A and B is non-nil; otherwise, returns nil.
1528 ;;NOTE: Don't use this as a numeric xor since it treats all non-nil
1529 ;; values as t including `0' (zero)."
1530 ;; (eq (null a) (not (null b))))
1531
1532 (defsubst strokes-xpm-encode-length-as-string (length)
1533 "Given some LENGTH in [0,62) do a fast lookup of its encoding."
1534 (aref strokes-base64-chars length))
1535
1536 (defsubst strokes-xpm-decode-char (character)
1537 "Given a CHARACTER, do a fast lookup to find its corresponding integer value."
1538 (aref strokes-char-table character))
1539
1540 (defun strokes-xpm-to-compressed-string (&optional xpm-buffer)
1541 "Convert XPM in XPM-BUFFER to compressed string representing the stroke.
1542 XPM-BUFFER defaults to ` *strokes-xpm*'."
1543 (with-current-buffer (setq xpm-buffer (or xpm-buffer " *strokes-xpm*"))
1544 (goto-char (point-min))
1545 (search-forward "/* pixels */") ; skip past header junk
1546 (forward-char 2)
1547 ;; a note for below:
1548 ;; the `current-char' is the char being counted -- NOT the char at (point)
1549 ;; which happens to be called `char-at-point'
1550 (let ((compressed-string "+/") ; initialize the output
1551 (count 0) ; keep a current count of
1552 ; `current-char'
1553 (last-char-was-on-p t) ; last entered stream
1554 ; represented `on' bits
1555 (current-char-is-on-p nil) ; current stream represents `on' bits
1556 (char-at-point (char-after))) ; read the first char
1557 (while (not (eq char-at-point ?})) ; a `}' denotes the
1558 ; end of the pixmap
1559 (cond ((zerop count) ; must restart counting
1560 ;; check to see if the `char-at-point' is an actual pixmap bit
1561 (when (strokes-xpm-char-bit-p char-at-point)
1562 (setq count 1
1563 current-char-is-on-p (strokes-xpm-char-on-p char-at-point)))
1564 (forward-char 1))
1565 ((= count 61) ; maximum single char's
1566 ; encoding length
1567 (setq compressed-string
1568 (concat compressed-string
1569 ;; add a zero-length encoding when
1570 ;; necessary
1571 (when (eq last-char-was-on-p
1572 current-char-is-on-p)
1573 ;; "0"
1574 (strokes-xpm-encode-length-as-string 0))
1575 (strokes-xpm-encode-length-as-string 61))
1576 last-char-was-on-p current-char-is-on-p
1577 count 0)) ; note that we just set
1578 ; count=0 and *don't* advance
1579 ; (point)
1580 ((strokes-xpm-char-bit-p char-at-point) ; an actual xpm bit
1581 (if (eq current-char-is-on-p
1582 (strokes-xpm-char-on-p char-at-point))
1583 ;; yet another of the same bit-type, so we continue
1584 ;; counting...
1585 (progn
1586 (incf count)
1587 (forward-char 1))
1588 ;; otherwise, it's the opposite bit-type, so we do a
1589 ;; write and then restart count ### NOTE (for myself
1590 ;; to be aware of) ### I really should advance
1591 ;; (point) in this case instead of letting another
1592 ;; iteration go through and letting the case: count=0
1593 ;; take care of this stuff for me. That's why
1594 ;; there's no (forward-char 1) below.
1595 (setq compressed-string
1596 (concat compressed-string
1597 ;; add a zero-length encoding when
1598 ;; necessary
1599 (when (eq last-char-was-on-p
1600 current-char-is-on-p)
1601 ;; "0"
1602 (strokes-xpm-encode-length-as-string 0))
1603 (strokes-xpm-encode-length-as-string count))
1604 count 0
1605 last-char-was-on-p current-char-is-on-p)))
1606 (t ; ELSE it's some other useless
1607 ; char, like `"' or `,'
1608 (forward-char 1)))
1609 (setq char-at-point (char-after)))
1610 (concat compressed-string
1611 (when (> count 0)
1612 (concat (when (eq last-char-was-on-p
1613 current-char-is-on-p)
1614 ;; "0"
1615 (strokes-xpm-encode-length-as-string 0))
1616 (strokes-xpm-encode-length-as-string count)))
1617 "/"))))
1618
1619 ;;;###autoload
1620 (defun strokes-decode-buffer (&optional buffer force)
1621 "Decode stroke strings in BUFFER and display their corresponding glyphs.
1622 Optional BUFFER defaults to the current buffer.
1623 Optional FORCE non-nil will ignore the buffer's read-only status."
1624 (interactive)
1625 ;; (interactive "*bStrokify buffer: ")
1626 (with-current-buffer (setq buffer (get-buffer (or buffer (current-buffer))))
1627 (when (or (not buffer-read-only)
1628 force
1629 inhibit-read-only
1630 (y-or-n-p
1631 (format "Buffer %s is read-only. Strokify anyway? " buffer)))
1632 (let ((inhibit-read-only t))
1633 (message "Strokifying %s..." buffer)
1634 (goto-char (point-min))
1635 (let (ext string image)
1636 ;; The comment below is what I'd have to do if I wanted to
1637 ;; deal with random newlines in the midst of the compressed
1638 ;; strings. If I do this, I'll also have to change
1639 ;; `strokes-xpm-to-compress-string' to deal with the newline,
1640 ;; and possibly other whitespace stuff. YUCK!
1641 ;; (while (re-search-forward "\\+/\\(\\w\\|\\)+/" nil t nil (get-buffer buffer))
1642 (while (with-current-buffer buffer
1643 (when (re-search-forward "\\+/\\(\\w+\\)/" nil t nil)
1644 (setq string (match-string 1))
1645 (goto-char (match-end 0))
1646 (replace-match " ")
1647 t))
1648 (strokes-xpm-for-compressed-string string " *strokes-xpm*")
1649 (setq image (create-image (with-current-buffer " *strokes-xpm*"
1650 (buffer-string))
1651 'xpm t))
1652 (insert-image image
1653 (propertize " "
1654 'type 'stroke-glyph
1655 'stroke-glyph image
1656 'data string))))
1657 (message "Strokifying %s...done" buffer)))))
1658
1659 (defun strokes-encode-buffer (&optional buffer force)
1660 "Convert the glyphs in BUFFER to their base-64 ASCII representations.
1661 Optional BUFFER defaults to the current buffer.
1662 Optional FORCE non-nil will ignore the buffer's read-only status."
1663 ;; ### NOTE !!! ### (for me)
1664 ;; For later on, you can/should make the inserted strings atomic
1665 ;; extents, so that the users have a clue that they shouldn't be
1666 ;; editing inside them. Plus, if you make them extents, you can
1667 ;; very easily just hide the glyphs, so if you unstrokify, and the
1668 ;; restrokify, then those that already are glyphed don't need to be
1669 ;; re-calculated, etc. It's just nicer that way. The only things
1670 ;; to worry about is cleanup (i.e. do the glyphs get gc'd when the
1671 ;; buffer is killed?
1672 ;; (interactive "*bUnstrokify buffer: ")
1673 (interactive)
1674 (with-current-buffer (setq buffer (or buffer (current-buffer)))
1675 (when (or (not buffer-read-only)
1676 force
1677 inhibit-read-only
1678 (y-or-n-p
1679 (format "Buffer %s is read-only. Encode anyway? " buffer)))
1680 (message "Encoding strokes in %s..." buffer)
1681 ;; (map-extents
1682 ;; (lambda (ext buf)
1683 ;; (when (eq (extent-property ext 'type) 'stroke-glyph)
1684 ;; (goto-char (extent-start-position ext))
1685 ;; (delete-char 1) ; ### What the hell do I do here? ###
1686 ;; (insert "+/" (extent-property ext 'data) "/")
1687 ;; (delete-extent ext))))))
1688 (let ((inhibit-read-only t)
1689 (start nil)
1690 glyph)
1691 (while (or (and (bobp)
1692 (get-text-property (point) 'type))
1693 (setq start (next-single-property-change (point) 'type)))
1694 (when (eq 'stroke-glyph (get-text-property (point) 'type))
1695 (goto-char start)
1696 (setq start (point-marker)
1697 glyph (get-text-property start 'display))
1698 (insert "+/" (get-text-property (point) 'data) ?/)
1699 (delete-char 1)
1700 (add-text-properties start (point)
1701 (list 'type 'stroke-string
1702 'face 'strokes-char
1703 'stroke-glyph glyph
1704 'display nil))))
1705 (message "Encoding strokes in %s...done" buffer)))))
1706
1707 (defun strokes-xpm-for-compressed-string (compressed-string &optional bufname)
1708 "Convert the stroke represented by COMPRESSED-STRING into an XPM.
1709 Store XPM in buffer BUFNAME if supplied \(default is ` *strokes-xpm*'\)"
1710 (or bufname (setq bufname " *strokes-xpm*"))
1711 (with-current-buffer (get-buffer-create bufname)
1712 (erase-buffer)
1713 (insert compressed-string)
1714 (goto-char (point-min))
1715 (let ((current-char-is-on-p nil))
1716 (while (not (eobp))
1717 (insert-char
1718 (if current-char-is-on-p
1719 ?*
1720 ?\s)
1721 (strokes-xpm-decode-char (char-after)))
1722 (delete-char 1)
1723 (setq current-char-is-on-p (not current-char-is-on-p)))
1724 (goto-char (point-min))
1725 (loop repeat 33 do
1726 (insert ?\")
1727 (forward-char 33)
1728 (insert "\",\n"))
1729 (goto-char (point-min))
1730 (insert strokes-xpm-header))))
1731
1732 ;;;###autoload
1733 (defun strokes-compose-complex-stroke ()
1734 ;; ### NOTE !!! ###
1735 ;; Even though we don't have lexical scoping, it's somewhat ugly how I
1736 ;; pass around variables in the global name space. I can/should
1737 ;; change this.
1738 "Read a complex stroke and insert its glyph into the current buffer."
1739 (interactive "*")
1740 (let ((strokes-grid-resolution 33))
1741 (strokes-read-complex-stroke)
1742 (strokes-xpm-for-stroke nil " *strokes-xpm*" t)
1743 (insert (strokes-xpm-to-compressed-string " *strokes-xpm*"))
1744 (strokes-decode-buffer)
1745 ;; strokes-decode-buffer does a save-excursion.
1746 (forward-char)))
1747
1748 (defun strokes-unload-function ()
1749 "Unload the Strokes library."
1750 (strokes-mode -1)
1751 ;; continue standard unloading
1752 nil)
1753
1754 (run-hooks 'strokes-load-hook)
1755 (provide 'strokes)
1756
1757 ;;; strokes.el ends here