1 ;;; strokes.el --- control Emacs through mouse strokes
3 ;; Copyright (C) 1997, 2000-2013 Free Software Foundation, Inc.
5 ;; Author: David Bakhash <cadet@alum.mit.edu>
7 ;; Keywords: lisp, mouse, extensions
9 ;; This file is part of GNU Emacs.
11 ;; GNU Emacs is free software: you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation, either version 3 of the License, or
14 ;; (at your option) any later version.
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.
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
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':
32 ;; and you can learn how to use the package. A mouse stroke, for now,
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
42 ;; > M-x strokes-global-set-stroke
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
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'.
50 ;; You can always unset the last stroke definition with the command
52 ;; > M-x strokes-unset-last-stroke
54 ;; and the last stroke that was added to `strokes-global-map' will be
57 ;; Other analogies between strokes and key bindings are as follows:
59 ;; 1) To describe a stroke binding, you can type
61 ;; > M-x strokes-describe-stroke
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'.
66 ;; 2) stroke bindings are set internally through the Lisp function
67 ;; `strokes-define-stroke', similar to the `define-key' function.
68 ;; some examples for a 3x3 stroke grid would be
70 ;; (strokes-define-stroke c-mode-stroke-map
71 ;; '((0 . 0) (1 . 1) (2 . 2))
73 ;; (strokes-define-stroke strokes-global-map
74 ;; '((0 . 0) (0 . 1) (0 . 2) (1 . 2) (2 . 2))
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
79 ;; entered. The Lisp function to interactively read a stroke is
80 ;; `strokes-read-stroke'. This is especially helpful when you're
81 ;; on a fast computer that can handle a 9x9 stroke grid.
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.
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
92 ;; and customizing the group named `strokes'. You can also read
93 ;; documentation on the variables there.
95 ;; `strokes-minimum-match-score' (determines the threshold of error that
96 ;; makes a stroke acceptable or unacceptable. If your strokes aren't
97 ;; matching, then you should raise this variable.
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.)
102 ;; The default value (9) should be fine for most decent computers.
103 ;; NOTE: This variable should not be set to a number less than 3.
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.
109 ;; If you find that your mouse is accelerating too fast, you can
110 ;; execute an X command to slow it down. A good possibility is
114 ;; which seems, heuristically, to work okay, without much disruption.
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
119 ;; > M-x strokes-prompt-user-save-strokes
121 ;; and it will save your strokes in ~/.strokes, or you may wish to change
122 ;; this by setting the variable `strokes-file'.
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
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.
131 ;; You can also toggle between strokes mode by simple typing
133 ;; > M-x strokes-mode
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.
142 ;; NOTE (Oct 7, 2006): The URLs below seem to be invalid!!!
144 ;; You can read more about strokes at:
146 ;; http://www.mit.edu/people/cadet/strokes-help.html
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.
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.
163 ;; Tasks: (what I'm getting ready for future version)...
164 ;; 2) use 'strokes-read-complex-stroke for Korean, etc.
165 ;; 4) buffer-local 'strokes-local-map, and mode-stroke-maps would be nice
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
171 ;; surely disappoint some people on decent machines (until they
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.
176 ;; Fixme: Use pbm instead of xpm for pixmaps to work generally.
180 ;;; Requirements and provisions...
182 (autoload 'mail-position-on-field
"sendmail")
183 (eval-when-compile (require 'cl-lib
))
187 (defconst strokes-lift
:strokes-lift
188 "Symbol representing a stroke lift event for complex strokes.
189 Complex strokes are those which contain two or more simple strokes.")
191 (defconst strokes-xpm-header
"/* XPM */
192 static char * stroke_xpm[] = {
193 /* width height ncolors cpp [x_hot y_hot] */
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\",
206 "The header to all xpm buffers created by strokes.")
208 ;;; user variables...
210 (defgroup strokes nil
211 "Control Emacs through mouse strokes."
212 :link
'(emacs-commentary-link "strokes")
215 (define-obsolete-variable-alias 'strokes-modeline-string
'strokes-lighter
218 (defcustom strokes-lighter
" Strokes"
219 "Mode line identifier for Strokes mode."
223 (defcustom strokes-character ?
@
224 "Character used when drawing strokes in the strokes buffer.
225 \(The default is `@', which works well.\)"
229 (defcustom strokes-minimum-match-score
1000
230 "Minimum score for a stroke to be considered a possible match.
231 Setting this variable to 0 would require a perfectly precise match.
232 The default value is 1000, but it's mostly dependent on how precisely
233 you manage to replicate your user-defined strokes. It also depends on
234 the value of `strokes-grid-resolution', since a higher grid resolution
235 will correspond to more sample points, and thus more distance
236 measurements. Usually, this is not a problem since you first set
237 `strokes-grid-resolution' based on what your computer seems to be able
238 to handle (though the defaults are usually more than sufficient), and
239 then you can set `strokes-minimum-match-score' to something that works
240 for you. The only purpose of this variable is to insure that if you
241 do a bogus stroke that really doesn't match any of the predefined
242 ones, then strokes should NOT pick the one that came closest."
246 (defcustom strokes-grid-resolution
9
247 "Integer defining dimensions of the stroke grid.
248 The grid is a square grid, where `strokes-grid-resolution' defaults to
249 `9', making a 9x9 grid whose coordinates go from (0 . 0) on the top
250 left to ((strokes-grid-resolution - 1) . (strokes-grid-resolution - 1))
251 on the bottom right. The greater the resolution, the more intricate
253 NOTE: This variable should be odd and MUST NOT be less than 3 and need
254 not be greater than 33, which is the resolution of the pixmaps.
255 WARNING: Changing the value of this variable will gravely affect the
256 strokes you have already programmed in. You should try to
257 figure out what it should be based on your needs and on how
258 quick the particular platform(s) you're operating on, and
259 only then start programming in your custom strokes."
263 (defcustom strokes-file
(convert-standard-filename "~/.strokes")
264 "File containing saved strokes for Strokes mode (default is ~/.strokes)."
268 (defvar strokes-buffer-name
" *strokes*"
269 "The name of the buffer that the strokes take place in.")
271 (defcustom strokes-use-strokes-buffer t
272 "If non-nil, the strokes buffer is used and strokes are displayed.
273 If nil, strokes will be read the same, however the user will not be
274 able to see the strokes. This be helpful for people who don't like
275 the delay in switching to the strokes buffer."
279 ;;; internal variables...
281 (defvar strokes-window-configuration nil
282 "The special window configuration used when entering strokes.
283 This is set properly in the function `strokes-update-window-configuration'.")
285 (defvar strokes-last-stroke nil
286 "Last stroke entered by the user.
287 Its value gets set every time the function
288 `strokes-fill-stroke' gets called,
289 since that is the best time to set the variable.")
291 (defvar strokes-global-map
'()
292 "Association list of strokes and their definitions.
293 Each entry is (STROKE . COMMAND) where STROKE is itself a list of
294 coordinates (X . Y) where X and Y are lists of positions on the
295 normalized stroke grid, with the top left at (0 . 0). COMMAND is the
296 corresponding interactive function.")
298 (defvar strokes-load-hook nil
299 "Functions to be called when Strokes is loaded.")
301 ;;; ### NOT IMPLEMENTED YET ###
302 ;;(defvar edit-strokes-menu
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]
316 ;; (defmacro strokes-while-inhibiting-garbage-collector (&rest forms)
317 ;; "Execute FORMS without interference from the garbage collector."
318 ;; `(let ((gc-cons-threshold 134217727))
321 (defsubst strokes-click-p
(stroke)
322 "Non-nil if STROKE is really click."
323 (< (length stroke
) 2))
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"
328 ;; (list 'if (list '< (list 'length stroke) 2)
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)))))
334 (defsubst strokes-remassoc
(key list
)
336 (while (setq elt
(assoc key list
))
337 (setq list
(delete elt list
))))
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
)
343 (error "That's a click, not a stroke")
344 (setq ,stroke-map
(cons (cons ,stroke
,def
)
345 (strokes-remassoc ,stroke
,stroke-map
)))))
347 (defsubst strokes-square
(x)
348 "Return the square of the number X."
351 (defsubst strokes-distance-squared
(p1 p2
)
352 "Gets the distance (squared) between to points P1 and P2.
353 P1 and P2 are cons cells in the form (X . Y)."
358 (+ (strokes-square (- x2 x1
))
359 (strokes-square (- y2 y1
)))))
363 (defsubst strokes-mouse-event-p
(event)
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
)))))
370 (defsubst strokes-button-press-event-p
(event)
371 (and (consp event
) (symbolp (car event
))
372 (memq 'down
(get (car event
) 'event-symbol-elements
))))
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
)))))
379 (defun strokes-event-closest-point-1 (window &optional line
)
380 "Return position of start of line LINE in WINDOW.
381 If LINE is nil, return the last position visible in WINDOW."
382 (let* ((total (- (window-height window
)
383 (if (window-minibuffer-p window
)
385 (distance (or line total
)))
387 (goto-char (window-start window
))
388 (if (= (vertical-motion distance
) distance
)
393 (defun strokes-event-closest-point (event &optional start-window
)
394 "Return the nearest position to where EVENT ended its motion.
395 This is computed for the window where EVENT's motion started,
396 or for window START-WINDOW if that is specified."
397 (or start-window
(setq start-window
(posn-window (event-start event
))))
398 (if (eq start-window
(posn-window (event-end event
)))
399 (if (eq (posn-point (event-end event
)) 'vertical-line
)
400 (strokes-event-closest-point-1 start-window
401 (cdr (posn-col-row (event-end event
))))
402 (if (eq (posn-point (event-end event
)) 'mode-line
)
403 (strokes-event-closest-point-1 start-window
)
404 (posn-point (event-end event
))))
405 ;; EVENT ended in some other window.
406 (let* ((end-w (posn-window (event-end event
)))
408 (w-top (nth 1 (window-edges start-window
))))
411 (nth 1 (window-edges end-w
))
412 (/ (cdr (posn-x-y (event-end event
)))
413 (frame-char-height end-w
))))
414 (if (>= end-w-top w-top
)
415 (strokes-event-closest-point-1 start-window
)
416 (window-start start-window
)))))
418 (defun strokes-lift-p (object)
419 "Return non-nil if OBJECT is a stroke-lift."
420 (eq object strokes-lift
))
422 (defun strokes-unset-last-stroke ()
423 "Undo the last stroke definition."
425 (let ((command (cdar strokes-global-map
)))
427 (format "Really delete last stroke definition, defined to `%s'? "
430 (setq strokes-global-map
(cdr strokes-global-map
))
431 (message "That stroke has been deleted"))
432 (message "Nothing done"))))
435 (defun strokes-global-set-stroke (stroke command
)
436 "Interactively give STROKE the global binding as COMMAND.
437 Operated just like `global-set-key', except for strokes.
438 COMMAND is a symbol naming an interactively-callable function. STROKE
439 is a list of sampled positions on the stroke grid as described in the
440 documentation for the `strokes-define-stroke' function.
442 See also `strokes-global-set-stroke-string'."
445 (and (or strokes-mode
(strokes-mode t
))
446 (strokes-read-complex-stroke
447 "Draw with mouse button 1 (or 2). End with button 3..."))
448 (read-command "Command to map stroke to: ")))
449 (strokes-define-stroke strokes-global-map stroke command
))
451 (defun strokes-global-set-stroke-string (stroke string
)
452 "Interactively give STROKE the global binding as STRING.
453 Operated just like `global-set-key', except for strokes. STRING
454 is a string to be inserted by the stroke. STROKE is a list of
455 sampled positions on the stroke grid as described in the
456 documentation for the `strokes-define-stroke' function.
458 Compare `strokes-global-set-stroke'."
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
))
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"
473 ;; (strokes-read-stroke "Enter the stroke you want to delete...")))
474 ;; (strokes-define-stroke 'strokes-global-map stroke command))
476 (defun strokes-get-grid-position (stroke-extent position
&optional grid-resolution
)
477 "Map POSITION to a new grid position.
478 Do so based on its STROKE-EXTENT and GRID-RESOLUTION.
479 STROKE-EXTENT as a list \(\(XMIN . YMIN\) \(XMAX . YMAX\)\).
480 If POSITION is a `strokes-lift', then it is itself returned.
481 Optional GRID-RESOLUTION may be used in place of `strokes-grid-resolution'.
482 The grid is a square whose dimension is [0,GRID-RESOLUTION)."
483 (cond ((consp position
) ; actual pixel location
484 (let ((grid-resolution (or grid-resolution strokes-grid-resolution
))
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
491 (xmax (1+ (car (cadr stroke-extent
))))
492 (ymax (1+ (cdr (cadr stroke-extent
)))))
493 (cons (floor (* grid-resolution
494 (/ (float (- x xmin
))
496 (floor (* grid-resolution
497 (/ (float (- y ymin
))
499 ((strokes-lift-p position
) ; stroke lift
502 (defun strokes-get-stroke-extent (pixel-positions)
503 "From a list of absolute PIXEL-POSITIONS, return absolute spatial extent.
504 The return value is a list ((XMIN . YMIN) (XMAX . YMAX))."
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
)))
512 (if (consp (car rest
))
513 (let ((x (caar rest
))
523 (setq rest
(cdr rest
)))
524 (let ((delta-x (- xmax xmin
))
525 (delta-y (- ymax ymin
)))
526 (if (> delta-x delta-y
)
528 (/ (- delta-x delta-y
)
531 (/ (- delta-x delta-y
)
534 (/ (- delta-y delta-x
)
537 (/ (- delta-y delta-x
)
539 (list (cons xmin ymin
)
543 (defun strokes-eliminate-consecutive-redundancies (entries)
544 "Return a list with no consecutive redundant entries."
545 ;; defun a grande vitesse grace a Dave G.
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
550 ;; nconc (if (not (equal (car el) (cadr el)))
551 ;; (list (car el)))))
552 ;; yet another (orig) way of doing it...
554 ;; (let* ((current (car entries))
555 ;; (rest (cdr entries))
556 ;; (non-redundant-list (list current))
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)
564 ;; rest (cdr rest))))
565 ;; (nreverse non-redundant-list))
568 (defun strokes-renormalize-to-grid (positions &optional grid-resolution
)
569 "Map POSITIONS to a new grid whose dimensions are based on GRID-RESOLUTION.
570 POSITIONS is a list of positions and stroke-lifts.
571 Optional GRID-RESOLUTION may be used in place of `strokes-grid-resolution'.
572 The grid is a square whose dimension is [0,GRID-RESOLUTION)."
573 (or grid-resolution
(setq grid-resolution strokes-grid-resolution
))
574 (let ((stroke-extent (strokes-get-stroke-extent positions
)))
577 (strokes-get-grid-position stroke-extent pos grid-resolution
)))
580 (defun strokes-fill-stroke (unfilled-stroke &optional force
)
581 "Fill in missing grid locations in the list of UNFILLED-STROKE.
582 If FORCE is non-nil, then fill the stroke even if it's `stroke-click'.
583 NOTE: 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
)
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
596 (x1 (and current-is-a-point-p
598 (y1 (and current-is-a-point-p
600 (x2 (and next-is-a-point-p
602 (y2 (and next-is-a-point-p
604 (delta-x (and both-are-points-p
606 (delta-y (and both-are-points-p
608 (slope (and both-are-points-p
610 nil
; undefined vertical slope
613 (cond ((not both-are-points-p
)
615 ((null slope
) ; undefined vertical slope
617 (cl-loop for y from y1 below y2
619 (cl-loop for y from y1 above y2
620 collect
(cons x1 y
))))
621 ((zerop slope
) ; (= y1 y2)
623 (cl-loop for x from x1 below x2
625 (cl-loop for x from x1 above x2
626 collect
(cons x y1
))))
627 ((>= (abs delta-x
) (abs delta-y
))
629 (cl-loop for x from x1 below x2
634 (cl-loop for x from x1 above x2
639 (t ; (< (abs delta-x) (abs delta-y))
641 ;; FIXME: Reduce redundancy between branches.
642 (cl-loop for y from y1 below y2
647 (cl-loop for y from y1 above y2
653 (defun strokes-rate-stroke (stroke1 stroke2
)
654 "Rates STROKE1 with STROKE2 and return a score based on a distance metric.
655 Note: the rating is an error rating, and therefore, a return of 0
656 represents a perfect match. Also note that the order of stroke
657 arguments 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
)
663 (while (and rest1 rest2
)
664 (while (and (consp (car rest1
))
667 (strokes-distance-squared (car rest1
)
672 rest2
(cdr stroke2
)))
673 (cond ((and (strokes-lift-p (car rest1
))
674 (strokes-lift-p (car rest2
)))
675 (setq rest1
(cdr rest1
)
677 ((strokes-lift-p (car rest2
))
678 (while (consp (car rest1
))
680 (strokes-distance-squared (car rest1
)
683 ((strokes-lift-p (car rest1
))
684 (while (consp (car rest2
))
686 (strokes-distance-squared (car stroke1
)
688 rest2
(cdr rest2
))))))
690 (while (consp (car rest1
))
692 (strokes-distance-squared (car rest1
)
696 (while (consp (car rest2
))
698 (strokes-distance-squared (car stroke1
)
701 (if (or (strokes-lift-p (car rest1
))
702 (strokes-lift-p (car rest2
)))
707 (defun strokes-match-stroke (stroke stroke-map
)
708 "Find the best matching command of STROKE in STROKE-MAP.
709 Returns 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
)))
715 (let ((newscore (strokes-rate-stroke stroke
(caar map
))))
716 (if (or (and newscore score
(< newscore score
))
717 (and newscore
(null score
)))
720 (setq map
(cdr map
))))
726 (defsubst strokes-fill-current-buffer-with-whitespace
()
727 "Erase the contents of the current buffer and fill it with whitespace."
729 (cl-loop repeat
(frame-height) do
730 (insert-char ?\s
(1- (frame-width)))
732 (goto-char (point-min)))
735 (defun strokes-read-stroke (&optional prompt event
)
736 "Read a simple stroke (interactively) and return the stroke.
737 Optional PROMPT in minibuffer displays before and during stroke reading.
738 This function will display the stroke interactively as it is being
739 entered in the strokes buffer if the variable
740 `strokes-use-strokes-buffer' is non-nil.
741 Optional EVENT is acceptable as the starting event of the stroke."
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
)
751 ;; The frame has been resized, so we need to refill the
752 ;; strokes buffer so that the strokes canvas is the whole
754 (unless (> 1 (abs (- (line-end-position) (window-width))))
755 (strokes-fill-current-buffer-with-whitespace))
757 (message "%s" prompt
)
758 (setq event
(read-event))
759 (or (strokes-button-press-event-p event
)
760 (error "You must draw with the mouse")))
763 (or event
(setq event
(read-event)
765 (while (not (strokes-button-release-event-p event
))
766 (if (strokes-mouse-event-p event
)
767 (let ((point (strokes-event-closest-point event
)))
768 (if (and point safe-to-draw-p
)
769 ;; we can draw that point
772 (subst-char-in-region point
(1+ point
)
773 ?\s strokes-character
))
774 ;; otherwise, we can start drawing the next time...
775 (setq safe-to-draw-p t
))
776 (push (cdr (mouse-pixel-position))
778 (setq event
(read-event)))))
780 ;; clean up strokes buffer and then bury it.
781 (when (equal (buffer-name) strokes-buffer-name
)
782 (subst-char-in-region (point-min) (point-max)
783 strokes-character ?\s
)
784 (goto-char (point-min))
786 ;; Otherwise, don't use strokes buffer and read stroke silently
788 (message "%s" prompt
)
789 (setq event
(read-event))
790 (or (strokes-button-press-event-p event
)
791 (error "You must draw with the mouse")))
793 (or event
(setq event
(read-event)))
794 (while (not (strokes-button-release-event-p event
))
795 (if (strokes-mouse-event-p event
)
796 (push (cdr (mouse-pixel-position))
798 (setq event
(read-event))))
799 (setq grid-locs
(strokes-renormalize-to-grid (nreverse pix-locs
)))
801 (strokes-eliminate-consecutive-redundancies grid-locs
)))))
804 (defun strokes-read-complex-stroke (&optional prompt event
)
805 "Read a complex stroke (interactively) and return the stroke.
806 Optional PROMPT in minibuffer displays before and during stroke reading.
807 Note that a complex stroke allows the user to pen-up and pen-down. This
808 is implemented by allowing the user to paint with button 1 or button 2 and
809 then complete the stroke with button 3.
810 Optional EVENT is acceptable as the starting event of the stroke."
812 (save-window-excursion
813 (set-window-configuration strokes-window-configuration
)
817 (while (not (strokes-button-press-event-p event
))
818 (message "%s" prompt
)
819 (setq event
(read-event))))
822 (or event
(setq event
(read-event)))
823 (while (not (and (strokes-button-press-event-p event
)
825 (car (get (car event
)
826 'event-symbol-elements
)))))
827 (while (not (strokes-button-release-event-p event
))
828 (if (strokes-mouse-event-p event
)
829 (let ((point (strokes-event-closest-point event
)))
832 (subst-char-in-region point
(1+ point
)
833 ?\s strokes-character
))
834 (push (cdr (mouse-pixel-position))
836 (setq event
(read-event)))
837 (push strokes-lift pix-locs
)
838 (while (not (strokes-button-press-event-p event
))
839 (setq event
(read-event))))
840 ;; ### KLUDGE! ### sit and wait
841 ;; for some useless event to
842 ;; happen to fix the minibuffer bug.
843 (while (not (strokes-button-release-event-p (read-event))))
844 (setq pix-locs
(nreverse (cdr pix-locs
))
845 grid-locs
(strokes-renormalize-to-grid pix-locs
))
847 (strokes-eliminate-consecutive-redundancies grid-locs
)))
849 (when (equal (buffer-name) strokes-buffer-name
)
850 (subst-char-in-region (point-min) (point-max)
851 strokes-character ?\s
)
852 (goto-char (point-min))
855 (defun strokes-execute-stroke (stroke)
856 "Given STROKE, execute the command which corresponds to it.
857 The command will be executed provided one exists for that stroke,
858 based on the variable `strokes-minimum-match-score'.
859 If 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
))
863 (cond ((and match
(<= score strokes-minimum-match-score
))
864 (message "%s" command
)
865 (command-execute command
))
866 ((null strokes-global-map
)
867 (if (file-exists-p strokes-file
)
869 (format "No strokes loaded. Load `%s'? "
871 (strokes-load-user-strokes))
872 (error "No strokes defined; use `strokes-global-set-stroke'")))
875 "No stroke matches; see variable `strokes-minimum-match-score'")
879 (defun strokes-do-stroke (event)
880 "Read a simple stroke from the user and then execute its command.
881 This must be bound to a mouse event."
883 (or strokes-mode
(strokes-mode t
))
884 (strokes-execute-stroke (strokes-read-stroke nil event
)))
887 (defun strokes-do-complex-stroke (event)
888 "Read a complex stroke from the user and then execute its command.
889 This must be bound to a mouse event."
891 (or strokes-mode
(strokes-mode t
))
892 (strokes-execute-stroke (strokes-read-complex-stroke nil event
)))
895 (defun strokes-describe-stroke (stroke)
896 "Displays the command which STROKE maps to, reading STROKE interactively."
899 (strokes-read-complex-stroke
900 "Enter stroke to describe; end with button 3...")))
901 (let* ((match (strokes-match-stroke stroke strokes-global-map
))
902 (command (car match
))
905 (<= score strokes-minimum-match-score
))
906 (message "That stroke maps to `%s'" command
)
907 (message "That stroke is undefined"))
908 (sleep-for 1))) ; helpful for recursive edits
911 (defun strokes-help ()
912 "Get instruction on using the Strokes package."
914 (with-output-to-temp-buffer "*Help with Strokes*"
916 (substitute-command-keys
917 "This is help for the strokes package.
919 ------------------------------------------------------------
923 The strokes package allows you to define strokes, made with
924 the mouse or other pointer device, that Emacs can interpret as
925 corresponding to commands, and then executes the commands. It does
926 character recognition, so you don't have to worry about getting it
929 Strokes also allows you to compose documents graphically. You can
930 fully edit documents in Chinese, Japanese, etc. based on Emacs
931 strokes. Once you've done so, you can ASCII compress-and-encode them
932 and then safely save them for later use, send letters to friends
933 \(using Emacs, of course). Strokes will later decode these documents,
934 extracting the strokes for editing use once again, so the editing
937 To toggle strokes-mode, invoke the command
941 ** Strokes for controlling the behavior of Emacs...
943 When you're ready to start defining strokes, just use the command
945 > M-x strokes-global-set-stroke
947 You will see a ` *strokes*' buffer which is waiting for you to enter in
948 your stroke. When you enter in the stroke, you draw with button 1 or
949 button 2, and then end with button 3. Next, you enter in the command
950 which will be executed when that stroke is invoked. Simple as that.
951 For now, try to define a stroke to copy a region. This is a popular
952 edit command, so type
954 > M-x strokes-global-set-stroke
956 Then, in the ` *strokes*' buffer, draw the letter `C' (for `copy')
957 and then, when it asks you to enter the command to map that to, type
959 > copy-region-as-kill
961 That's about as hard as it gets.
962 Remember: paint with button 1 or button 2 and then end with button 3.
964 If ever you want to know what a certain strokes maps to, then do
966 > M-x strokes-describe-stroke
968 and you can enter in any arbitrary stroke. Remember: The strokes
969 package lets you program in simple and complex (multi-lift) strokes.
970 The only difference is how you *invoke* the two. You will most likely
971 use simple strokes, as complex strokes were developed for
972 Chinese/Japanese/Korean. So the shifted middle mouse button (S-mouse-2) will
973 invoke the command `strokes-do-stroke'.
975 If ever you define a stroke which you don't like, then you can unset
978 > M-x strokes-unset-last-stroke
980 You can always get an idea of what your current strokes look like with
983 > M-x strokes-list-strokes
985 Your strokes will be displayed in alphabetical order (based on command
986 names) and the beginning of each simple stroke will be marked by a
987 color dot. Since you may have several simple strokes in a complex
988 stroke, the dot colors are arranged in the rainbow color sequence,
989 `ROYGBIV'. If you want a listing of your strokes from most recent
990 down, then use a prefix argument:
992 > C-u M-x strokes-list-strokes
994 Your strokes are stored as you enter them. They get saved in a file
995 called ~/.strokes, along with other strokes configuration variables.
996 You can change this location by setting the variable `strokes-file'.
997 You will be prompted to save them when you exit Emacs, or you can save
1000 > M-x strokes-prompt-user-save-strokes
1002 Your strokes get loaded automatically when you enable `strokes-mode'.
1003 You can also load in your user-defined strokes with
1005 > M-x strokes-load-user-strokes
1007 ** Strokes for pictographic editing...
1009 If you'd like to create graphical files with strokes, you'll have to
1010 be running a version of Emacs with XPM support. You use the binding
1011 to `strokes-compose-complex-stroke' to start drawing your strokes.
1012 These are just complex strokes, and thus continue drawing with mouse-1
1013 or mouse-2 and end with mouse-3. Then the stroke image gets inserted
1014 into the buffer. You treat it somewhat like any other character,
1015 which you can copy, paste, delete, move, etc. When all is done, you
1016 may want to send the file, or save it. This is done with
1018 > M-x strokes-encode-buffer
1020 Likewise, to decode the strokes from a strokes-encoded buffer you do
1022 > M-x strokes-decode-buffer
1024 ** A few more important things...
1026 o The command `strokes-do-complex-stroke' is invoked with M-mouse-2,
1027 so that you can execute complex strokes (i.e. with more than one lift)
1030 o Strokes are a bit computer-dependent in that they depend somewhat on
1031 the speed of the computer you're working on. This means that you
1032 may have to tweak some variables. You can read about them in the
1033 commentary of `strokes.el'. Better to just use \\[apropos] and read their
1034 docstrings. All variables/functions start with `strokes'. The one
1035 variable which many people wanted to see was
1036 `strokes-use-strokes-buffer' which allows the user to use strokes
1037 silently--without displaying the strokes. All variables can be set
1038 by customizing the group `strokes' via \\[customize-group]."))
1039 (set-buffer standard-output
)
1041 (help-print-return-message)))
1043 (define-obsolete-function-alias 'strokes-report-bug
'report-emacs-bug
"24.1")
1045 (defun strokes-window-configuration-changed-p ()
1046 "Non-nil if the `strokes-window-configuration' frame properties changed.
1047 This is based on the last time `strokes-window-configuration' was updated."
1048 (compare-window-configurations (current-window-configuration)
1049 strokes-window-configuration
))
1051 (defun strokes-update-window-configuration ()
1052 "Ensure that `strokes-window-configuration' is up-to-date."
1054 (let ((current-window (selected-window)))
1055 (cond ((or (window-minibuffer-p current-window
)
1056 (window-dedicated-p current-window
))
1057 ;; don't try to update strokes window configuration
1058 ;; if window is dedicated or a minibuffer
1060 ((or (called-interactively-p 'interactive
)
1061 (not (buffer-live-p (get-buffer strokes-buffer-name
)))
1062 (null strokes-window-configuration
))
1063 ;; create `strokes-window-configuration' from scratch...
1065 (save-window-excursion
1066 (set-buffer (get-buffer-create strokes-buffer-name
))
1067 (set-window-buffer current-window strokes-buffer-name
)
1068 (delete-other-windows)
1073 (buffer-disable-undo (current-buffer))
1074 (setq truncate-lines nil
)
1075 (strokes-fill-current-buffer-with-whitespace)
1076 (setq strokes-window-configuration
(current-window-configuration))
1078 ((strokes-window-configuration-changed-p) ; simple update
1079 ;; update the strokes-window-configuration for this
1080 ;; specific frame...
1082 (save-window-excursion
1083 (set-window-buffer current-window strokes-buffer-name
)
1084 (delete-other-windows)
1085 (strokes-fill-current-buffer-with-whitespace)
1086 (setq strokes-window-configuration
(current-window-configuration))
1090 (defun strokes-load-user-strokes ()
1091 "Load user-defined strokes from file named by `strokes-file'."
1093 (cond ((and (file-exists-p strokes-file
)
1094 (file-readable-p strokes-file
))
1095 (load-file strokes-file
))
1096 ((called-interactively-p 'interactive
)
1097 (error "Trouble loading user-defined strokes; nothing done"))
1099 (message "No user-defined strokes, sorry"))))
1101 (defun strokes-prompt-user-save-strokes ()
1102 "Save user-defined strokes to file named by `strokes-file'."
1105 (let ((current strokes-global-map
))
1108 (setq strokes-global-map nil
)
1109 (strokes-load-user-strokes)
1110 (if (and (not (equal current strokes-global-map
))
1111 (or (called-interactively-p 'interactive
)
1112 (yes-or-no-p "Save your strokes? ")))
1114 (require 'pp
) ; pretty-print variables
1115 (message "Saving strokes in %s..." strokes-file
)
1116 (get-buffer-create "*saved-strokes*")
1117 (set-buffer "*saved-strokes*")
1120 (goto-char (point-min))
1122 ";; -*- emacs-lisp -*-\n")
1123 (insert (format ";;; saved strokes for %s, as of %s\n\n"
1125 (format-time-string "%B %e, %Y" nil
)))
1126 (message "Saving strokes in %s..." strokes-file
)
1127 (insert (format "(setq strokes-global-map\n'%s)"
1129 (message "Saving strokes in %s..." strokes-file
)
1130 (indent-region (point-min) (point-max) nil
)
1131 (write-region (point-min)
1134 (message "(no changes need to be saved)")))
1136 (if (get-buffer "*saved-strokes*")
1137 (kill-buffer (get-buffer "*saved-strokes*")))
1138 (setq strokes-global-map current
)))))
1140 (defun strokes-toggle-strokes-buffer (&optional arg
)
1141 "Toggle the use of the strokes buffer.
1142 In other words, toggle the variable `strokes-use-strokes-buffer'.
1143 With ARG, use strokes buffer if and only if ARG is positive or true.
1144 Returns value of `strokes-use-strokes-buffer'."
1146 (setq strokes-use-strokes-buffer
1147 (if arg
(> (prefix-numeric-value arg
) 0)
1148 (not strokes-use-strokes-buffer
))))
1150 (defun strokes-xpm-for-stroke (&optional stroke bufname b
/w-only
)
1151 "Create an XPM pixmap for the given STROKE in buffer ` *strokes-xpm*'.
1152 If STROKE is not supplied, then `strokes-last-stroke' will be used.
1153 Optional BUFNAME to name something else.
1154 The pixmap will contain time information via rainbow dot colors
1155 where each individual strokes begins.
1156 Optional B/W-ONLY non-nil will create a mono pixmap, not intended
1157 for trying to figure out the order of strokes, but rather for reading
1158 the stroke as a character in some language."
1161 (let ((buf (get-buffer-create (or bufname
" *strokes-xpm*")))
1162 (stroke (strokes-eliminate-consecutive-redundancies
1163 (strokes-fill-stroke
1164 (strokes-renormalize-to-grid (or stroke
1165 strokes-last-stroke
)
1168 (rainbow-chars (list ?R ?O ?Y ?G ?B ?P
))) ; ROYGBIV w/o indigo
1171 (insert strokes-xpm-header
)
1172 (cl-loop repeat
33 do
1174 (insert-char ?\s
33)
1181 (cl-loop for point in stroke
1182 for x
= (car-safe point
)
1183 for y
= (cdr-safe point
) do
1184 (cond ((consp point
)
1185 ;; draw a point, and possibly a starting-point
1186 (if (and lift-flag
(not b
/w-only
))
1187 ;; mark starting point with the appropriate color
1188 (let ((char (or (car rainbow-chars
) ?\.
)))
1189 (cl-loop for i from
0 to
2 do
1190 (cl-loop for j from
0 to
2 do
1191 (goto-char (point-min))
1192 (forward-line (+ 15 i y
))
1193 (forward-char (+ 1 j x
))
1196 (setq rainbow-chars
(cdr rainbow-chars
)
1198 ;; Otherwise, just plot the point...
1199 (goto-char (point-min))
1200 (forward-line (+ 16 y
))
1201 (forward-char (+ 2 x
))
1202 (subst-char-in-region (point) (1+ (point)) ?\s ?\
*)))
1203 ((strokes-lift-p point
)
1204 ;; a lift--tell the loop to X out the next point...
1205 (setq lift-flag t
))))
1206 (when (called-interactively-p 'interactive
)
1207 (pop-to-buffer " *strokes-xpm*")
1209 (goto-char (point-min))
1210 (put-image (create-image (buffer-string) 'xpm t
:ascent
100)
1211 (line-end-position))))))
1213 ;;; Strokes Edit stuff... ### NOT IMPLEMENTED YET ###
1215 ;;(defun strokes-edit-quit ()
1217 ;; (or (one-window-p t 0)
1219 ;; (kill-buffer "*Strokes List*"))
1221 ;;(define-derived-mode edit-strokes-mode list-mode
1223 ;; "Major mode for `edit-strokes' and `list-strokes' buffers.
1227 ;;\\{edit-strokes-mode-map}"
1228 ;; (setq truncate-lines nil
1229 ;; auto-show-mode nil ; don't want problems here either
1230 ;; mode-popup-menu edit-strokes-menu) ; what about extent-specific stuff?
1231 ;; (and (featurep 'menubar)
1233 ;; (set (make-local-variable 'current-menubar)
1234 ;; (copy-sequence current-menubar))
1235 ;; (add-submenu nil edit-strokes-menu)))
1237 ;;(let ((map edit-strokes-mode-map))
1238 ;; (define-key map "<" 'beginning-of-buffer)
1239 ;; (define-key map ">" 'end-of-buffer)
1240 ;; ;; (define-key map "c" 'strokes-copy-other-face)
1241 ;; ;; (define-key map "C" 'strokes-copy-this-face)
1242 ;; ;; (define-key map "s" 'strokes-smaller)
1243 ;; ;; (define-key map "l" 'strokes-larger)
1244 ;; ;; (define-key map "b" 'strokes-bold)
1245 ;; ;; (define-key map "i" 'strokes-italic)
1246 ;; (define-key map "e" 'strokes-list-edit)
1247 ;; ;; (define-key map "f" 'strokes-font)
1248 ;; ;; (define-key map "u" 'strokes-underline)
1249 ;; ;; (define-key map "t" 'strokes-truefont)
1250 ;; ;; (define-key map "F" 'strokes-foreground)
1251 ;; ;; (define-key map "B" 'strokes-background)
1252 ;; ;; (define-key map "D" 'strokes-doc-string)
1253 ;; (define-key map "a" 'strokes-global-set-stroke)
1254 ;; (define-key map "d" 'strokes-list-delete-stroke)
1255 ;; ;; (define-key map "n" 'strokes-list-next)
1256 ;; ;; (define-key map "p" 'strokes-list-prev)
1257 ;; ;; (define-key map " " 'strokes-list-next)
1258 ;; ;; (define-key map "\C-?" 'strokes-list-prev)
1259 ;; (define-key map "g" 'strokes-list-strokes) ; refresh display
1260 ;; (define-key map "q" 'strokes-edit-quit)
1261 ;; (define-key map [(control c) (control c)] 'bury-buffer))
1264 ;;(defun strokes-edit-strokes (&optional chronological strokes-map)
1265 ;; ;; ### DEAL WITH THE 2nd ARGUMENT ISSUE! ###
1266 ;; "Edit strokes in a pop-up buffer containing strokes and their definitions.
1267 ;;If STROKES-MAP is not given, `strokes-global-map' will be used instead.
1271 ;;\\{edit-faces-mode-map}"
1272 ;; (interactive "P")
1273 ;; (pop-to-buffer (get-buffer-create "*Strokes List*"))
1274 ;; (reset-buffer (current-buffer)) ; handy function from minibuf.el
1275 ;; (setq strokes-map (or strokes-map
1276 ;; strokes-global-map
1278 ;; (strokes-load-user-strokes)
1279 ;; strokes-global-map)))
1280 ;; (or chronological
1281 ;; (setq strokes-map (sort (copy-sequence strokes-map)
1282 ;; 'strokes-alphabetic-lessp)))
1283 ;; ;; (push-window-configuration)
1285 ;; "Command Stroke\n"
1286 ;; "------- ------")
1287 ;; (cl-loop for def in strokes-map
1288 ;; for i from 0 to (1- (length strokes-map)) do
1289 ;; (let ((stroke (car def))
1290 ;; (command-name (symbol-name (cdr def))))
1291 ;; (strokes-xpm-for-stroke stroke " *strokes-xpm*")
1293 ;; (insert-char ?\s 45)
1294 ;; (beginning-of-line)
1295 ;; (insert command-name)
1296 ;; (beginning-of-line)
1297 ;; (forward-char 45)
1298 ;; (set (intern (format "strokes-list-annotation-%d" i))
1299 ;; (make-annotation (make-glyph
1302 ;; :data (buffer-substring
1303 ;; (point-min " *strokes-xpm*")
1304 ;; (point-max " *strokes-xpm*")
1305 ;; " *strokes-xpm*"))
1306 ;; [string :data "[Stroke]"]))
1308 ;; (set-annotation-data (symbol-value (intern (format "strokes-list-annotation-%d" i)))
1310 ;; finally do (kill-region (1+ (point)) (point-max)))
1311 ;; (edit-strokes-mode)
1312 ;; (goto-char (point-min)))
1315 ;;(defalias 'edit-strokes 'strokes-edit-strokes)
1317 (defvar view-mode-map
)
1320 (defun strokes-list-strokes (&optional chronological strokes-map
)
1321 "Pop up a buffer containing an alphabetical listing of strokes in STROKES-MAP.
1322 With CHRONOLOGICAL prefix arg \(\\[universal-argument]\) list strokes
1323 chronologically by command name.
1324 If STROKES-MAP is not given, `strokes-global-map' will be used instead."
1326 (setq strokes-map
(or strokes-map
1329 (strokes-load-user-strokes)
1330 strokes-global-map
)))
1331 (if (not chronological
)
1332 ;; then alphabetize the strokes based on command names...
1333 (setq strokes-map
(sort (copy-sequence strokes-map
)
1334 (function strokes-alphabetic-lessp
))))
1335 (let ((config (current-window-configuration)))
1336 (set-buffer (get-buffer-create "*Strokes List*"))
1337 (setq buffer-read-only nil
)
1343 for def in strokes-map do
1344 (let ((stroke (car def
))
1345 (command-name (if (symbolp (cdr def
))
1346 (symbol-name (cdr def
))
1347 (prin1-to-string (cdr def
)))))
1348 (strokes-xpm-for-stroke stroke
" *strokes-xpm*")
1350 (insert-char ?\s
45)
1352 (insert command-name
)
1356 (create-image (with-current-buffer " *strokes-xpm*"
1361 .
,(frame-parameter nil
'foreground-color
))))))
1362 finally do
(unless (eobp)
1363 (kill-region (1+ (point)) (point-max))))
1364 (view-buffer "*Strokes List*" nil
)
1365 (set (make-local-variable 'view-mode-map
)
1366 (let ((map (copy-keymap view-mode-map
)))
1367 (define-key map
"q" `(lambda ()
1370 (set-window-configuration ,config
)))
1372 (goto-char (point-min))))
1374 (defun strokes-alphabetic-lessp (stroke1 stroke2
)
1375 "Return t if STROKE1's command name precedes STROKE2's in lexicographic order."
1376 (let ((command-name-1 (symbol-name (cdr stroke1
)))
1377 (command-name-2 (symbol-name (cdr stroke2
))))
1378 (string-lessp command-name-1 command-name-2
)))
1380 (defvar strokes-mode-map
1381 (let ((map (make-sparse-keymap)))
1382 (define-key map
[(shift down-mouse-2
)] 'strokes-do-stroke
)
1383 (define-key map
[(meta down-mouse-2
)] 'strokes-do-complex-stroke
)
1387 (define-minor-mode strokes-mode
1388 "Toggle Strokes mode, a global minor mode.
1389 With a prefix argument ARG, enable Strokes mode if ARG is
1390 positive, and disable it otherwise. If called from Lisp, enable
1391 the mode if ARG is omitted or nil.
1393 \\<strokes-mode-map>
1394 Strokes are pictographic mouse gestures which invoke commands.
1395 Strokes are invoked with \\[strokes-do-stroke]. You can define
1396 new strokes with \\[strokes-global-set-stroke]. See also
1397 \\[strokes-do-complex-stroke] for `complex' strokes.
1399 To use strokes for pictographic editing, such as Chinese/Japanese, use
1400 \\[strokes-compose-complex-stroke], which draws strokes and inserts them.
1401 Encode/decode your strokes with \\[strokes-encode-buffer],
1402 \\[strokes-decode-buffer].
1404 \\{strokes-mode-map}"
1405 nil strokes-lighter strokes-mode-map
1406 :group
'strokes
:global t
1407 (cond ((not (display-mouse-p))
1408 (error "Can't use Strokes without a mouse"))
1409 (strokes-mode ; turn on strokes
1410 (and (file-exists-p strokes-file
)
1411 (null strokes-global-map
)
1412 (strokes-load-user-strokes))
1413 (add-hook 'kill-emacs-query-functions
1414 'strokes-prompt-user-save-strokes
)
1415 (add-hook 'select-frame-hook
1416 'strokes-update-window-configuration
)
1417 (strokes-update-window-configuration))
1418 (t ; turn off strokes
1419 (if (get-buffer strokes-buffer-name
)
1420 (kill-buffer (get-buffer strokes-buffer-name
)))
1421 (remove-hook 'select-frame-hook
1422 'strokes-update-window-configuration
))))
1425 ;;;; strokes-xpm stuff (later may be separate)...
1427 ;; This is the stuff that will eventually be used for composing letters in
1428 ;; any language, compression, decompression, graphics, editing, etc.
1430 (defface strokes-char
'((t (:background
"lightgray")))
1431 "Face for strokes characters."
1435 (put 'strokes
'char-table-extra-slots
0)
1436 (defconst strokes-char-table
(make-char-table 'strokes
) ;
1437 "The table which stores values for the character keys.")
1438 (aset strokes-char-table ?
0 0)
1439 (aset strokes-char-table ?
1 1)
1440 (aset strokes-char-table ?
2 2)
1441 (aset strokes-char-table ?
3 3)
1442 (aset strokes-char-table ?
4 4)
1443 (aset strokes-char-table ?
5 5)
1444 (aset strokes-char-table ?
6 6)
1445 (aset strokes-char-table ?
7 7)
1446 (aset strokes-char-table ?
8 8)
1447 (aset strokes-char-table ?
9 9)
1448 (aset strokes-char-table ?a
10)
1449 (aset strokes-char-table ?b
11)
1450 (aset strokes-char-table ?c
12)
1451 (aset strokes-char-table ?d
13)
1452 (aset strokes-char-table ?e
14)
1453 (aset strokes-char-table ?f
15)
1454 (aset strokes-char-table ?g
16)
1455 (aset strokes-char-table ?h
17)
1456 (aset strokes-char-table ?i
18)
1457 (aset strokes-char-table ?j
19)
1458 (aset strokes-char-table ?k
20)
1459 (aset strokes-char-table ?l
21)
1460 (aset strokes-char-table ?m
22)
1461 (aset strokes-char-table ?n
23)
1462 (aset strokes-char-table ?o
24)
1463 (aset strokes-char-table ?p
25)
1464 (aset strokes-char-table ?q
26)
1465 (aset strokes-char-table ?r
27)
1466 (aset strokes-char-table ?s
28)
1467 (aset strokes-char-table ?t
29)
1468 (aset strokes-char-table ?u
30)
1469 (aset strokes-char-table ?v
31)
1470 (aset strokes-char-table ?w
32)
1471 (aset strokes-char-table ?x
33)
1472 (aset strokes-char-table ?y
34)
1473 (aset strokes-char-table ?z
35)
1474 (aset strokes-char-table ?A
36)
1475 (aset strokes-char-table ?B
37)
1476 (aset strokes-char-table ?C
38)
1477 (aset strokes-char-table ?D
39)
1478 (aset strokes-char-table ?E
40)
1479 (aset strokes-char-table ?F
41)
1480 (aset strokes-char-table ?G
42)
1481 (aset strokes-char-table ?H
43)
1482 (aset strokes-char-table ?I
44)
1483 (aset strokes-char-table ?J
45)
1484 (aset strokes-char-table ?K
46)
1485 (aset strokes-char-table ?L
47)
1486 (aset strokes-char-table ?M
48)
1487 (aset strokes-char-table ?N
49)
1488 (aset strokes-char-table ?O
50)
1489 (aset strokes-char-table ?P
51)
1490 (aset strokes-char-table ?Q
52)
1491 (aset strokes-char-table ?R
53)
1492 (aset strokes-char-table ?S
54)
1493 (aset strokes-char-table ?T
55)
1494 (aset strokes-char-table ?U
56)
1495 (aset strokes-char-table ?V
57)
1496 (aset strokes-char-table ?W
58)
1497 (aset strokes-char-table ?X
59)
1498 (aset strokes-char-table ?Y
60)
1499 (aset strokes-char-table ?Z
61)
1501 (defconst strokes-base64-chars
1502 ;; I wanted to make this a vector of individual like (vector ?0
1503 ;; ?1 ?2 ...), but `concat' refuses to accept single
1505 (vector "0" "1" "2" "3" "4" "5" "6" "7" "8" "9"
1506 "a" "b" "c" "d" "e" "f" "g" "h" "i" "j" "k" "l" "m" "n" "o"
1507 "p" "q" "r" "s" "t" "u" "v" "w" "x" "y" "z" "A" "B" "C" "D"
1508 "E" "F" "G" "H" "I" "J" "K" "L" "M" "N" "O" "P" "Q" "R" "S"
1509 "T" "U" "V" "W" "X" "Y" "Z")
1510 ;; (vector [?0] [?1] [?2] [?3] [?4] [?5] [?6] [?7] [?8] [?9]
1511 ;; [?a] [?b] [?c] [?d] [?e] [?f] [?g] [?h] [?i] [?j]
1512 ;; [?k] [?l] [?m] [?n] [?o] [?p] [?q] [?r] [?s] [?t]
1513 ;; [?u] [?v] [?w] [?x] [?y] [?z]
1514 ;; [?A] [?B] [?C] [?D] [?E] [?F] [?G] [?H] [?I] [?J]
1515 ;; [?K] [?L] [?M] [?N] [?O] [?P] [?Q] [?R] [?S] [?T]
1516 ;; [?U] [?V] [?W] [?X] [?Y] [?Z])
1517 "Character vector for fast lookup of base-64 encoding of numbers in [0,61].")
1519 (defsubst strokes-xpm-char-on-p
(char)
1520 "Non-nil if CHAR represents an `on' bit in the XPM."
1523 (defsubst strokes-xpm-char-bit-p
(char)
1524 "Non-nil if CHAR represents an `on' or `off' bit in the XPM."
1528 ;;(defsubst strokes-xor (a b) ### Should I make this an inline function? ###
1529 ;; "T if one and only one of A and B is non-nil; otherwise, returns nil.
1530 ;;NOTE: Don't use this as a numeric xor since it treats all non-nil
1531 ;; values as t including `0' (zero)."
1532 ;; (eq (null a) (not (null b))))
1534 (defsubst strokes-xpm-encode-length-as-string
(length)
1535 "Given some LENGTH in [0,62) do a fast lookup of its encoding."
1536 (aref strokes-base64-chars length
))
1538 (defsubst strokes-xpm-decode-char
(character)
1539 "Given a CHARACTER, do a fast lookup to find its corresponding integer value."
1540 (aref strokes-char-table character
))
1542 (defun strokes-xpm-to-compressed-string (&optional xpm-buffer
)
1543 "Convert XPM in XPM-BUFFER to compressed string representing the stroke.
1544 XPM-BUFFER defaults to ` *strokes-xpm*'."
1545 (with-current-buffer (setq xpm-buffer
(or xpm-buffer
" *strokes-xpm*"))
1546 (goto-char (point-min))
1547 (search-forward "/* pixels */") ; skip past header junk
1549 ;; a note for below:
1550 ;; the `current-char' is the char being counted -- NOT the char at (point)
1551 ;; which happens to be called `char-at-point'
1552 (let ((compressed-string "+/") ; initialize the output
1553 (count 0) ; keep a current count of
1555 (last-char-was-on-p t
) ; last entered stream
1556 ; represented `on' bits
1557 (current-char-is-on-p nil
) ; current stream represents `on' bits
1558 (char-at-point (char-after))) ; read the first char
1559 (while (not (eq char-at-point ?
})) ; a `}' denotes the
1561 (cond ((zerop count
) ; must restart counting
1562 ;; check to see if the `char-at-point' is an actual pixmap bit
1563 (when (strokes-xpm-char-bit-p char-at-point
)
1565 current-char-is-on-p
(strokes-xpm-char-on-p char-at-point
)))
1567 ((= count
61) ; maximum single char's
1569 (setq compressed-string
1570 (concat compressed-string
1571 ;; add a zero-length encoding when
1573 (when (eq last-char-was-on-p
1574 current-char-is-on-p
)
1576 (strokes-xpm-encode-length-as-string 0))
1577 (strokes-xpm-encode-length-as-string 61))
1578 last-char-was-on-p current-char-is-on-p
1579 count
0)) ; note that we just set
1580 ; count=0 and *don't* advance
1582 ((strokes-xpm-char-bit-p char-at-point
) ; an actual xpm bit
1583 (if (eq current-char-is-on-p
1584 (strokes-xpm-char-on-p char-at-point
))
1585 ;; yet another of the same bit-type, so we continue
1590 ;; otherwise, it's the opposite bit-type, so we do a
1591 ;; write and then restart count ### NOTE (for myself
1592 ;; to be aware of) ### I really should advance
1593 ;; (point) in this case instead of letting another
1594 ;; iteration go through and letting the case: count=0
1595 ;; take care of this stuff for me. That's why
1596 ;; there's no (forward-char 1) below.
1597 (setq compressed-string
1598 (concat compressed-string
1599 ;; add a zero-length encoding when
1601 (when (eq last-char-was-on-p
1602 current-char-is-on-p
)
1604 (strokes-xpm-encode-length-as-string 0))
1605 (strokes-xpm-encode-length-as-string count
))
1607 last-char-was-on-p current-char-is-on-p
)))
1608 (t ; ELSE it's some other useless
1609 ; char, like `"' or `,'
1611 (setq char-at-point
(char-after)))
1612 (concat compressed-string
1614 (concat (when (eq last-char-was-on-p
1615 current-char-is-on-p
)
1617 (strokes-xpm-encode-length-as-string 0))
1618 (strokes-xpm-encode-length-as-string count
)))
1622 (defun strokes-decode-buffer (&optional buffer force
)
1623 "Decode stroke strings in BUFFER and display their corresponding glyphs.
1624 Optional BUFFER defaults to the current buffer.
1625 Optional FORCE non-nil will ignore the buffer's read-only status."
1627 ;; (interactive "*bStrokify buffer: ")
1628 (with-current-buffer (setq buffer
(get-buffer (or buffer
(current-buffer))))
1629 (when (or (not buffer-read-only
)
1633 (format "Buffer %s is read-only. Strokify anyway? " buffer
)))
1634 (let ((inhibit-read-only t
))
1635 (message "Strokifying %s..." buffer
)
1636 (goto-char (point-min))
1638 ;; The comment below is what I'd have to do if I wanted to
1639 ;; deal with random newlines in the midst of the compressed
1640 ;; strings. If I do this, I'll also have to change
1641 ;; `strokes-xpm-to-compress-string' to deal with the newline,
1642 ;; and possibly other whitespace stuff. YUCK!
1643 ;; (while (re-search-forward "\\+/\\(\\w\\|\\)+/" nil t nil (get-buffer buffer))
1644 (while (with-current-buffer buffer
1645 (when (re-search-forward "\\+/\\(\\w+\\)/" nil t nil
)
1646 (setq string
(match-string 1))
1647 (goto-char (match-end 0))
1650 (strokes-xpm-for-compressed-string string
" *strokes-xpm*")
1651 (setq image
(create-image (with-current-buffer " *strokes-xpm*"
1659 (message "Strokifying %s...done" buffer
)))))
1661 (defun strokes-encode-buffer (&optional buffer force
)
1662 "Convert the glyphs in BUFFER to their base-64 ASCII representations.
1663 Optional BUFFER defaults to the current buffer.
1664 Optional FORCE non-nil will ignore the buffer's read-only status."
1665 ;; ### NOTE !!! ### (for me)
1666 ;; For later on, you can/should make the inserted strings atomic
1667 ;; extents, so that the users have a clue that they shouldn't be
1668 ;; editing inside them. Plus, if you make them extents, you can
1669 ;; very easily just hide the glyphs, so if you unstrokify, and the
1670 ;; restrokify, then those that already are glyphed don't need to be
1671 ;; re-calculated, etc. It's just nicer that way. The only things
1672 ;; to worry about is cleanup (i.e. do the glyphs get gc'd when the
1673 ;; buffer is killed?
1674 ;; (interactive "*bUnstrokify buffer: ")
1676 (with-current-buffer (setq buffer
(or buffer
(current-buffer)))
1677 (when (or (not buffer-read-only
)
1681 (format "Buffer %s is read-only. Encode anyway? " buffer
)))
1682 (message "Encoding strokes in %s..." buffer
)
1684 ;; (lambda (ext buf)
1685 ;; (when (eq (extent-property ext 'type) 'stroke-glyph)
1686 ;; (goto-char (extent-start-position ext))
1687 ;; (delete-char 1) ; ### What the hell do I do here? ###
1688 ;; (insert "+/" (extent-property ext 'data) "/")
1689 ;; (delete-extent ext))))))
1690 (let ((inhibit-read-only t
)
1693 (while (or (and (bobp)
1694 (get-text-property (point) 'type
))
1695 (setq start
(next-single-property-change (point) 'type
)))
1696 (when (eq 'stroke-glyph
(get-text-property (point) 'type
))
1698 (setq start
(point-marker)
1699 glyph
(get-text-property start
'display
))
1700 (insert "+/" (get-text-property (point) 'data
) ?
/)
1702 (add-text-properties start
(point)
1703 (list 'type
'stroke-string
1707 (message "Encoding strokes in %s...done" buffer
)))))
1709 (defun strokes-xpm-for-compressed-string (compressed-string &optional bufname
)
1710 "Convert the stroke represented by COMPRESSED-STRING into an XPM.
1711 Store XPM in buffer BUFNAME if supplied \(default is ` *strokes-xpm*'\)"
1712 (or bufname
(setq bufname
" *strokes-xpm*"))
1713 (with-current-buffer (get-buffer-create bufname
)
1715 (insert compressed-string
)
1716 (goto-char (point-min))
1717 (let ((current-char-is-on-p nil
))
1720 (if current-char-is-on-p
1723 (strokes-xpm-decode-char (char-after)))
1725 (setq current-char-is-on-p
(not current-char-is-on-p
)))
1726 (goto-char (point-min))
1727 (cl-loop repeat
33 do
1731 (goto-char (point-min))
1732 (insert strokes-xpm-header
))))
1735 (defun strokes-compose-complex-stroke ()
1737 ;; Even though we don't have lexical scoping, it's somewhat ugly how I
1738 ;; pass around variables in the global name space. I can/should
1740 "Read a complex stroke and insert its glyph into the current buffer."
1742 (let ((strokes-grid-resolution 33))
1743 (strokes-read-complex-stroke)
1744 (strokes-xpm-for-stroke nil
" *strokes-xpm*" t
)
1745 (insert (strokes-xpm-to-compressed-string " *strokes-xpm*"))
1746 (strokes-decode-buffer)
1747 ;; strokes-decode-buffer does a save-excursion.
1750 (defun strokes-unload-function ()
1751 "Unload the Strokes library."
1753 ;; continue standard unloading
1756 (run-hooks 'strokes-load-hook
)
1759 ;;; strokes.el ends here