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