first commit
This commit is contained in:
		
							parent
							
								
									cc639da34c
								
							
						
					
					
						commit
						c702b5fa52
					
				| 
						 | 
				
			
			@ -0,0 +1,342 @@
 | 
			
		|||
;; the window for the question is mapped at the bottom of parent, or
 | 
			
		||||
;; in the middle of the root-window if parent is #f.
 | 
			
		||||
 | 
			
		||||
;; if answers is a list of strings then that are the valid answers and
 | 
			
		||||
;; one of it is returned. if it is #f then the string terminated by RETURN
 | 
			
		||||
;; is returned. ..... list of chars
 | 
			
		||||
;; if ESC is pressed #f is returned.
 | 
			
		||||
 | 
			
		||||
(define-options-spec prompt-options-spec
 | 
			
		||||
  (font font "-*-helvetica-medium-r-normal-*-12-*-*-*-*-*-*-*")
 | 
			
		||||
  (font-color color "black")
 | 
			
		||||
  (background-color color "#cccccc")
 | 
			
		||||
  (edit-font font "-*-helvetica-medium-r-normal-*-12-*-*-*-*-*-*-*")
 | 
			
		||||
  (edit-font-color color "black")
 | 
			
		||||
  (edit-background-color color "white")
 | 
			
		||||
  (border-width int 4))
 | 
			
		||||
 | 
			
		||||
