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