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