(define options #f)
 | 
			
		||||
 | 
			
		||||
(define line-spacing 1)
 | 
			
		||||
 | 
			
		||||
(define xk-escape 65307) ;; 0xFF1B
 | 
			
		||||
(define xk-return 65293) ;; 0xFF0D
 | 
			
		||||
(define xk-left 65361) ;; 0xFF51
 | 
			
		||||
(define xk-right 65363) ;; 0xFF53
 | 
			
		||||
(define xk-home 65360) ;; 0xFF50
 | 
			
		||||
(define xk-end 65367) ;; 0xFF57
 | 
			
		||||
(define xk-delete 65535) ;; 0xFFFF
 | 
			
		||||
(define xk-backspace 65288) ;; 0xFF08
 | 
			
		||||
(define xk-up 65362) ;; 0xFF52
 | 
			
		||||
(define xk-down 65364) ;; 0xFF54
 | 
			
		||||
(define xk-tab 65289) ;; 0xFF09
 | 
			
		||||
 | 
			
		||||
;; complete is a function (lambda (string cursor-pos) ...) returning
 | 
			
		||||
;; either a new (string . cursor-pos) pair or a string list that is
 | 
			
		||||
;; displayed as possible completions.
 | 
			
		||||
 | 
			
		||||
(define (prompt dpy parent question answers complete)
 | 
			
		||||
  (if (not options)
 | 
			
		||||
      (set! options
 | 
			
		||||
	    (create-options dpy (screen:default-colormap
 | 
			
		||||
				 (display:default-screen dpy))
 | 
			
		||||
			    prompt-options-spec '())))
 | 
			
		||||
  (let* ((root-window (default-root-window dpy))
 | 
			
		||||
	 (need-edit? (not (and answers (not (null? answers))
 | 
			
		||||
			       (every char? answers))))
 | 
			
		||||
	 (rects (calc-rects dpy parent question need-edit? #f options))
 | 
			
		||||
	 (win-r (first rects))
 | 
			
		||||
	 (window (create-simple-window
 | 
			
		||||
		  dpy (or parent root-window)
 | 
			
		||||
		  (rectangle:x win-r) (rectangle:y win-r)
 | 
			
		||||
		  (rectangle:width win-r) (rectangle:height win-r)
 | 
			
		||||
		  0 (black-pixel dpy)
 | 
			
		||||
		  (get-option-value options 'background-color))))
 | 
			
		||||
    (set-window-override-redirect! dpy window #t)
 | 
			
		||||
 | 
			
		||||
    ;; maybe grab-pointer
 | 
			
		||||
    (call-with-event-channel
 | 
			
		||||
     dpy window (event-mask exposure key-press)
 | 
			
		||||
     (lambda (event-channel)
 | 
			
		||||
       (map-window dpy window)
 | 
			
		||||
       (set-input-focus dpy window (revert-to parent) current-time)
 | 
			
		||||
       (let* ((gc (create-gc dpy window (make-gc-value-alist)))
 | 
			
		||||
	      (result (do-input dpy parent question answers complete need-edit?
 | 
			
		||||
				window gc options event-channel)))
 | 
			
		||||
	 (free-gc dpy gc)
 | 
			
		||||
	 (destroy-window dpy window)
 | 
			
		||||
	 result)))))
 | 
			
		||||
 | 
			
		||||
(define (do-input dpy parent question answers complete need-edit?
 | 
			
		||||
		  window gc options event-channel)
 | 
			
		||||
  (let* ((matched-answer
 | 
			
		||||
	  (lambda (s)
 | 
			
		||||
	    (if answers
 | 
			
		||||
		(let ((l (filter (lambda (a)
 | 
			
		||||
				   (or (and (char? a) (not (equal? s ""))
 | 
			
		||||
					    (eq? a (string-ref s 0)))
 | 
			
		||||
				       (equal? a s)))
 | 
			
		||||
				 answers)))
 | 
			
		||||
		  (and (not (null? l)) (car l)))
 | 
			
		||||
		s)))
 | 
			
		||||
	 (last-input #f) (last-cursor #f)
 | 
			
		||||
	 (last-completions #t)
 | 
			
		||||
	 (rects #f)
 | 
			
		||||
	 (completions-rect #f)
 | 
			
		||||
	 (question-rect #f)
 | 
			
		||||
	 (edit-rect #f))
 | 
			
		||||
    (let loop ((input "")
 | 
			
		||||
	       (cursor 0)
 | 
			
		||||
	       (completions #f))
 | 
			
		||||
      (if (not (eq? completions last-completions))
 | 
			
		||||
	  (begin
 | 
			
		||||
	    (set! rects (calc-rects dpy parent question need-edit? completions
 | 
			
		||||
				    options))
 | 
			
		||||
	    (move-resize-window* dpy window (first rects))
 | 
			
		||||
	    (set! completions-rect (second rects))
 | 
			
		||||
	    (set! question-rect (third rects))
 | 
			
		||||
	    (set! edit-rect (fourth rects)))
 | 
			
		||||
	  (if (and need-edit?
 | 
			
		||||
		   (or (not (eq? input last-input))
 | 
			
		||||
		       (not (eq? cursor last-cursor))))
 | 
			
		||||
	      (draw-edit dpy window gc options input cursor edit-rect)))
 | 
			
		||||
      (set! last-input input)
 | 
			
		||||
      (set! last-cursor cursor)
 | 
			
		||||
      (set! last-completions completions)
 | 
			
		||||
 | 
			
		||||
      (let ((e (receive event-channel)))
 | 
			
		||||
	(cond
 | 
			
		||||
	 ((expose-event? e)
 | 
			
		||||
	  (if completions
 | 
			
		||||
	      (draw-completions dpy window gc options completions
 | 
			
		||||
				completions-rect))
 | 
			
		||||
	  (draw-question dpy window gc options question question-rect)
 | 
			
		||||
	  (if need-edit?
 | 
			
		||||
	      (draw-edit dpy window gc options input cursor edit-rect))
 | 
			
		||||
	  (loop input cursor completions))
 | 
			
		||||
 | 
			
		||||
	 ((eq? (event-type key-press) (any-event-type e))
 | 
			
		||||
	  (let* ((keysym.str (lookup-string/keysym e))
 | 
			
		||||
		 (keysym (car keysym.str))
 | 
			
		||||
		 (str (cdr keysym.str)))
 | 
			
		||||
	    (cond
 | 
			
		||||
	     ((equal? keysym xk-escape) #f)
 | 
			
		||||
	     ((equal? keysym xk-return)
 | 
			
		||||
	      (or (matched-answer input)
 | 
			
		||||
		  (loop "" 0 #f)))
 | 
			
		||||
	     ((not need-edit?)
 | 
			
		||||
	      (or (matched-answer str)
 | 
			
		||||
		  (loop "" 0 #f)))
 | 
			
		||||
	     ((equal? keysym xk-left)
 | 
			
		||||
	      (loop input
 | 
			
		||||
		    (if (> cursor 0) (- cursor 1) cursor)
 | 
			
		||||
		    completions))
 | 
			
		||||
	     ((equal? keysym xk-right)
 | 
			
		||||
	      (loop input
 | 
			
		||||
		    (if (< cursor (string-length input))
 | 
			
		||||
			(+ cursor 1) cursor)
 | 
			
		||||
		    completions))
 | 
			
		||||
	     ((equal? keysym xk-home)
 | 
			
		||||
	      (loop input 0 completions))
 | 
			
		||||
	     ((equal? keysym xk-end)
 | 
			
		||||
	      (loop input (string-length input) completions))
 | 
			
		||||
	     ((equal? keysym xk-delete)
 | 
			
		||||
	      (loop (if (< cursor (string-length input))
 | 
			
		||||
			(string-append
 | 
			
		||||
			 (substring input 0 cursor)
 | 
			
		||||
			 (substring input (+ cursor 1)
 | 
			
		||||
				    (string-length input)))
 | 
			
		||||
			input)
 | 
			
		||||
		    cursor completions))
 | 
			
		||||
	     ((equal? keysym xk-backspace)
 | 
			
		||||
	      (if (> cursor 0)
 | 
			
		||||
		  (loop (string-append
 | 
			
		||||
			 (substring input 0 (- cursor 1))
 | 
			
		||||
			 (substring input cursor
 | 
			
		||||
				    (string-length input)))
 | 
			
		||||
			(- cursor 1) completions)
 | 
			
		||||
		  (loop input cursor completions)))
 | 
			
		||||
	     ((equal? keysym xk-tab)
 | 
			
		||||
	      ;; tab-completion
 | 
			
		||||
	      (if complete
 | 
			
		||||
		  (let ((res (complete input cursor)))
 | 
			
		||||
		    (if (and (pair? res) (not (pair? (cdr res))))
 | 
			
		||||
			(loop (car res) (cdr res) #f)
 | 
			
		||||
			(loop input cursor res)))
 | 
			
		||||
		  (loop input cursor completions)))
 | 
			
		||||
	     ;; TODO: up-down history
 | 
			
		||||
	     (else
 | 
			
		||||
	      (let ((new (string-append
 | 
			
		||||
			  (substring input 0 cursor)
 | 
			
		||||
			  str
 | 
			
		||||
			  (substring input cursor
 | 
			
		||||
				     (string-length input)))))
 | 
			
		||||
		(loop new
 | 
			
		||||
		      (+ cursor (string-length str))
 | 
			
		||||
		      completions))))))
 | 
			
		||||
	 (else (loop input cursor completions)))))))
 | 
			
		||||
 | 
			
		||||
(define (calc-rects dpy parent question need-edit? completions? options)
 | 
			
		||||
  (let* ((edit-font (get-option-value options 'edit-font))
 | 
			
		||||
	 (want-completions-width (if completions?
 | 
			
		||||
				     (strings-width edit-font completions?)
 | 
			
		||||
				     0))
 | 
			
		||||
	 (question-font (get-option-value options 'font))
 | 
			
		||||
	 (maximum-width 800) ;; or 2/3 of screen-width ??
 | 
			
		||||
	 (minimum-width 512) ;; or 1/3 of screen-widht ??
 | 
			
		||||
	 (want-question-width
 | 
			
		||||
	  (strings-width question-font
 | 
			
		||||
			 (split-multiline-text
 | 
			
		||||
			  question-font question
 | 
			
		||||
			  (if parent (window-width dpy parent)
 | 
			
		||||
			      maximum-width))))
 | 
			
		||||
	 (width (if parent
 | 
			
		||||
		    (window-width dpy parent)
 | 
			
		||||
		    (max minimum-width (min maximum-width
 | 
			
		||||
					    (max want-completions-width
 | 
			
		||||
						 want-question-width)))))
 | 
			
		||||
	 (border-width (get-option-value options 'border-width))
 | 
			
		||||
	 (completions-height (if completions?
 | 
			
		||||
				 (strings-height edit-font completions?)
 | 
			
		||||
				 0))
 | 
			
		||||
	 (question-width (- width (* 2 border-width)))
 | 
			
		||||
	 (completions-width question-width)
 | 
			
		||||
	 (question-height (strings-height question-font
 | 
			
		||||
					  (split-multiline-text
 | 
			
		||||
					   question-font question
 | 
			
		||||
					   question-width)))
 | 
			
		||||
	 (edit-height (if need-edit?
 | 
			
		||||
			  (+ 2 (font-struct:ascent edit-font)
 | 
			
		||||
			     (font-struct:descent edit-font))
 | 
			
		||||
			  0))
 | 
			
		||||
	 (edit-width question-width)
 | 
			
		||||
	 (spacing 2)
 | 
			
		||||
	 (height (+ (* 2 border-width) question-height completions-height
 | 
			
		||||
		    edit-height
 | 
			
		||||
		    (if need-edit? spacing 0)
 | 
			
		||||
		    (if completions? spacing 0)))
 | 
			
		||||
	 (root-window (default-root-window dpy))
 | 
			
		||||
	 (x (if parent 0
 | 
			
		||||
		(quotient (- (window-width dpy root-window) width) 2)))
 | 
			
		||||
	 (y (if parent
 | 
			
		||||
		(- (window-height dpy parent) height)
 | 
			
		||||
		(quotient (- (window-height dpy root-window) height) 2))))
 | 
			
		||||
    (let* ((window-rect (make-rectangle x y width height))
 | 
			
		||||
	   (completions-rect (make-rectangle border-width border-width
 | 
			
		||||
					     completions-width
 | 
			
		||||
					     completions-height))
 | 
			
		||||
	   (question-rect
 | 
			
		||||
	    (make-rectangle border-width
 | 
			
		||||
			    (if completions?
 | 
			
		||||
				(+ spacing (rectangle:y completions-rect)
 | 
			
		||||
				   (rectangle:height completions-rect))
 | 
			
		||||
				border-width)
 | 
			
		||||
			    question-width question-height))
 | 
			
		||||
	   (edit-rect
 | 
			
		||||
	    (make-rectangle border-width
 | 
			
		||||
			    (+ spacing (rectangle:y question-rect)
 | 
			
		||||
			       (rectangle:height question-rect))
 | 
			
		||||
			    edit-width edit-height)))
 | 
			
		||||
      (list window-rect completions-rect question-rect edit-rect))))
 | 
			
		||||
 | 
			
		||||
(define (draw-background dpy window gc options floating?)
 | 
			
		||||
  (let ((r (clip-rectangle dpy window)))
 | 
			
		||||
    (set-gc-foreground! dpy gc (get-option-value options 'background-color))
 | 
			
		||||
    (fill-rectangle* dpy window gc r)
 | 
			
		||||
    (if floating?
 | 
			
		||||
	(draw-shadow-rectangle dpy window gc r (white-pixel dpy)
 | 
			
		||||
			       (black-pixel dpy)))))
 | 
			
		||||
 | 
			
		||||
(define (draw-question dpy window gc options question r)
 | 
			
		||||
  (let ((font-struct (get-option-value options 'font)))
 | 
			
		||||
    (set-gc-font! dpy gc (font-struct:fid font-struct))
 | 
			
		||||
    (set-gc-foreground! dpy gc (get-option-value options 'font-color))
 | 
			
		||||
    (set-gc-background! dpy gc (get-option-value options 'background-color))
 | 
			
		||||
    (draw-multiline-text dpy window gc font-struct question r)))
 | 
			
		||||
 | 
			
		||||
(define (draw-completions dpy window gc options strings r)
 | 
			
		||||
  (let ((fs (get-option-value options 'edit-font)))
 | 
			
		||||
    (set-gc-font! dpy gc (font-struct:fid fs)) ;; ??
 | 
			
		||||
    (set-gc-foreground! dpy gc (get-option-value options 'font-color))
 | 
			
		||||
    (set-gc-background! dpy gc (get-option-value options 'background-color))
 | 
			
		||||
    (draw-text-lines dpy window gc fs strings r)))
 | 
			
		||||
 | 
			
		||||
(define (split-multiline-text fs text max-width)
 | 
			
		||||
  (letrec ((split (lambda (x chars current lines)
 | 
			
		||||
		    (cond
 | 
			
		||||
		     ((null? chars) (reverse (cons current lines)))
 | 
			
		||||
		     ((eq? (car chars) #\newline) (split 0 (cdr chars)
 | 
			
		||||
						    "" (cons current lines)))
 | 
			
		||||
		     (else
 | 
			
		||||
		      (let* ((s (make-string 1 (car chars)))
 | 
			
		||||
			     (cw (char-struct:width (text-extents fs s))))
 | 
			
		||||
			(if (<= (+ x cw) max-width)
 | 
			
		||||
			    (split (+ x cw) (cdr chars)
 | 
			
		||||
				   (string-append current s) lines)
 | 
			
		||||
			    (split cw (cdr chars)
 | 
			
		||||
				   s (cons current lines)))))))))
 | 
			
		||||
    (split 0 (string->list text) "" '())))
 | 
			
		||||
 | 
			
		||||
(define (draw-multiline-text dpy window gc fs text r)
 | 
			
		||||
  (draw-text-lines dpy window gc fs
 | 
			
		||||
		   (split-multiline-text fs text (rectangle:width r))
 | 
			
		||||
		   r))
 | 
			
		||||
 | 
			
		||||
(define (draw-text-lines dpy window gc fs lines r)
 | 
			
		||||
  ;; maybe clip drawing...
 | 
			
		||||
  (let ((x (rectangle:x r))
 | 
			
		||||
	(line-height (+ line-spacing (font-struct:ascent fs)
 | 
			
		||||
			(font-struct:descent fs))))
 | 
			
		||||
    (let loop ((y (rectangle:y r)) ;; what about max-height ??
 | 
			
		||||
	       (lines lines))
 | 
			
		||||
      (if (not (null? lines))
 | 
			
		||||
	  (let ((line (car lines)))
 | 
			
		||||
	    (draw-image-string dpy window gc x (+ y (font-struct:ascent fs))
 | 
			
		||||
			       line)
 | 
			
		||||
	    (loop (+ y line-height) (cdr lines)))))))
 | 
			
		||||
 | 
			
		||||
(define (draw-edit dpy window gc options input cursor r)
 | 
			
		||||
  (let* ((font-struct (get-option-value options 'edit-font))
 | 
			
		||||
	 (bg (get-option-value options 'edit-background-color))
 | 
			
		||||
	 (fg (get-option-value options 'edit-font-color))
 | 
			
		||||
	 (asc (font-struct:ascent font-struct)))
 | 
			
		||||
    (set-gc-foreground! dpy gc bg)
 | 
			
		||||
    (fill-rectangle* dpy window gc r)
 | 
			
		||||
    (draw-shadow-rectangle dpy window gc r (black-pixel dpy)
 | 
			
		||||
			   (white-pixel dpy))
 | 
			
		||||
 | 
			
		||||
    (set-gc-font! dpy gc (font-struct:fid font-struct))
 | 
			
		||||
    (set-gc-foreground! dpy gc fg)
 | 
			
		||||
    (set-gc-background! dpy gc bg)
 | 
			
		||||
    (draw-image-string dpy window gc (+ (rectangle:x r) 1)
 | 
			
		||||
		       (+ (rectangle:y r) 1 asc) input)
 | 
			
		||||
    ;; cursor
 | 
			
		||||
    (set-gc-function! dpy gc (gc-function xor))
 | 
			
		||||
    (set-gc-foreground! dpy gc (white-pixel dpy))
 | 
			
		||||
    (let* ((pre-cursor-width
 | 
			
		||||
	    (char-struct:width (text-extents font-struct
 | 
			
		||||
					     (substring input 0 cursor))))
 | 
			
		||||
	   (cursor-width (char-struct:width
 | 
			
		||||
			  (text-extents font-struct
 | 
			
		||||
					(if (< cursor (string-length input))
 | 
			
		||||
					    (substring input cursor
 | 
			
		||||
						       (+ cursor 1))
 | 
			
		||||
					    " ")))))
 | 
			
		||||
      (fill-rectangle dpy window gc
 | 
			
		||||
		      (+ (rectangle:x r) pre-cursor-width 1)
 | 
			
		||||
		      (+ (rectangle:y r) 1)
 | 
			
		||||
		      cursor-width
 | 
			
		||||
		      (+ 1 (font-struct:ascent font-struct)
 | 
			
		||||
			 (font-struct:descent font-struct)))
 | 
			
		||||
      (set-gc-function! dpy gc (gc-function copy)))))
 | 
			
		||||
 | 
			
		||||
(define (strings-width fs strings)
 | 
			
		||||
  (apply max
 | 
			
		||||
	 (map (lambda (s)
 | 
			
		||||
		(char-struct:width (text-extents fs s)))
 | 
			
		||||
	      strings)))
 | 
			
		||||
 | 
			
		||||
(define (strings-height fs strings)
 | 
			
		||||
  (let ((line-height (+ line-spacing (font-struct:ascent fs)
 | 
			
		||||
			(font-struct:descent fs))))
 | 
			
		||||
    (* line-height (length strings))))
 | 
			
		||||
		Loading…
	
		Reference in New Issue