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