dotfiles/sbcl/.quicklisp/dists/quicklisp/software/croatoan-20191227-git/test/clos.lisp
2020-02-18 14:21:14 -05:00

2878 lines
131 KiB
Common Lisp

(in-package :de.anvi.croatoan.test)
(defun snake ()
(labels ((display-snake (scr body)
(mapc #'(lambda (pair) (add-char scr (char-code #\*) :y (car pair) :x (cadr pair))) body)))
(with-screen (scr :input-echoing nil :input-blocking nil :enable-function-keys t :cursor-visible nil)
(let* ((body '((0 7) (0 6) (0 5) (0 4) (0 3) (0 2) (0 1) (0 0)))
(xpos (cadar body))
(ypos (caar body))
(dir #c(1 0)))
(clear scr)
(display-snake scr body)
(refresh scr)
(loop
(let ((event (get-event scr)))
(if event
(case event
(:up (setf dir #c( 0 1)))
(:down (setf dir #c( 0 -1)))
(:right (setf dir #c( 1 0)))
(:left (setf dir #c(-1 0)))
(#\q (return)))
(progn
(sleep 0.1)
(add-char scr (char-code #\space) :y (caar (last body)) :x (cadar (last body)))
(setf body (cons (list (incf ypos (- (imagpart dir)))
(incf xpos (realpart dir)))
(butlast body)))
(display-snake scr body)
(refresh scr)))))))))
(defun snake2 ()
"Use bind and run-event-loop for event handling. Use lists instead of complex numbers for directions."
(with-screen (scr :input-echoing nil :input-blocking nil :enable-function-keys t :cursor-visible nil)
(let* ((body '((0 7) (0 6) (0 5) (0 4) (0 3) (0 2) (0 1) (0 0)))
(head (car body))
(tail (car (last body)))
;; initial direction = right
(dir '(0 1)))
(flet ((draw-snake (win body)
(mapc (lambda (pos) (add win #\* :position pos)) body)))
(bind scr #\q 'exit-event-loop)
(bind scr :right (lambda (w e) (setq dir '( 0 1))))
(bind scr :left (lambda (w e) (setq dir '( 0 -1))))
(bind scr :down (lambda (w e) (setq dir '( 1 0))))
(bind scr :up (lambda (w e) (setq dir '(-1 0))))
(bind scr nil (lambda (w e)
;; snake moves = erase last body pair by overwriting it with space
(add scr #\space :position tail)
(setq body (cons (mapcar #'+ head dir) (butlast body)))
(setq head (car body))
(setq tail (car (last body)))
(draw-snake scr body)
(refresh scr)))
(clear scr)
(setf (frame-rate scr) 20)
(run-event-loop scr)))))
;; https://github.com/skydrome/random/blob/master/shell/screensaver.sh
;; https://github.com/pipeseroni/pipes.sh/blob/master/pipes.sh
;; https://www.youtube.com/watch?v=T4n87IIa--U
(defun pipes ()
(with-screen (scr :input-echoing nil :input-blocking nil :enable-function-keys t :cursor-visible nil)
(let* ((pos (list (round (/ (height scr) 2)) (round (/ (width scr) 2))))
(dirs '((1 0) (-1 0) (0 -1) (0 1)))
(dir (nth (random 4) dirs))
(dir-prev dir)
ch
(i 0))
(clear scr)
(box scr)
(refresh scr)
(event-case (scr event)
(#\q (return-from event-case))
((nil)
(sleep 0.1)
(move scr (car pos) (cadr pos))
(setf ch (cond ((equal dir dir-prev) (if (= (car dir) 0) :horizontal-line :vertical-line))
((and (equal dir-prev '(1 0)) (equal dir '(0 -1))) :lower-right-corner)
((and (equal dir-prev '(1 0)) (equal dir '(0 1))) :lower-left-corner)
((and (equal dir-prev '(0 1)) (equal dir '(-1 0))) :lower-right-corner)
((and (equal dir-prev '(0 1)) (equal dir '(1 0))) :upper-right-corner)
((and (equal dir-prev '(-1 0)) (equal dir '(0 -1))) :upper-right-corner)
((and (equal dir-prev '(-1 0)) (equal dir '(0 1))) :upper-left-corner)
((and (equal dir-prev '(0 -1)) (equal dir '(-1 0))) :lower-left-corner)
((and (equal dir-prev '(0 -1)) (equal dir '(1 0))) :upper-left-corner)))
(add-char scr ch)
(let ((pos-trial (list (+ (car pos) (car dir)) (+ (cadr pos) (cadr dir)))))
(if (or (= (car pos-trial) 0)
(= (cadr pos-trial) 0)
(= (car pos-trial) (1- (height scr)))
(= (cadr pos-trial) (1- (width scr))))
(setf pos (list (round (/ (height scr) 2)) (round (/ (width scr) 2))))
(setf pos pos-trial)))
(if (> i (random 5))
(progn (setf i 0)
(setf dir-prev dir)
(setf dir (nth (random 3) (remove (list (- (car dir)) (- (cadr dir))) dirs :test #'equal))))
(progn (incf i)
(setf dir-prev dir)))
(refresh scr))))))
(defun matrix ()
(with-screen (scr :input-echoing nil :input-blocking nil :cursor-visible nil)
(let* ((width (width scr))
(height (height scr))
(positions (loop repeat width collect (random height)))
(speeds (loop repeat width collect (random 4))))
(event-case (scr event)
(#\q (return-from event-case))
((nil)
(sleep 0.05)
(loop for column from 0 to (1- width) do
(loop repeat (nth column speeds) do
(setf (attributes scr) '(:bold))
(setf (color-pair scr) '(:white :black))
(add-char scr (+ 64 (random 58)) :y (mod (nth column positions) height) :x column)
(setf (color-pair scr) '(:green :black))
(add-char scr (+ 64 (random 58)) :y (mod (- (nth column positions) 1) height) :x column)
(add-char scr (+ 64 (random 58)) :y (mod (- (nth column positions) 2) height) :x column)
(setf (attributes scr) '())
(add-char scr (+ 64 (random 58)) :y (mod (- (nth column positions) 3) height) :x column)
(add-char scr (char-code #\space) :y (mod (- (nth column positions) (floor height 2)) height) :x column)
(refresh scr)
(setf (nth column positions) (mod (1+ (nth column positions)) height)))))))))
(defun matrix2 ()
(with-screen (scr :input-echoing nil :input-blocking nil :cursor-visible nil)
(let* ((width (width scr))
(height (height scr))
;; start at a random height in each column.
(positions (loop repeat width collect (random height)))
;; run each column at a random speed.
(speeds (loop repeat width collect (random 4))))
(flet ((randch () (+ 64 (random 58))))
;; hit the q key to exit the main loop.
(bind scr #\q 'exit-event-loop)
(bind scr nil
(lambda (win event)
;; generate a random ascii char
(loop for column from 0 to (1- width) do
(loop repeat (nth column speeds) do
;; position of the first point in the current column
(let ((pos (nth column positions)))
(setf (attributes win) '(:bold))
(setf (color-pair win) '(:white :black))
(add win (randch) :y (mod pos height) :x column)
(setf (color-pair win) '(:green :black))
(add win (randch) :y (mod (- pos 1) height) :x column)
(add win (randch) :y (mod (- pos 2) height) :x column)
(setf (attributes win) '())
(add win (randch) :y (mod (- pos 3) height) :x column)
;; overwrite the last char with a space
(add win #\space :y (mod (- pos (floor height 3)) height) :x column)
(refresh win)
;; increment the column positions
(setf (nth column positions) (mod (1+ pos) height)))))))))
;; after the handlers have been defined, run the main event loop at 20 fps.
(setf (frame-rate scr) 20)
(run-event-loop scr)))
(defun matrix3 ()
"Test character styles and color pair completion."
(with-screen (scr :input-echoing nil :input-blocking nil :cursor-visible nil)
(let* ((width (width scr))
(height (height scr))
(positions (loop repeat width collect (random height)))
(speeds (loop repeat width collect (random 4)))
(s1 (list :attributes '(:bold) :fgcolor :white))
(s2 (list :attributes '(:bold) :fgcolor :green :bgcolor nil))
(s3 (list :attributes nil :fgcolor :green :bgcolor :black)))
(flet ((randch () (+ 64 (random 58))))
(bind scr #\q 'exit-event-loop)
(bind scr #\r
(lambda (win event)
(setf (getf s2 :fgcolor) :red
(getf s3 :fgcolor) :red)))
(bind scr nil
(lambda (win event)
(loop for column from 0 to (1- width) do
(loop repeat (nth column speeds) do
(let ((pos (nth column positions)))
(add win (randch) :y (mod pos height) :x column :style s1)
(add win (randch) :y (mod (- pos 1) height) :x column :style s2)
(add win (randch) :y (mod (- pos 2) height) :x column :style s2)
(add win (randch) :y (mod (- pos 3) height) :x column :style s3)
(add win #\space :y (mod (- pos (floor height 3)) height) :x column :style s3)
(refresh win)
(setf (nth column positions) (mod (1+ pos) height)))))))))
(setf (frame-rate scr) 20)
(run-event-loop scr)))
(defun matrix4 ()
"Test passing colors with fgcolor without passing them as pairs."
(with-screen (scr :input-echoing nil :input-blocking nil :cursor-visible nil)
(let* ((width (width scr))
(height (height scr))
(positions (loop repeat width collect (random height)))
(speeds (loop repeat width collect (random 4))))
(flet ((randch () (+ 64 (random 58))))
(bind scr #\q 'exit-event-loop)
(bind scr nil
(lambda (win event)
(loop for column from 0 to (1- width) do
(loop repeat (nth column speeds) do
(let ((pos (nth column positions)))
(setf (attributes win) '(:bold))
(setf (fgcolor win) :green)
(add win (randch) :y (mod pos height) :fgcolor :white :x column)
(add win (randch) :y (mod (- pos 1) height) :x column)
(add win (randch) :y (mod (- pos 2) height) :x column)
(setf (attributes win) '())
(add win (randch) :y (mod (- pos 3) height) :x column)
(add win #\space :y (mod (- pos (floor height 3)) height) :x column)
(refresh win)
(setf (nth column positions) (mod (1+ pos) height)))))))))
(setf (frame-rate scr) 20)
(run-event-loop scr)))
;; initialize ncurses, deinitialize ncurses
;; tests initialize-instance
(defun t00 ()
(let ((scr (make-instance 'screen)))
(unwind-protect
nil
(close scr))))
;; clos screen, accessors, unwind-protect.
(defun t01 ()
(unwind-protect
(let ((scr (make-instance 'screen :enable-colors t)))
(clear scr)
(move scr 0 0)
(add-string scr "hello there!")
;; the text will be red on yellow.
;; this affects only new characters, not the whole window.
(setf (color-pair scr) '(:red :yellow))
(move scr 5 5)
(add-string scr "dear john!")
(refresh scr)
;; wait for keypress, works only in blocking mode, which is the default.
(get-char scr)
;; set the background character for new characters
;; the optional argument nil prevents it from being applied to every window cell.
;; a newline sets the background till the end of the line.
(setf (background scr nil) (make-instance 'complex-char :simple-char #\- :color-pair '(:white :green)))
(format scr "~%Hello again!~%")
(refresh scr)
(get-char scr)
;; finally, set the background for the whole window.
;; the change is applied only to empty cells and to
;; characters that have no already set attributes or colors.
(setf (background scr) (make-instance 'complex-char :simple-char #\. :color-pair '(:green :white)))
(refresh scr)
(get-char scr)
(setf (background scr) (make-instance 'complex-char :simple-char #\- :color-pair '(:red :white)))
(refresh scr)
;; wait for the next keypress, then end.
(get-char scr))
;; unwind protect makes sure that ncurses is ended at all cost.
(end-screen)))
;; the same as t01, but hides the window creation and ncurses ending by utilizing the with-screen macro.
(defun t02 ()
(with-screen (scr :color-pair (list :yellow :red))
(clear scr)
(move scr 0 0)
(add-string scr "hello there!")
(move scr 3 6)
(add-string scr "dear john!")
(setf (color-pair scr) '(:red :yellow))
(move scr 3 3 :relative t)
(add-string scr "call me maybe!")
;; setting the cursor position directly instead of using move
(setf (cursor-position scr) (list 9 12))
(add-string scr "welcome to tijuana")
(refresh scr)
(get-char scr)
(setf (background scr) (make-instance 'complex-char :simple-char #\. :color-pair '(:green :white)))
(refresh scr)
(get-char scr)
(setf (background scr) (make-instance 'complex-char :simple-char #\, :color-pair '(:white :green)))
(refresh scr)
(get-char scr) ))
(defun t02a ()
"Separately set the window foreground and background color pairs."
(with-screen (scr :fgcolor :yellow :bgcolor :red)
(clear scr)
(move scr 0 0)
(add-string scr "hello there!")
(setf (color-pair scr) '(:red :yellow))
(move scr 3 6)
(add-string scr "dear john!")
(setf (fgcolor scr) :yellow
(bgcolor scr) :red)
(move scr 3 3 :relative t)
(add-string scr "call me maybe!")
;; setting the cursor position directly instead of using move
(setf (cursor-position scr) (list 9 12))
(add-string scr "welcome to tijuana")
(refresh scr)
(get-char scr)
;; setting fg to green, will set the bg to the default black
(setf (background scr) (make-instance 'complex-char :simple-char #\. :fgcolor :green))
(refresh scr)
(get-char scr)
;; setting bg to green will set the fg to the default white
(setf (background scr) (make-instance 'complex-char :simple-char #\, :bgcolor :green))
;; text will still use the window color pair
(move scr 3 3 :relative t)
(add scr "hasta siempre")
(refresh scr)
(get-char scr) ))
(defun t02b ()
"Set and get a wide character background."
(with-screen (scr)
;;(de.anvi.croatoan::funcall-make-cchar_t-ptr #'%wbkgrnd (winptr scr) 0 0 0 1)
;; if we set char 0 as background, ncurses sets char 32 (space), which is obviously the default char.
;; also setting 0 leads to the color pair not being accepted.
;; TODO: check that only graphic chars are set.
(setf (background scr) (make-instance 'complex-char :simple-char #x2592 :color-pair '(:yellow :red)))
(get-char scr)
(move scr 0 0)
;; the low-level function returns the code.
(let ((ch (de.anvi.croatoan::get-background-cchar_t scr)))
(format scr "ch: ~A~%" ch)
(format scr "~A ~A ~A" (char-code (simple-char ch)) (attributes ch) (color-pair ch)))
(move scr 2 0)
;; the high level interface returns what was set by the high-level setf.
;; TODO: when we set background to :board, should it return :board or the numeric code point?
(let ((ch (background scr)))
(if ch
(format scr "~A ~A ~A" (simple-char ch) (attributes ch) (color-pair ch))
(format scr "ch: ~A" ch)))
(refresh scr)
(get-char scr)))
(defun t02c ()
(with-screen (scr :input-blocking t :input-echoing nil :enable-colors t :use-terminal-colors t)
;; simple chars added to a window without a rendered style.
(add-string scr "Hello there!")
(fresh-line scr) (refresh scr) (get-char scr)
;; color-pair applies to newly added text.
(setf (color-pair scr) '(:red :yellow))
(add-string scr "Dear John!")
(fresh-line scr) (refresh scr) (get-char scr)
;; removing the color pair puts back the outut into the default state.
(setf (color-pair scr) '())
(add-string scr "Open the pod bay door.")
(fresh-line scr) (refresh scr) (get-char scr)
;; the background style renders simple text, but it doesnt change the text with a set color pair
(setf (background scr) (make-instance 'complex-char :simple-char #\. :fgcolor :black :bgcolor :magenta))
(add-string scr "I can feel it.")
(fresh-line scr) (refresh scr) (get-char scr)
;; remove the background char, set back to the default state.
(setf (background scr) nil)
(add-string scr "My mind is going.")
(fresh-line scr) (refresh scr) (get-char scr)
;; the empty cells of the last foreground will be overwritten by the next background call.
(setf (background scr) (make-instance 'complex-char :simple-char #\_ :color-pair '(:black :yellow)))
(format scr "I'm sorry, Dave.")
(fresh-line scr) (refresh scr) (get-char scr)))
;; read and display chars until a q is pressed, blocking version.
(defun t03 ()
(with-screen (scr :input-echoing nil :input-blocking t)
(clear scr)
(add-string scr "Type chars. Type q to quit. ")
(refresh scr)
(loop for ch = (get-char scr)
while (not (equal (code-char ch) #\q))
do (add-char scr ch))
(add-string scr "You pressed q. Now press any char to quit.")
(get-char scr)))
;; read and display chars until a q is pressed, non-blocking version (leads to 100% CPU usage).
;; wait for keyboard using get-char makes no sense in non-blocking code because it doesnt wait.
(defun t03a ()
(with-screen (scr :input-echoing nil :input-blocking nil)
(clear scr)
(add-string scr "Type chars. Type q to quit. ")
(refresh scr)
(loop for ch = (get-char scr)
while (or (= ch -1) (not (equal (code-char ch) #\q)))
do (unless (= ch -1) (add-char scr ch)))))
;; test which integer is returned by ncurses on a non-blocking nil event.
;; when get-char is used, -1 is returned,
;; when get-wide-char is used, 0 is returned.
(defun t03a2 ()
(with-screen (scr :input-echoing nil :input-blocking nil)
(clear scr)
(add-string scr "Type chars. Type q to quit. ")
(refresh scr)
(loop for ch = (get-char scr)
while (not (equal ch 113)) ; 113 = q
do (princ ch scr))))
;; read and display chars until a q is pressed, non-blocking version (leads to 100% CPU usage).
;; uses get-event for event handling.
(defun t03b ()
(with-screen (scr :input-echoing nil :input-blocking nil)
(clear scr)
(add-string scr "Type chars. Type q to quit. ")
(refresh scr)
;; TODO: should get-event only return single bytes, or should we merge it with get-wide-event?
(loop (let ((event (get-event scr)))
(when event
(case event
(#\q (return))
(otherwise (add-char scr (char-code event)))))))))
;; using the event-case macro to simplify the event loop.
;; do not use ((nil) nil) with input-blocking nil, it leads to 100% CPU usage.
;; if nothing happens in the nil case anyway, we can use blocking.
(defun t03b2 ()
(with-screen (scr :input-echoing nil :input-blocking t)
(clear scr)
(add-string scr "Type chars. Type q to quit. ")
(refresh scr)
(event-case (scr event)
;; ((nil) nil)
(#\q (return-from event-case))
(otherwise (add-char scr (char-code event))))))
;; slightly improved t03b2 pasted as an example to the cliki croatoan page
(defun t03b3 ()
(with-screen (scr :input-echoing nil :input-blocking t :enable-colors t)
(clear scr)
(move scr 2 0)
(format scr "Type chars. Type q to quit.~%~%")
(refresh scr)
(setf (color-pair scr) '(:yellow :red)
(attributes scr) '(:bold))
(event-case (scr event)
(#\q (return-from event-case))
(otherwise (princ event scr)
(refresh scr)))))
;; read and display chars until a q is pressed, blocking + gray stream version.
;; the stream reading function wont work in non-blocking mode and with non-char keys.
(defun t03c ()
(with-screen (scr :input-echoing nil :input-blocking t)
(clear scr)
(princ "Type chars. Type q to quit." scr)
(refresh scr)
(loop for ch = (read-char scr)
while (not (equal ch #\q))
do (princ ch scr))
(princ "You pressed q. Now press any char to quit." scr)
(read-char scr)))
(defun t03d ()
"Read and display wide (multi-byte) characters until q is pressed."
(with-screen (scr :input-echoing nil :input-blocking t :enable-colors t :cursor-visible nil)
(clear scr)
(refresh scr)
(loop for ch = (get-wide-char scr)
while (not (equal (code-char ch) #\q))
do
(clear scr)
;; display the human-readable version of a wide char.
(add-wide-char scr ch :attributes (list :underline) :fgcolor :yellow :bgcolor :red :y 0 :x 0)
(add-wide-char scr (code-char ch) :attributes (list :bold) :color-pair (list :yellow :red) :y 0 :x 2)
;; extract the wide char again.
(let ((ch2 (extract-wide-char scr :y 0 :x 0)))
;; display the lisp-readable version of the extracted complex wide char
(move scr 1 0)
;; TODO: prin1, print and ~S should print unreadable #<..>
;; only princ and ~A should render complex chars
(prin1 (simple-char ch2) scr)
;; print the slots of the extracted complex wide char
(princ (attributes ch2) scr)
(princ (color-pair ch2) scr)
;; display the rendered complex wide char again
(add-wide-char scr ch2 :y 3 :x 0) ))))
;; gray stream version of t03d
;; we can not use ~C and write-char to write complex-chars, but it works for wide chars, which are normal lisp chars.
;; princ and ~A should work, because they rely on specialized print-object.
;; also see t08c
(defun t03d2 ()
"Use gray stream functions to read and display wide (multi-byte) characters until q is pressed."
(with-screen (scr :input-echoing nil :input-blocking t :enable-colors t :cursor-visible nil)
(clear scr)
(refresh scr)
(loop for ch = (read-char scr)
while (not (equal ch #\q))
do
(clear scr)
(move scr 0 0)
;; we only can write-char if it is a lisp character.
(write-char ch scr)
;; the extracted "char" is not a lisp character any more, but a complex-char
(let ((ch2 (extract-wide-char scr :y 0 :x 0))
(*standard-output* scr))
;; this soon will not work with sbcl and complex chars.
;; write-char only takes characters, no other objects.
;; even if we specialize stream-write-char to complex-chars
;;(move scr 1 0)
;;(write-char ch2 scr)
;; this will work because it uses print-objectm, since print-object can be
;; specialized on complex-chars.
(move scr 1 0)
(princ ch scr)
(princ ch)
(move scr 2 0)
(princ ch2 scr)
(princ ch2)
;; aestethic ~A can be used, since it uses princ underneath
;; standard ~S can not because it cant be read back in
;; character ~C can not be used because it requires characters.
(move scr 3 0)
(format scr "~A ~A" ch ch2)
(move scr 4 0)
(format t "~A ~A" ch ch2)
(refresh scr) ))))
;; | cooked | cbreak | raw
;; ----------+--------+--------+-----
;; buffering | t | nil | nil
;; ----------+--------+--------+-----
;; control | t | t | nil
;;
(defun t03e ()
"Test switching between input modes and control char processing."
(with-screen (scr :input-echoing nil :input-blocking t :input-buffering nil :process-control-chars nil)
(with-accessors ((input-buffering input-buffering-p) (process-control-chars process-control-chars-p)) scr
(clear scr)
(format scr "buffering ~A process-control-chars ~A (raw)~%" input-buffering process-control-chars)
(format scr "ch1: ~A~%" (get-char scr))
(setf (process-control-chars-p scr) t)
(format scr "buffering ~A process-control-chars ~A (cbreak)~%" input-buffering process-control-chars)
(format scr "ch2: ~A~%" (get-char scr))
(setf (input-buffering-p scr) t)
(format scr "buffering ~A process-control-chars ~A (cooked)~%" input-buffering process-control-chars)
(format scr "ch3: ~A~%" (get-char scr))
;; ignore the typed enter key
(get-char scr)
;; wait for one keypress before exiting.
(get-char scr))))
;; take a function given as symbol name and display its docstring. press q to exit.
;; Example: (a:t04 'cdr)
(defun t04 (&optional (name 'car))
(unwind-protect
(let ((scr (make-instance 'screen)))
(clear scr)
(refresh scr)
(setf (attributes scr) '(:bold :reverse))
(add-string scr "Docstring")
(setf (attributes scr) '())
(add-string scr " --> ")
(setf (attributes scr) '(:bold :underline))
(add-string scr (format nil "~A~%~%" name))
(setf (attributes scr) '())
(add-string scr (documentation name 'function))
(refresh scr)
(get-char scr))
(end-screen)))
;; adding and removing attributes.
(defun t04a ()
(unwind-protect
(let ((scr (make-instance 'screen :enable-colors t)))
(clear scr)
(refresh scr)
(add-string scr (write-to-string (attributes scr)))
(add-char scr (char-code #\newline))
(pushnew :bold (attributes scr))
(add-string scr (write-to-string (attributes scr)))
(add-char scr (char-code #\newline))
(pushnew :underline (attributes scr))
(add-string scr (write-to-string (attributes scr)))
(add-char scr (char-code #\newline))
(pushnew :reverse (attributes scr))
(add-string scr (write-to-string (attributes scr)))
(add-char scr (char-code #\newline))
(setf (attributes scr) (remove :reverse (attributes scr)))
(add-string scr (write-to-string (attributes scr)))
(add-char scr (char-code #\newline))
(setf (attributes scr) (remove :underline (attributes scr)))
(add-string scr (write-to-string (attributes scr)))
(add-char scr (char-code #\newline))
(setf (attributes scr) (remove :bold (attributes scr)))
(add-string scr (write-to-string (attributes scr)))
(add-char scr (char-code #\newline))
(refresh scr)
(get-char scr))
(end-screen)))
;; a more concise way to write t04a
(defun t04b ()
(with-screen (scr :enable-colors t :cursor-visible nil)
(clear scr)
(print (attributes scr) scr)
(add-attributes scr '(:bold))
(print (attributes scr) scr)
(add-attributes scr '(:underline))
(print (attributes scr) scr)
(add-attributes scr '(:reverse))
(print (attributes scr) scr)
(remove-attributes scr '(:bold :underline :reverse))
(print (attributes scr) scr)
(add-attributes scr '(:bold :underline :reverse))
(print (attributes scr) scr)
(setf (attributes scr) nil)
(print (attributes scr) scr)
(print "test" scr)
(move scr 6 10)
(change-attributes scr 10 '())
(refresh scr)
(get-char scr)))
;; Make sure we shut down ncurses and dont mess up the terminal when an error is signaled.
(defun t05 ()
(unwind-protect
(let ((scr (make-instance 'screen)))
(add-string scr "press any char to signal an error and go to the debugger in a messed up screen!")
(refresh scr)
(get-char scr)
(error "zu huelf!"))
;; this will be executed after we somehow return from the debugger.
(end-screen)))
;; End ncurses cleanly _before_ getting into the debugger when an error is signalled.
;; do get into the debugger, but only after we are back to the repl.
(defun t06 ()
(let ((*debugger-hook* #'(lambda (c h) (declare (ignore c)) (declare (ignore h)) (end-screen))))
(unwind-protect
(let ((scr (make-instance 'screen)))
(add-string scr "hello there! press a char to signal an error and go to the debugger without messung up the screen!")
(refresh scr)
(get-char scr)
(error "zu huelf!"))
(end-screen))))
;; The debugger hook is added now to with-screen.
;; When an error is signalled in a ncurses app, we cleanly exit ncurses first, we dont get
;; into the debugger and we merely print the signalled condition to the REPL.
(defun t06a ()
(with-screen (scr)
(add-string scr "hello there! press a char to signal an error. the app will be ended without going to the debugger.")
(refresh scr)
(get-char scr)
(error "zu huelf!")))
;; display a new window on the stadard screen.
;; problem: deleting different windows is not standardized. (problem solved, example obsolete)
(defun t07 ()
(unwind-protect
(let ((scr (make-instance 'screen :enable-colors t)))
(clear scr)
(add-string scr "Standard screen")
(refresh scr)
(get-char scr)
(let ((win (make-instance 'window :height 15 :width 50 :location '(5 5))))
(setf (background win) (make-instance 'complex-char :color-pair '(:red :blue)))
(add-string win "Window 1")
(refresh win)
(get-char win)))
;(delete-window win) ; this is missing, we have to properly delete windows manually.
;; this will be executed after we somhow return from the debugger.
(end-screen)))
;; close now closes both windows and the main screen.
;; but the creation of a window/screen now has to be outside the unwind-protect form.
(defun t07a ()
(let ((scr (make-instance 'screen :enable-colors t :cursor-visible nil)))
;; since windows are streams now, we can use close on tem too.
;; in order to be able to close scr, as compared to end-screen without arguments in t07,
;; we have to move unwind-protect inside the let scope.
(unwind-protect
(progn
(clear scr)
(add-string scr "Standard screen")
(refresh scr)
(get-char scr)
(let ((win (make-instance 'window :height 15 :width 50 :location '(5 5) :draw-border t)))
(setf (background win) (make-instance 'complex-char :simple-char #\: :color-pair '(nil :blue)))
(add-string win "Window 1" :y 2 :x 4 :fgcolor :red :bgcolor :yellow)
(refresh win)
(get-char win)
(close win))
(clear scr)
(refresh scr)
(get-char scr))
;; close is defined by the gray stream interface.
(close scr))))
;; test the gray streams interface.
(defun t08 ()
(unwind-protect
(let ((scr (make-instance 'screen)))
(clear scr)
(write-char #\a) ; writes a to the repl. wont be visible until we quit ncurses.
(write-char #\b scr) ; writes b to scr.
(terpri scr)
(princ "hello") ; writes to the repl. wont be visible until we quit ncurses.
(princ "hello" scr)
(terpri scr) ; 3 calls, 3 new lines will be added.
(terpri scr)
(terpri scr)
(princ "there" scr)
(terpri scr)
(write-string "dear john" scr :start 0 :end 4)
(terpri scr)
(write-string "dear john" scr :start 5)
(fresh-line scr) ; we call it 3 times, but only one newline will be added.
(fresh-line scr)
(fresh-line scr)
(format scr "--~%~r~%--" 1234) ; each % will add a newline.
(write-char #\newline scr) ; this is the char that actually gets displayed each time terpri is called.
(refresh scr)
(get-char scr))
(end-screen)))
;; when we define windows as streams, we can rebind the *standard-output* to print to a ncurses window.
;; this only works with standard lisp output functions, format, write-char, terpri, print, etc.
;; we still have to explicitely ncurses functions clear, close, refresh, etc. though.
(defun t08a ()
(let ((scr (make-instance 'screen)))
(unwind-protect
(progn
(clear scr)
;; writes explicitely to scr
(write-char #\a scr)
(terpri scr)
(princ "hello" scr)
(terpri scr)
(format scr "~r" 1234)
(terpri scr)
;; writes to *standard-output*, which here is scr.
(let ((*standard-output* scr))
(write-char #\b)
(terpri)
(princ "there")
(terpri)
(format t "~r" 5678)
(terpri))
(refresh scr)
(get-char scr))
(close scr))))
(defun t08b ()
"Use colors on *standard-output* with standard lisp printing functions.
Test whether a window (stream) was closed."
(let* ((scr (make-instance 'screen :enable-colors t))
(*standard-output* scr))
(unwind-protect
(progn
(clear scr)
(setf (background scr) (make-instance 'complex-char :color-pair '(:black :white)))
(format t "~r" 1984)
(refresh scr)
(get-char scr)
;; temporarily bind *standard-output* to a window.
(let* ((win (make-instance 'window :height 15 :width 50 :location '(5 5)))
(*standard-output* win))
(setf (background win) (make-instance 'complex-char :color-pair '(:white :black)))
(format t "~r" 1985)
(refresh win)
(get-char win)
(format t "~%before close: streamp: ~A open-stream-p: ~A" (streamp win) (open-stream-p win))
(refresh win)
(get-char win)
(format scr "~%after close: close: ~A streamp: ~A open-stream-p: ~A" (close win) (streamp win) (open-stream-p win))
(refresh scr)
(get-char scr))
;; *standard-output*is now again scr.
(setf (background scr) (make-instance 'complex-char :color-pair '(:black :white)))
(terpri)
(format t "~r" 1984)
(refresh scr)
(get-char scr))
(close scr))))
;; Write complex chars to standard output using standard lisp output functions + gray streams.
;; According to SBCL devs, write-char and ~C are not supposed to work, because according to the
;; standard, only lisp characters should be accepted s arguments by write-char, and any other
;; object like a complex-char should signal an error.
(defun t08c ()
(with-screen (scr)
(let ((*standard-output* scr)
(ch (make-instance 'complex-char :simple-char #\a :attributes '(:bold :underline) :color-pair '(:green :black))))
(write-char #\a)
(terpri)
;;(write-char ch)
(terpri)
(princ ch)
(terpri)
(print ch)
(terpri)
(format t "~%Format:~%~S~%~A" ch ch)
(terpri))
(refresh scr)
(get-char scr)))
;; box, move
(defun t09 ()
(let* ((scr (make-instance 'screen :enable-colors t)))
(unwind-protect
(progn
(clear scr)
(setf (background scr) (make-instance 'complex-char :color-pair '(:black :white)))
(box scr)
(move scr 1 1)
(princ 0 scr)
(refresh scr)
(let ((w1 (make-instance 'window :height 10 :width 30 :location '(3 5)))
(w2 (make-instance 'window :height 10 :width 30 :location '(6 10)))
(w3 (make-instance 'window :height 10 :width 30 :location '(9 15))))
(setf (background w1) (make-instance 'complex-char :color-pair '(:white :black)))
(setf (background w2) (make-instance 'complex-char :color-pair '(:black :white)))
(setf (background w3) (make-instance 'complex-char :color-pair '(:white :black)))
(box w1)
(box w2)
(box w3)
(move w1 1 1)
(princ 1 w1)
(move w2 1 1)
(princ 2 w2)
(move w3 1 1)
(princ 3 w3)
(refresh w1)
(refresh w2)
(refresh w3)
(get-char w1)
;; todo: before we can refresh w2 to raise it, we have to "touch" it.
;; if we dont touch it, only the changed parts of it will be redrawn.
(touch w2)
(refresh w2)
(get-char w1)
;; move the whole window 3. note that this doesnt refresh the windows below,
;; they have to be refreshed separately.
(setf (location w3) '(9 20))
(refresh w3)
(get-char w3)
(close w1)
(close w2)
(close w3))
(setf (background scr) (make-instance 'complex-char :color-pair '(:black :white)))
(refresh scr)
(get-char scr))
(close scr))))
;; the same as t09, but we can now raise the overlapping windows by hitting 1, 2 or 3.
(defun t09a ()
(let* ((scr (make-instance 'screen :enable-colors t :input-blocking t :input-echoing nil ;;:use-terminal-colors t
:cursor-visible nil)))
(unwind-protect
(progn
(clear scr)
(box scr)
(refresh scr)
;; the default value for width or height is "to the end of the screen".
(let ((w1 (make-instance 'window :height 10 :width 30 :location '(3 5)))
(w2 (make-instance 'window :width 30 :location '(6 10)))
(w3 (make-instance 'window :height 10 :location '(9 15))))
(box w1)
(box w2)
(box w3)
;; after we define a default pair, :terminal -1 refers to these colors.
(setf (use-terminal-colors-p scr) t)
;;(setf (default-color-pair scr) (list :yellow :red))
(setf (background w1) (make-instance 'complex-char :color-pair '(:default-fg :yellow)))
;; window w2 uses the :default fg and bg color of the terminal, because use-terminal-colors is t.
(setf (background w3) (make-instance 'complex-char :color-pair '(:yellow :terminal)))
;; print currently active color pairs to w3
(format w3 "~A" de.anvi.croatoan::*color-pair-alist*)
;; TODO: clear, refresh, etc, should take one or more windows as arguments.
;; so we can do (refresh w1 w2 w3) instead of:
(refresh w1)
(refresh w2)
(refresh w3)
(loop (let ((event (get-event scr)))
(when event
(case event
(#\1 (touch w1) (refresh w1))
(#\2 (touch w2) (refresh w2))
(#\3 (touch w3) (refresh w3))
(#\q (return))
(otherwise nil)))))
(close w1)
(close w2)
(close w3)))
(close scr))))
(defun t09b ()
"Use with-screen, event-case and mapc to simplify t09a."
(with-screen (scr :input-blocking t :input-echoing nil :enable-colors t :cursor-visible nil)
(box scr)
(refresh scr)
(let ((w1 (make-instance 'window :height 10 :width 30 :location '(3 5) :draw-border t))
(w2 (make-instance 'window :width 30 :location '(6 10) :draw-border t))
(w3 (make-instance 'window :height 10 :location '(9 15) :draw-border t)))
(setf (background w1) (make-instance 'complex-char :simple-char #\space :color-pair '(:white :black))
(background w3) (make-instance 'complex-char :simple-char #\space :color-pair '(:white :black)))
(mapc #'refresh (list w1 w2 w3))
(event-case (scr event)
(#\1 (touch w1) (refresh w1))
(#\2 (touch w2) (refresh w2))
(#\3 (touch w3) (refresh w3))
(#\q (return-from event-case))
(otherwise nil))
(mapc #'close (list w1 w2 w3))
;; return nil explicitely, so it doesnt return the window list.
nil)))
;; https://www.gnu.org/software/guile-ncurses/manual/html_node/Panels-Basics.html#Panels-Basics
;; https://www.gnu.org/software/guile-ncurses/manual/html_node/The-curses-panel-library.html
(defun t09c ()
"Use a window stack to manage overlapping windows."
(with-screen (scr :input-blocking t :input-echoing nil :enable-colors t :enable-function-keys t :cursor-visible nil :stacked t)
(box scr)
(setf (background scr) (make-instance 'complex-char :simple-char #\space :color-pair '(:black :white)))
;; we have to stack scr because the event loop runs on scr, and this refreshes scr implicitely every time
;; and overlaps the other windows
;;(setf (stackedp scr) t)
(let ((winlst nil)
(n 0))
;; create 8 windows (with 8 different background colors),
;; add them to the local winlist and to the global stack
(loop for i from 0 to 7 do
(push (make-instance 'window :height 10 :width 30 :location (list (+ 3 (* i 1)) (+ 3 (* i 3))) :draw-border t :stacked t)
winlst))
;; number them and set the background colors
(loop for i from 0 to 7 do
(setf (attributes (nth i winlst)) (list :reverse))
(format (nth i winlst) "~A" i)
(setf (background (nth i winlst))
(make-instance 'complex-char :simple-char #\space :color-pair (list :black (nth i *ansi-color-list*)))))
;;(setf (stackedp (nth i winlst)) t)
(refresh-stack)
(event-case (scr event)
;; type 0-7 to pick a window
((#\0 #\1 #\2 #\3 #\4 #\5 #\6 #\7)
;; show the picked window number in the upper right screen corner
(add-char scr event :y 1 :x 78 :color-pair (list :white :black))
(setf n (- (char-code event) 48)))
;; to raise a window to the top of the stack
(#\t
(raise-to-top (nth n winlst))
(refresh-stack))
(#\r
(raise (nth n winlst))
(refresh-stack))
(#\l
(lower (nth n winlst))
(refresh-stack))
(#\b
(lower-to-bottom (nth n winlst))
(refresh-stack))
;; type v to toggle window visibility
(#\v
;; toggle visibility for window n
(setf (visiblep (nth n winlst)) (not (visiblep (nth n winlst))))
(refresh-stack))
(#\q (return-from event-case))
(otherwise nil))
;; before closing them, remove all windows from the stack, so they can be GCd.
(empty-stack)
(mapc #'close winlst)
;; return nil explicitely, so it doesnt return the window list.
nil)))
;; port of kletva/test05.
;; print the screen size.
;; print the keys and key codes of any key pressed.
;; TODO: when we reach the end of the screen, the ~% doesnt print newlines any more and the screen doesnt scroll.
;; see scroll.lisp. a lot of window options, including scrolling, arent closified yet.
;; enable-function-keys t: fkeys have codes 255+
;; enable-function-keys nil: fkeys are multi-char escape codes.
(defun t10 ()
(let ((scr (make-instance 'screen :input-echoing nil :enable-function-keys t)))
(unwind-protect
(progn
(clear scr)
(format scr "~A lines high, ~A columns wide.~%~%" (height scr) (width scr))
(refresh scr)
(loop
;; act only on events, do nothing on non-events.
(when (key-pressed-p scr)
(let ((ch (get-char scr)))
;; quit on q, print everything else including function keys.
(cond ((equal (code-char ch) #\q) (return))
(t (format scr "Code: ~A, Char: ~A~%" ch (code-char ch))))))))
(close scr))))
;; cleaner key printing.
;; prints key names instead of numeric char codes.
(defun t10a ()
(with-screen (scr :input-echoing nil :input-blocking t :enable-function-keys t
:input-buffering nil :process-control-chars nil :enable-newline-translation t)
(setf (scrolling-enabled-p scr) t)
(format scr "~A lines high, ~A columns wide.~%~%" (height scr) (width scr))
(loop (let ((event (get-event scr)))
(when event
(case event
(#\q (return))
;; non-printable ascii chars. (#\space and #\newline are standard, all others non-standard)
(#\escape (format scr "escape char ESC ^[ \e~%"))
(#\tab (format scr "horizontal tab char HT \t~%"))
(#\space (format scr "space char~%"))
;; NL can be either LF \n,CR \r,or CRLF \r\n, depending on the system. it is LF on ubuntu.
;; NL is the standard, system independent, portable way.
(#\linefeed (format scr "enter/linefeed LF ^J \n char~%"))
(#\return (format scr "enter/return CR ^M \r char~%"))
(#\newline (format scr "enter/newline char~%")) ; = #\linefeed, code 10.
;; use %nonl to prevent ^M (CR) from automatically being translated to ^J (LF)
(#\n (setf (newline-translation-enabled-p scr)
(not (newline-translation-enabled-p scr)))
(if (newline-translation-enabled-p scr)
(format scr "%nl newline translation enabled: RET => NL (= LF)~%")
(format scr "%nonl newline translation disabled: RET => CR~%")))
(#\rubout (format scr "rubout char ^?~%")) ;; DEL, ^?, delete char 127
(#\backspace (format scr "backspace char ^H~%")) ;; BS, \b, ^H, code 8, not the same as the :backspace key
(:backspace (format scr "backspace key <--~%"))
(otherwise (format scr "Event: ~A~%" event))))))))
;; demonstrate scrolling and scrolling regions.
(defun t11 ()
(let ((scr (make-instance 'screen)))
(unwind-protect
(progn
(clear scr)
(refresh scr)
;; (move scr 5 5)
(setf (cursor-position scr) '(5 5))
;; see inopts.
(setf
;; this is sufficient to make the whole window scroll.
;;(scrolling-enabled-p scr t)
;;(%scrollok (winptr scr) t)
(scrolling-enabled-p scr) t
;; to make only a few lines (5 to 10) scroll, we have to set a line-based region.
;;(set-scrolling-region scr 5 10)
;;(%wsetscrreg (winptr scr) 5 10)
(scrolling-region scr) '(5 10))
(loop for i from 0 to 30 do
(format scr "~A~%" i)
(refresh scr)
(sleep 0.2)))
(close scr))))
;; Display available ACS (alternative character set) pseudo-graphical characters.
(defun t12 ()
(let ((scr (make-instance 'screen)))
(unwind-protect
(progn
(add-string scr "ACS_ULCORNER ") (add-char scr (acs :upper-left-corner )) (add-string scr " upper left corner") (new-line scr)
(add-string scr "ACS_LLCORNER ") (add-char scr (acs :lower-left-corner )) (add-string scr " lower left corner ") (new-line scr)
(add-string scr "ACS_URCORNER ") (add-char scr (acs :upper-right-corner )) (add-string scr " upper right corner ") (new-line scr)
(add-string scr "ACS_LRCORNER ") (add-char scr (acs :lower-right-corner )) (add-string scr " lower right corner ") (new-line scr)
(add-string scr "ACS_LTEE ") (add-char scr (acs :tee-pointing-right )) (add-string scr " tee pointing right ") (new-line scr)
(add-string scr "ACS_RTEE ") (add-char scr (acs :tee-pointing-left )) (add-string scr " tee pointing left ") (new-line scr)
(add-string scr "ACS_BTEE ") (add-char scr (acs :tee-pointing-up )) (add-string scr " tee pointing up ") (new-line scr)
(add-string scr "ACS_TTEE ") (add-char scr (acs :tee-pointing-down )) (add-string scr " tee pointing down ") (new-line scr)
(add-string scr "ACS_HLINE ") (add-char scr (acs :horizontal-line )) (add-string scr " horizontal line ") (new-line scr)
(add-string scr "ACS_VLINE ") (add-char scr (acs :vertical-line )) (add-string scr " vertical line ") (new-line scr)
(add-string scr "ACS_PLUS ") (add-char scr (acs :crossover-plus )) (add-string scr " large plus or crossover ") (new-line scr)
(add-string scr "ACS_S1 ") (add-char scr (acs :scan-line-1 )) (add-string scr " scan line 1 ") (new-line scr)
(add-string scr "ACS_S3 ") (add-char scr (acs :scan-line-3 )) (add-string scr " scan line 3 ") (new-line scr)
(add-string scr "ACS_S7 ") (add-char scr (acs :scan-line-7 )) (add-string scr " scan line 7 ") (new-line scr)
(add-string scr "ACS_S9 ") (add-char scr (acs :scan-line-9 )) (add-string scr " scan line 9 ") (new-line scr)
(add-string scr "ACS_DIAMOND ") (add-char scr (acs :diamond-symbol )) (add-string scr " diamond ") (new-line scr)
(add-string scr "ACS_BOARD ") (add-char scr (acs :board )) (add-string scr " board of squares ") (new-line scr)
(add-string scr "ACS_CKBOARD ") (add-char scr (acs :checker-board )) (add-string scr " checker board (stipple) ") (new-line scr)
(add-string scr "ACS_DEGREE ") (add-char scr (acs :degree-symbol )) (add-string scr " degree symbol ") (new-line scr)
(add-string scr "ACS_PLMINUS ") (add-char scr (acs :plus-minus )) (add-string scr " plus/minus ") (new-line scr)
(add-string scr "ACS_BULLET ") (add-char scr (acs :bullet-symbol )) (add-string scr " bullet ") (new-line scr)
(add-string scr "ACS_LARROW ") (add-char scr (acs :arrow-pointing-left )) (add-string scr " arrow pointing left ") (new-line scr)
(add-string scr "ACS_RARROW ") (add-char scr (acs :arrow-pointing-right )) (add-string scr " arrow pointing right ") (new-line scr)
(add-string scr "ACS_DARROW ") (add-char scr (acs :arrow-pointing-down )) (add-string scr " arrow pointing down ") (new-line scr)
(add-string scr "ACS_UARROW ") (add-char scr (acs :arrow-pointing-up )) (add-string scr " arrow pointing up ") (new-line scr)
(add-string scr "ACS_LANTERN ") (add-char scr (acs :lantern-symbol )) (add-string scr " lantern symbol ") (new-line scr)
(add-string scr "ACS_BLOCK ") (add-char scr (acs :solid-square-block )) (add-string scr " solid square block ") (new-line scr)
(add-string scr "ACS_LEQUAL ") (add-char scr (acs :less-than-or-equal )) (add-string scr " less/equal ") (new-line scr)
(add-string scr "ACS_GEQUAL ") (add-char scr (acs :greater-than-or-equal)) (add-string scr " greater/equal ") (new-line scr)
(add-string scr "ACS_PI ") (add-char scr (acs :pi )) (add-string scr " Pi ") (new-line scr)
(add-string scr "ACS_NEQUAL ") (add-char scr (acs :not-equal )) (add-string scr " not equal ") (new-line scr)
(add-string scr "ACS_STERLING ") (add-char scr (acs :uk-pound-sterling )) (add-string scr " UK pound sign") (new-line scr)
(get-char scr))
(close scr))))
;; after adding gray streams and adding acs to add-char.
;; http://melvilletheatre.com/articles/ncurses-extended-characters/index.html
(defun t12a ()
(with-screen (scr)
(add-char scr :upper-left-corner ) (format scr " ACS_ULCORNER / upper left corner ~%")
(add-char scr :lower-left-corner ) (format scr " ACS_LLCORNER / lower left corner ~%")
(add-char scr :upper-right-corner ) (format scr " ACS_URCORNER / upper right corner ~%")
(add-char scr :lower-right-corner ) (format scr " ACS_LRCORNER / lower right corner ~%")
(add-char scr :tee-pointing-right ) (format scr " ACS_LTEE / tee pointing right ~%")
(add-char scr :tee-pointing-left ) (format scr " ACS_RTEE / tee pointing left ~%")
(add-char scr :tee-pointing-up ) (format scr " ACS_BTEE / tee pointing up ~%")
(add-char scr :tee-pointing-down ) (format scr " ACS_TTEE / tee pointing down ~%")
(add-char scr :horizontal-line ) (format scr " ACS_HLINE / horizontal line ~%")
(add-char scr :vertical-line ) (format scr " ACS_VLINE / vertical line ~%")
(add-char scr :crossover-plus ) (format scr " ACS_PLUS / large plus or crossover~%")
(add-char scr :scan-line-1 ) (format scr " ACS_S1 / scan line 1 ~%")
(add-char scr :scan-line-3 ) (format scr " ACS_S3 / scan line 3 ~%")
(add-char scr :scan-line-7 ) (format scr " ACS_S7 / scan line 7 ~%")
(add-char scr :scan-line-9 ) (format scr " ACS_S9 / scan line 9 ~%")
(add-char scr :diamond-symbol ) (format scr " ACS_DIAMOND / diamond ~%")
(add-char scr :board ) (format scr " ACS_BOARD / board of squares ~%")
(add-char scr :checker-board ) (format scr " ACS_CKBOARD / checker board (stipple)~%")
(add-char scr :degree-symbol ) (format scr " ACS_DEGREE / degree symbol ~%")
(add-char scr :plus-minus ) (format scr " ACS_PLMINUS / plus/minus ~%")
(add-char scr :bullet-symbol ) (format scr " ACS_BULLET / bullet ~%")
(add-char scr :arrow-pointing-left ) (format scr " ACS_LARROW / arrow pointing left ~%")
(add-char scr :arrow-pointing-right ) (format scr " ACS_RARROW / arrow pointing right ~%")
(add-char scr :arrow-pointing-down ) (format scr " ACS_DARROW / arrow pointing down ~%")
(add-char scr :arrow-pointing-up ) (format scr " ACS_UARROW / arrow pointing up ~%")
(add-char scr :lantern-symbol ) (format scr " ACS_LANTERN / lantern symbol ~%")
(add-char scr :solid-square-block ) (format scr " ACS_BLOCK / solid square block ~%")
(add-char scr :less-than-or-equal ) (format scr " ACS_LEQUAL / less/equal ~%")
(add-char scr :greater-than-or-equal) (format scr " ACS_GEQUAL / greater/equal ~%")
(add-char scr :pi ) (format scr " ACS_PI / Pi ~%")
(add-char scr :not-equal ) (format scr " ACS_NEQUAL / not equal ~%")
(add-char scr :uk-pound-sterling ) (format scr " ACS_STERLING / UK pound sign ~%")
(refresh scr)
(get-char scr)))
;; Smaller ACS example to explain how the ACS system works.
(defun t12b ()
(with-screen (scr)
;; acs takes a keyword :ulcorner, translates it to a char #\l,
;; ncurses translates it at run-time to a terminal-dependent
;; integer value that doesnt correspond to a char code.
(format scr "~A ~A ~A " #\l (char-code #\l) (acs :upper-left-corner))
;; We cant output it in a string, but have to use add-char.
(add-char scr 4194412)
(terpri scr)
(refresh scr)
(get-char scr)))
;; Show the longer symbol names next to the ACS chars in a more readable table view.
(defun t12c ()
(with-screen (scr)
(loop for i from 0 for symbol in
'(:upper-left-corner
:lower-left-corner
:upper-right-corner
:lower-right-corner
:tee-pointing-right
:tee-pointing-left
:tee-pointing-up
:tee-pointing-down
:horizontal-line
:vertical-line)
do (progn (move scr (+ 2 (* i 2)) 2)
(add-char scr symbol)
(format scr " ~S~%~%" symbol)))
(loop for i from 0 for symbol in
'(:crossover-plus
:scan-line-1
:scan-line-3
:scan-line-7
:scan-line-9
:diamond-symbol
:board
:checker-board
:degree-symbol
:plus-minus
:bullet-symbol)
do (progn (move scr (+ 2 (* i 2)) 30)
(add-char scr symbol)
(format scr " ~S~%~%" symbol)))
(loop for i from 0 for symbol in
'(:arrow-pointing-left
:arrow-pointing-right
:arrow-pointing-down
:arrow-pointing-up
:lantern-symbol
:solid-square-block
:less-than-or-equal
:greater-than-or-equal
:pi
:not-equal
:uk-pound-sterling)
do (progn (move scr (+ 2 (* i 2)) 55)
(add-char scr symbol)
(format scr " ~S~%~%" symbol)))
(refresh scr)
(get-char scr)))
(defun t12c2 ()
"ACS chars not available on all terminals: thick and double line drawing characters.
Tested with xterm, gnome-terminal, st. Doesn't work in the linux console, aterm, eterm."
(with-screen (scr)
(loop for i from 0 for symbol in
'(:thick-upper-left-corner
:thick-lower-left-corner
:thick-upper-right-corner
:thick-lower-right-corner
:thick-tee-pointing-right
:thick-tee-pointing-left
:thick-tee-pointing-up
:thick-tee-pointing-down
:thick-horizontal-line
:thick-vertical-line
:thick-crossover-plus)
do (progn (move scr (+ 1 (* i 2)) 2)
(add-char scr symbol)
(format scr " ~S~%~%" symbol)))
(loop for i from 0 for symbol in
'(:double-upper-left-corner
:double-lower-left-corner
:double-upper-right-corner
:double-lower-right-corner
:double-tee-pointing-right
:double-tee-pointing-left
:double-tee-pointing-up
:double-tee-pointing-down
:double-horizontal-line
:double-vertical-line
:double-crossover-plus)
do (progn (move scr (+ 1 (* i 2)) 40)
(add-char scr symbol)
(format scr " ~S~%~%" symbol)))
(refresh scr)
(get-char scr)))
(defun t12d ()
"Print all graphic alternative chars supported by the non-wide acs_map[128] array.
Only the chars 33 to 126 are graphic, and not all are accessible through the
keywords provided by ncurses, and the supported chars are terminal dependent."
(with-screen (scr)
(let* ((ptr (foreign-symbol-pointer "acs_map"))
(code (mem-aref ptr :unsigned-int (char-code #\l))))
(format scr "~A ~A ~A ~A " #\l (char-code #\l) (acs :upper-left-corner) code)
;; l 108 4194412 4194412 ***
;; 4194412 is nothing more than 108 with the A_ALTCHARSET bit turned on.
(add-char scr code)
(add-char scr (acs :upper-left-corner))
(add-char scr #\l :attributes (list :altcharset))
(terpri scr)
;; so we do not really need to access acs_map, like acs is doing.
;; we just need to translate chars like #\l to :upper-left-corner
(loop for i from 33 to 126 do
(add-char scr (code-char i))
(add-char scr #\space))
(terpri scr)
(loop for i from 33 to 126 do
(add-char scr (mem-aref ptr :unsigned-int i))
(add-char scr #\space))
(refresh scr)
(get-char scr))))
;; Demonstrate flash and beep alerts.
;; It depends on the terminal emulator whether they will work for you.
;; They both worked in xterm for me.
(defun t13 ()
(with-screen (scr :input-echoing nil :input-blocking nil :enable-function-keys t :cursor-visible nil)
(loop
(let ((event (get-event scr)))
(if event
(case event
(#\b (alert :beep))
(#\f (alert :flash))
(#\q (return)))
(sleep 0.1))))))
;; minimal setting to get the mouse working.
;; reads and prints a single mouse event.
(defun t14 ()
(let ((scr (make-instance 'screen :input-echoing nil :input-blocking t :enable-function-keys t)))
(unwind-protect
(progn
(%mousemask #b00000111111111111111111111111111 (null-pointer)) ; activate all mouse events.
(get-char scr) ; here you have to click to generate a mouse event.
(with-foreign-object (me '(:struct mevent)) ; create a pointer to the struct mevent.
(%getmouse me) ; save the mouse event struct to the pointed position.
(princ (mem-ref me '(:struct mevent)) scr) ; dereference the pointer, return a plist of the struct.
(get-char scr)))
(close scr))))
;; (cffi:convert-to-foreign '(id 1 x 1 y 2 z 3 bstate 2) '(:struct mevent))
;; (setf ev (convert-from-foreign (mem-ref bstate '(:struct mevent)) '(:struct mevent)))
;; mouse events are now detected in the event loop.
;; print the y x coordinates and the detected event.
(defun t14a ()
(with-screen (scr :input-echoing nil :input-blocking t :enable-function-keys t :cursor-visible nil)
(set-mouse-event '(:button-1-clicked :button-2-clicked :button-3-clicked))
(event-case (scr event y x)
((:button-1-clicked :button-2-clicked :button-3-clicked) (format scr "~3A ~3A ~A~%" y x event))
(#\q (return-from event-case)))))
;; left click prints a 1, right click prints a 3.
(defun t14b ()
(with-screen (scr :input-echoing nil :input-blocking t :enable-function-keys t :cursor-visible nil)
(set-mouse-event '(:button-1-clicked :button-3-clicked))
(event-case (scr event mouse-y mouse-x)
(:button-1-clicked (move scr mouse-y mouse-x) (princ "1" scr))
(:button-3-clicked (move scr mouse-y mouse-x) (princ "3" scr))
(#\q (return-from event-case)))))
(defun t14c ()
"Print all mouse events."
(with-screen (scr :input-echoing nil :input-blocking t :enable-function-keys t :cursor-visible nil)
(%mousemask #b00000111111111111111111111111111 (null-pointer))
(event-case (scr event y x)
(#\q (return-from event-case))
(t (format scr "~3A ~3A ~A~%" y x event)) )))
;; resize event: the standard screen size is resized automatically.
(defun t15 ()
(with-screen (scr :input-echoing nil :input-blocking nil :enable-function-keys t :cursor-visible nil :enable-colors t)
(add-string scr "Current standard screen geometry (Y x X):" :y 0 :x 0)
(loop
(let ((event (get-event scr)))
(if event
(case event
(:resize (move scr 1 0)
(format scr "~A Y lines x ~A X columns." (height scr) (width scr))
;; the environment variables get updated on a resize event too.
(format scr "~%~A Y lines x ~A X cols." %LINES %COLS)
(refresh scr))
(#\q (return)))
(progn
(sleep 0.1)))))))
;; resize event: arrange window _locations_ relative to the screen size.
(defun t15a ()
(with-screen (scr :input-echoing nil :input-blocking nil :enable-function-keys t :cursor-visible nil :enable-colors t)
(add-string scr "Current standard screen geometry (Y x X):" :y 0 :x 0)
(setf (background scr) (make-instance 'complex-char :simple-char #\. :color-pair '(:green :white)))
(let ((time 0)
;; place a window in the center of the screen.
(win (make-instance 'window :height 5 :width 10 :location (list (round (/ (height scr) 2))
(round (/ (width scr) 2))))))
(loop
(let ((event (get-event scr)))
(if event
(case event
(:resize ;; if the scren is resized, relocate the window to the new center.
(move-window win (round (/ (height scr) 2)) (round (/ (width scr) 2)))
;; better differentiation of types with methods.
(move win 0 0)
(format win "Y:~A X:~A" (height scr) (width scr))
;; repaint all windows completely by touching them before refreshing.
;; overlapping windows have to be refreshed in reverse stacking order.
(mapc #'(lambda (w) (touch w) (refresh w))
(list scr win)))
(#\q (return)))
(progn
(sleep 0.01)
(move win 1 0)
(incf time 0.01)
(format win "~A" time)
(mapc #'(lambda (w) (touch w) (refresh w))
(list scr win)) ))))
(close win))))
;; resize event: arrange window _sizes_ relative to the screen size.
(defun t15b ()
(with-screen (scr :input-echoing nil :input-blocking nil :enable-function-keys t :cursor-visible nil :enable-colors t)
(add-string scr "Current standard screen geometry (Y x X):" :y 0 :x 0)
(setf (background scr) (make-instance 'complex-char :color-pair '(:black :white)))
(let ((time 0)
;; make the window slightly smaller than the standard screen.
(win (make-instance 'window :height (- (height scr) 4) :width (- (width scr) 6) :location '(2 3))))
(loop
(let ((event (get-event scr)))
(if event
(case event
(:resize ;; resize the window on every termina resize.
(resize win (- (height scr) 4) (- (width scr) 6))
(move win 0 0)
(format win "Y:~A X:~A" (height scr) (width scr))
;; when updating several overlapping windows, using mark-for-refresh and
;; batch-refresh instead of several calls to refresh prevents flickering.
(mapc #'(lambda (w) (touch w) (mark-for-refresh w)) (list scr win))
(refresh-marked))
(#\q (return)))
(progn
(sleep 0.01)
(move win 1 0)
(incf time 0.01)
(format win "~A" time)
(mapc #'(lambda (w) (touch w) (mark-for-refresh w)) (list scr win))
(refresh-marked) ))))
(close win))))
;; test get-string: read a string, then output it.
;; the only function key we can use during input is backspace.
(defun t16 ()
(unwind-protect
(let ((scr (make-instance 'screen)))
(clear scr)
(move scr 0 0)
(add-string scr "Type your name: ")
(refresh scr)
(let ((str (get-string scr 10)))
(add-string scr "Your name is: ")
(add-string scr str))
(refresh scr)
(get-char scr))
(end-screen)))
;; read a single line of Lisp input (30 chars max) from the last line,
;; evaluate it and print the result to the output window above.
(defun t16a ()
(with-screen (scr :input-echoing t :input-blocking t :enable-function-keys t :cursor-visible nil :enable-colors nil)
(let ((out (make-instance 'window :height (1- (height scr)) :width (width scr) :location '(0 0)))
(in (make-instance 'window :height 1 :width (width scr) :location (list (1- (height scr)) 0))))
(print (eval (read-from-string (get-string in 30))) out)
(refresh out)
;; blocking is t, so wait till the next keypress before exiting.
(get-char in)
;; close windows and window streams.
(close in)
(close out))))
;; add a loop to the input, making it a simple repl.
(defun t16b ()
(with-screen (scr :input-echoing t :input-blocking t :enable-function-keys t :cursor-visible t :enable-colors nil)
(let ((out (make-instance 'window :height (1- (height scr)) :width (width scr) :location '(0 0)))
(in (make-instance 'window :height 1 :width (width scr) :location (list (1- (height scr)) 0))))
(loop
(let ((str (get-string in 30)))
;; if the input line is empty (length = 0), do nothing.
(when (> (length str) 0)
;; a single blocking q exits the loop.
(when (string= str "q") (return))
;; after the input is read, clear the input line.
(clear in)
(print (eval (read-from-string str)) out)
(refresh out))))
(close in)
(close out))))
;; ncurses' get-string only allows the backspace key.
;; extend the functionality of the input line of the simple repl.
(defun t16c ()
(with-screen (scr :input-echoing nil :cursor-visible t :enable-colors t)
(let* ((wout (make-instance 'window :height (1- (height scr)) :width (width scr) :location '(0 0) :enable-scrolling t))
;; input blocking is a property of every single window, not just of the global screen.
(win (make-instance 'window :height 1 :width (width scr) :location (list (1- (height scr)) 0)
:enable-function-keys t :input-blocking t))
(*standard-output* wout)
(n 0)) ; no of chars in the input line.
(event-case (win event)
(:left
(when (> (cadr (cursor-position win)) 0)
(move win 0 -1 :relative t)))
(:right
(when (< (cadr (cursor-position win)) n)
(move win 0 1 :relative t)))
(#\newline ; RET key, C-j, C-m
(when (> n 0) ; only print when the line is not empty.
(let* ((strin (extract-wide-string win :n n :y 0 :x 0))
(strout (eval (read-from-string strin))))
(print (length strin))
(princ strin)
(terpri)
(add-string wout (format nil "=> ~A~%~%" strout)))
(setf n 0)
(clear win) ; empty the input line after evaluation.
(refresh wout)))
(:dc ; DEL key
(when (> n (cadr (cursor-position win)))
(decf n)
(delete-char win)))
(:ic ; INS / Einfg key
(format t "(insert-mode-p win) => ~A~%" (insert-mode-p win))
(setf (insert-mode-p win) (not (insert-mode-p win)))
(format t "(insert-mode-p win) => ~A~%" (insert-mode-p win))
(refresh wout))
(:backspace ; BS key
(when (> (cadr (cursor-position win)) 0)
(decf n)
(move win 0 -1 :relative t)
(delete-char win)))
(#\q (return-from event-case))
((nil) ; when no key is hit at all
;; when there is no event, get-event will return nil.
;; this is the place for no-event code.
;; instead of doing nothing, set blocking to t.
nil)
;; non-function keys, i.e. normal character keys
(otherwise
(when (and (characterp event)
(< (cadr (cursor-position win)) (1- (width win))))
(incf n)
;; insert-mode-p does not insert if we do not use gray stream functions
;;(add-wide-char win event))))
(write-char event win)))) ; calls stream-write-char
;; (princ event win) ; calls print-object
(close win)
(close wout))))
;; buffer: (3 2 1)
;; screen: 123
;; in the buffer, elements are added to the left and counted from the left.
;; on the screen, the list is displayed in reverse.
(defun t16d ()
"Use an input buffer instead of extracting the string from the window. Create windows using the with-windows macro."
(with-screen (scr :input-echoing nil :cursor-visible t :enable-colors t)
(with-windows ((wout :height (1- (height scr)) :width (width scr) :location '(0 0) :enable-scrolling t)
(win :height 1 :width (width scr) :location (list (1- (height scr)) 0) :enable-function-keys t :input-blocking t))
(let ((*standard-output* wout)
(inbuf nil) ; input buffer character list
(inptr 0)) ; position of the next character in the buffer
(event-case (win event)
(#\q (return-from event-case))
(:left
(when (> inptr 0) (decf inptr))
(move win 0 inptr))
(:right
(when (< inptr (length inbuf)) (incf inptr))
(move win 0 inptr))
(#\newline
(when (> (length inbuf) 0)
(format t "~A~%" (coerce (reverse inbuf) 'string)) (refresh wout)
(setf inbuf nil inptr 0)
(clear win)
(move win 0 inptr)
(refresh win)))
(:dc
(when (> (length inbuf) inptr)
(setf inbuf (remove-nth (- (length inbuf) (1+ inptr)) inbuf))
(clear win)
(add-string win (coerce (reverse inbuf) 'string))
(move win 0 inptr)
(refresh win)))
(:backspace
(when (> inptr 0)
(decf inptr)
(setf inbuf (remove-nth (- (length inbuf) 1 inptr) inbuf))
(clear win)
(add-string win (coerce (reverse inbuf) 'string))
(move win 0 inptr)
(refresh win)))
(:ic
(format t "(insert-mode-p win) => ~A~%" (insert-mode-p win))
(setf (insert-mode-p win) (not (insert-mode-p win)))
(format t "(insert-mode-p win) => ~A~%" (insert-mode-p win))
(refresh wout))
(otherwise
(if (= inptr (length inbuf))
(setf inbuf (cons event inbuf))
(if (insert-mode-p win)
(setf inbuf (insert-nth (- (length inbuf) inptr) event inbuf))
(setf inbuf (replace-nth (- (length inbuf) (1+ inptr)) event inbuf))))
(incf inptr)
(clear win)
(add-string win (coerce (reverse inbuf) 'string))
(move win 0 inptr)
(refresh win)))))))
(defun t16e ()
"Edit a single input field, not part of a form."
(with-screen (scr :input-echoing nil :cursor-visible t :enable-colors t :enable-function-keys t :input-blocking t)
(let ((*standard-output* scr)
(s1 (list :attributes '(:underline)))
(s2 (list :simple-char #\.))
(field (make-instance 'field :location (list 3 20) :width 10 :window scr)))
(setf (style field) (list :foreground s1 :background s2))
(bind field #\newline 'de.anvi.croatoan::debug-print-field-buffer)
;; pressing ^A (for "accept") exits the edit mode (for now)
;; the field variables are not reset when edit is exited, we can access them after the edit.
(if (edit field)
;; accept returns t
(progn
(clear scr)
;; display the contents of the input buffer of the field
(format t "buffer: ~A~%" (buffer field))
(format t "string: ~A" (value field)))
;; when cancel returns nil
(progn
(clear scr)
(format t "field edit canceled.")))
(refresh scr)
;; wait for keypress, then exit
(get-char scr) )))
(defun t16f ()
"Group several input fields and buttons to a form."
(with-screen (scr :input-echoing nil :cursor-visible t :enable-colors t :enable-function-keys t :input-blocking t)
(let* ((s1 (list :fgcolor nil :bgcolor nil :attributes nil))
(s2 (list :simple-char #\_))
(s3 (list :fgcolor :yellow :bgcolor :red :attributes '(:underline :bold :italic)))
(s4 (list :fgcolor :blue :bgcolor :white :attributes nil))
;; a style is a plist interpreted by the element drawing functions.
(s5 (list :foreground s1 :background s2 :selected-foreground s3 :selected-background s4))
(field1 (make-instance 'field :location (list 3 20) :width 10 :style s5 :max-buffer-length 5))
(field2 (make-instance 'field :location (list 5 20) :width 10 :style s5))
(field3 (make-instance 'field :location (list 7 20) :width 10 :style s5 :max-buffer-length 15))
(s6 (list :foreground s1 :selected-foreground s4))
(button1 (make-instance 'button :location (list 10 10) :name "Hello" :style s6))
(button2 (make-instance 'button :location (list 10 20) :name "Accept" :style s6))
(button3 (make-instance 'button :location (list 10 30) :name "Cancel" :style s6))
;; a window is associated with the parent form, and can be accessed by the elements.
(form (make-instance 'form :elements (list field1 field2 field3 button1 button2 button3) :window scr)))
;; for debugging, return prints the content of the buffer and then deletes the buffer
(bind form :f4 'de.anvi.croatoan::debug-print-field-buffer)
(bind (find-keymap 'field-map) :f3 'de.anvi.croatoan::debug-print-field-buffer)
;; Functions to be called when the button is activated by #\newline or #\space.
(setf (callback button1) (lambda (b e) (save-excursion scr (move scr 0 0) (format scr "Hello there"))))
(setf (callback button2) 'accept)
(setf (callback button3) 'cancel)
;; pressing ^A or C-a (for "accept") exits the edit mode
;; TAB, up and down cycles the fields and buttons
(if (prog1 (edit form) (clear scr))
;; display the contents of the input buffer of all fields of the form
;; use field-buffer-to-string to get the contents of the field buffer as a string instead of a list of chars.
(loop for el in (elements form)
do (when (typep el 'field)
(format scr "~A ~A ~%" (buffer el) (value el))))
;; user did not accept the form (default) or called cancel-form.
(format scr "nil"))
(refresh scr)
;; wait for keypress, then exit
(get-char scr) )))
(defun t16g ()
"Use the element default style of the form."
(with-screen (scr :input-echoing nil :cursor-visible t :enable-colors t :enable-function-keys t :input-blocking t)
(let* (;; character styles that can be referenced in element styles
(ch1 (list :fgcolor :blue :bgcolor :black))
(ch2 (list :simple-char #\_))
(ch3 (list :fgcolor :yellow :bgcolor :red :attributes '(:bold :italic)))
(ch4 (list :simple-char #\_ :fgcolor :white :bgcolor :blue))
(ch5 (list :fgcolor :yellow :simple-char #\.))
;; element styles reference previousy defined character styles.
(s1 (list :foreground ch1 :background ch2 :selected-foreground ch3 :selected-background ch4))
(s2 (list :foreground ch4 :selected-foreground ch3))
(s3 (list :foreground ch1 :background ch5))
;; the form style consists of default styles of form elements.
(sf1 (list 'field s1 'button s2 'label s3 'checkbox s1))
(field1 (make-instance 'field :name :f1 :title "Forename" :location (list 3 20) :width 15 :max-buffer-length 5))
(field2 (make-instance 'field :name :f2 :title "Surname" :location (list 5 20) :width 15))
(field3 (make-instance 'field :name :f3 :location (list 7 20) :width 15 :max-buffer-length 20))
(cb1 (make-instance 'checkbox :name :c1 :title "Employed" :location (list 9 20)))
(m1 (make-instance 'checklist :name :m1 :title "checklist" :items (list 'a 'b 'c 'd 'e 'f) :layout (list 2 3)
:max-item-length 6 :menu-location (list 11 20) :window scr))
(label1 (make-instance 'label :name :l1 :reference :f1 :width 18 :location (list 3 1)))
(label2 (make-instance 'label :name :l2 :reference :f2 :width 18 :location (list 5 1)))
(label3 (make-instance 'label :name :l3 :title "Age" :location (list 7 1)))
(label4 (make-instance 'label :name :l4 :reference :c1 :width 18 :location (list 9 1)))
(label5 (make-instance 'label :name :l5 :reference :m1 :width 18 :location (list 11 1)))
(button1 (make-instance 'button :name :b1 :title "Hello" :location (list 14 10)))
(button2 (make-instance 'button :name :b2 :title "Accept" :location (list 14 20)))
(button3 (make-instance 'button :name :b3 :title "Cancel" :location (list 14 30)))
(form (make-instance 'form
:elements (list field1 field2 field3 cb1 label1 label2 label3 label4 label5
m1 button1 button2 button3)
:style sf1 :window scr)))
;; for debugging, return prints the content of the buffer and then deletes the buffer
(bind form :f4 'crt::debug-print-field-buffer)
;; Functions to be called when the button is activated by #\newline or #\space.
(setf (callback button1) (lambda (b e) (save-excursion scr (move scr 0 0) (format scr "Hello there"))))
(setf (callback button2) 'accept)
(setf (callback button3) 'cancel)
;; The set value shouldnt be longer than the max-buffer-length
(setf (value field2) "hello"
(value field3) "dear john")
;; Access fields by their name instead of looping through the elements list.
(if (edit form)
;; edit returned t, which means the user accepted the form
(progn
(clear scr)
(mapc #'(lambda (name)
(let ((element (find-element form name)))
(format scr "~5A ~10A ~20A~%" name (title element) (value element))))
(list :f1 :f2 :f3))
;; display the state of the checkbox
(format scr "~5A ~10A ~20A~%" (name cb1) (title cb1) (checkedp cb1))
(format scr "~5A ~10A ~20A~%" (name m1) (title m1) (value m1)))
;; edit returned nil, which means the user canceled the form
(progn
(clear scr)
(format scr "nil")))
(refresh scr)
;; wait for keypress, then exit
(get-char scr) )))
(defun t16h ()
"Create a form window."
(with-screen (scr :input-echoing nil :cursor-visible t :enable-colors t :enable-function-keys t :input-blocking t)
(let* ((ch1 (list :fgcolor :black :bgcolor :white))
(ch2 (list :simple-char #\_ :fgcolor :black :bgcolor :white))
(ch3 (list :fgcolor :yellow :bgcolor :red :attributes '(:underline :bold :italic)))
(ch4 (list :simple-char #\_ :fgcolor :white :bgcolor :blue))
(style1 (list :foreground ch1 :background ch2 :selected-foreground ch3 :selected-background ch4))
(style2 (list :foreground ch4 :selected-foreground ch3))
(style3 (list 'field style1 'button style2))
(field1 (make-instance 'field :name :f1 :title "Forename" :location (list 3 3) :width 20 :max-buffer-length 5))
(field2 (make-instance 'field :name :f2 :title "Surname" :location (list 5 3) :width 20))
(field3 (make-instance 'field :name :f3 :title "Age" :location (list 7 3) :width 20 :max-buffer-length 20))
(button1 (make-instance 'button :name :b1 :title "Say Hello" :location (list 10 7)))
(button2 (make-instance 'button :name :b1 :title "Cancel" :location (list 10 20)))
(button3 (make-instance 'button :name :b2 :title "Accept" :location (list 10 30)))
(form (make-instance 'form-window :elements (list field1 field2 field3 button1 button2 button3)
:style style3 :enable-function-keys t :input-blocking t :title "form window"
:draw-border t :height 15 :width 50 :location (list 5 15))))
(setf (background scr) (make-instance 'complex-char :simple-char #\space))
(setf (background form) (make-instance 'complex-char :simple-char #\space :color-pair '(:black :white)))
(refresh scr)
;; Functions to be called when the button is activated by #\newline or #\space.
(setf (callback button1) (lambda (b e) (save-excursion scr (move scr 0 0) (format scr "Hello there") (refresh scr))))
(setf (callback button2) 'cancel)
(setf (callback button3) 'accept)
;; The set value shouldnt be longer than the max-buffer-length (not checked yet)
(setf (value field1) "hello"
(value field2) "there"
(value field3) "dear john")
(event-case (scr event)
(#\q (return-from event-case))
;; display the form window input box by pressing a.
(#\a
(if (edit form)
;; edit returns t if the form is accepted
(progn
(clear scr)
;; Access fields by their name instead of looping through the elements list.
(mapc #'(lambda (name)
(let ((field (find-element form name)))
(format scr "~5A ~10A ~20A~%" name (title field) (value field))))
(list :f1 :f2 :f3))
(refresh scr))
;; and nil if it is cancelled.
(progn
(clear scr)
(princ nil scr)
(refresh scr)))))
;; close the ncurses windows before exiting.
(close form))))
(defun t16i ()
"A simple input form window."
(let ((value nil))
(with-screen (scr :input-echoing nil :cursor-visible t :enable-colors t :enable-function-keys t :input-blocking t)
(let* ((ch1 (list :fgcolor :black :bgcolor :white))
(ch2 (list :simple-char #\_ :fgcolor :black :bgcolor :white))
(ch3 (list :fgcolor :yellow :bgcolor :red :attributes '(:underline :bold :italic)))
(ch4 (list :simple-char #\_ :fgcolor :white :bgcolor :blue))
(ch5 (list :simple-char #\. :fgcolor :black :bgcolor :white))
(style1 (list :foreground ch1 :background ch2 :selected-foreground ch3 :selected-background ch4))
(style2 (list :foreground ch1 :selected-foreground ch4))
(style3 (list :foreground ch1 :background ch5))
;; form element default styles
(style4 (list 'field style1 'button style2 'label style3))
(label1 (make-instance 'label :name :l1 :reference :f1 :width 20 :location (list 2 1)))
(field1 (make-instance 'field :name :f1 :title "Forename" :location (list 2 22) :width 20 :max-buffer-length 25))
(label2 (make-instance 'label :name :l2 :reference :f2 :width 20 :location (list 4 1)))
(field2 (make-instance 'field :name :f2 :title "Surname" :location (list 4 22) :width 20 :max-buffer-length 25))
(button1 (make-instance 'button :name :b3 :title "Reset" :location (list 6 15)))
(button2 (make-instance 'button :name :b2 :title "Cancel" :location (list 6 25)))
(button3 (make-instance 'button :name :b2 :title "Accept" :location (list 6 34)))
(form (make-instance 'form-window :elements (list field1 field2 label1 label2 button1 button2 button3)
:style style4 :enable-function-keys t :input-blocking t :title "form window"
:draw-border t :height 10 :width 50 :location (list 5 15))))
(setf (background form) (make-instance 'complex-char :simple-char #\space :color-pair '(:black :white)))
(refresh scr)
(setf (callback button1) 'reset-form)
(setf (callback button2) 'cancel)
(setf (callback button3) 'accept)
(if (edit form)
(setq value (pairlis (list (title field1) (title field2))
(list (value field1) (value field2))))
(setq value nil))
(close form)))
;; return the input values
value))
;; creating sub-windows and how they share memory with the parent window.
;; leaving out the size of a window maxes it out to the right (win1) and to the bottom (win1, win3)
(defun t17 ()
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
(let* ((win1 (make-instance 'window :location '(2 2) :draw-border t))
(win2 (make-instance 'sub-window :parent win1 :height 5 :width 20 :location '(4 4) :draw-border t))
(win3 (make-instance 'sub-window :parent win1 :width 20 :location '(4 4) :draw-border t :relative t)))
(princ "win1" win1)
(princ "win2" win2)
(princ "win3 relative" win3)
(mapc #'(lambda (w) (refresh w)) (list win1 win2 win3))
(get-char win3)
(mapc #'(lambda (w) (close w)) (list win2 win3))
(refresh win1)
;; observe that the content from win 2 and 3 is still in win1 after they have been closed.
(get-char win1)
(close win1) )))
;; by default, the sub-window displays the part of the parent window it overlaps with.
;; we can change which part of the parent is displayed by changing the sub-windows source-location
;; we can change where it is displayed by changing the sub-windows location.
(defun t17a ()
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
(let ((win (make-instance 'sub-window :parent scr :height 5 :width 20 :location '(2 2) :draw-border t :relative t)))
;; initial content written to subwin and thus to scr.
(move win 1 1) (princ "subwin" win)
;; create two content areas in the parent window.
(move scr 2 25) (princ "area1" scr)
(move scr 3 29) (princ "area1" scr)
(move scr 4 33) (princ "area1" scr)
(move scr 5 37) (princ "area1" scr)
(move scr 6 41) (princ "area1" scr)
(move scr 2 50) (princ "area2" scr)
(move scr 3 54) (princ "area2" scr)
(move scr 4 58) (princ "area2" scr)
(move scr 5 62) (princ "area2" scr)
(move scr 6 66) (princ "area2" scr)
(mapc #'(lambda (w) (touch w) (refresh w)) (list scr win))
(get-char scr)
;; map area1 to subwin location
;; the mapping only _displays_ the content from the source area.
;; the mapping it doesnt _write_ it onto the displayed area of scr.
(setf (source-location win) '(2 25)) ; (%mvderwin (winptr win) 2 25)
(mapc #'(lambda (w) (touch w) (refresh w)) (list scr win))
(get-char scr)
;; map area2 to subwin location
(setf (source-location win) '(2 50))
(mapc #'(lambda (w) (touch w) (refresh w)) (list scr win))
(get-char scr)
;;; now move subwin location
;; it still maps area2, but now to the new location.
;; the original content written to the subwin (and thus to scr because they share memory)
;; is now visible in scr, since the subwin overlay has moved.
(setf (location win) '(10 2))
(mapc #'(lambda (w) (touch w) (refresh w)) (list scr win))
(get-char scr)
;; map area1 again, but now to the new location.
(setf (source-location win) '(2 25))
(mapc #'(lambda (w) (touch w) (refresh w)) (list scr win))
(get-char scr)
;; writing to a sub-window writes to the _mapped_ location of the parent window
;; _not_ to the parent location below the subwindow.
(clear win)
(move win 0 0) (princ "writing to sub-win" win)
(move win 1 0) (princ "writes to parent win" win)
(mapc #'(lambda (w) (touch w) (refresh w)) (list scr win))
(get-char scr)
;; delete the subwindow win
;; the mapped content isnt displayed any more.
;; now the content before the mapping should be displayed again.
(mapc #'(lambda (w) (close w)) (list win))
(refresh scr)
(get-char scr) )))
;; Display misc system information
;; https://github.com/rudolfochrist/dotfiles/blob/master/.rc.lisp
(defun t18 ()
(with-screen (scr)
(format scr "Lisp implementation type: ~A~%" (lisp-implementation-type))
(format scr "Lisp implementation version: ~A~%" (lisp-implementation-version))
(format scr "Machine type (Arch) ~A~%" (machine-type))
(format scr "Machine version (CPU) ~A~%" (machine-version))
(format scr "Software type (OS) ~A~%" (software-type))
(format scr "Software version (OS) ~A~%" (software-version))
(format scr "Machine instance (hostname) ~A~%" (machine-instance))
(format scr "User HOME ~A~%" (user-homedir-pathname))
(format scr "Ncurses version: ~A~%" (de.anvi.ncurses:%curses-version))
(format scr "Terminal: ~A~%" (%termname))
(format scr "Colors supported: ~A~%" (%has-colors))
(format scr "Color change supported: ~A~%" (%can-change-color))
(format scr "No of supported colors: ~A~%" %colors)
(format scr "No of supported color pairs ~A~%" %color-pairs)
(format scr "Can insert/delete chars: ~A~%" (%has-ic))
(format scr "Can insert/delete lines: ~A~%" (%has-il))
#+sbcl
(progn
(format scr "Terminal: ~A~%" (sb-ext:posix-getenv "TERM"))
(format scr "Locale: ~A~%" (sb-ext:posix-getenv "LANG"))
(format scr "Hostname: ~A~%" (sb-ext:posix-getenv "HOSTNAME"))
(format scr "Username: ~A~%" (sb-ext:posix-getenv "USER"))
(format scr "Operating system: ~A~%" (sb-ext:posix-getenv "OSTYPE"))
(format scr "CPU Type: ~A~%" (sb-ext:posix-getenv "MACHTYPE")))
(format scr "uname -a:~%~A" (uiop:run-program "uname -a" :output :string))
(refresh scr)
(get-char scr)))
;; http://stackoverflow.com/questions/38684906/how-to-get-package-documentation-in-quicklisp
(defun t18a ()
"Display misc quicklisp information."
#+quicklisp
(with-screen (scr)
(let* ((dist (ql-dist:dist "quicklisp")) ; current dist
;; output to an empty broadcast stream is discraded like redirecting to /dev/null
(*standard-output* (make-broadcast-stream))
;; Quicklisp prints its "Fetching xyz" messages to *trace-output*
(*trace-output* *standard-output*)
(*error-output* *standard-output*)
(installed-systems (mapcar #'ql-dist:name (ql-dist:installed-systems dist))))
(format scr "Quicklisp dist name ~A~%" (ql-dist:name dist))
(format scr "Quicklisp dist version ~A~%" (ql-dist:version dist))
(format scr "Quicklisp available version ~A~%" (caar (ql-dist:available-versions dist)))
(format scr "~%Quicklisp installed releases ~{~A, ~}~%" (mapcar #'ql-dist:name (ql-dist:installed-releases dist)))
(format scr "~%Quicklisp installed systems ~{~A, ~}~%~%" installed-systems)
(loop
for i in installed-systems
for sys = (asdf:find-system i nil)
do
(when sys
(format scr "~27@A | ~A~%"
i
(remove #\newline (asdf:system-description sys))))))
(refresh scr)
(get-char scr))
#-quicklisp
(princ "Quicklisp not installed."))
;; print a simple menu, let the user choose an item by arrow keys, return the chosen item.
;; https://www.gnu.org/software/guile-ncurses/manual/html_node/A-simple-key-usage-example.html
(defun t19 ()
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
(flet ((draw-menu (win choices i)
(clear win)
(loop for j from 0 to (1- (length choices)) do
(move win j 0)
(format win "~A~A" (nth j choices) (if (= i j) "*" ""))
(when (= i j)
(move win j 0)
(change-attributes win 9 '(:reverse)))
(refresh win))))
(let* ((choices '("Choice 0" "Choice 1" "Choice 2" "Choice 3" "Choice 4" "Choice 5" "Choice 6"))
(n (length choices))
(i 0)) ; current choice
(draw-menu scr choices i)
(event-case (scr event)
(:up (setf i (mod (1- i) n)) (draw-menu scr choices i))
(:down (setf i (mod (1+ i) n)) (draw-menu scr choices i))
(#\newline (return-from event-case (nth i choices)))
(#\q (return-from event-case)))))))
;; an even simpler 2-item menu is a yes-no-dialog.
(defun t19a ()
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
(flet ((draw-menu (win choices i)
(move win 2 0) (clear win :target :end-of-line)
(loop for j from 0 to (1- (length choices)) do
(move win 2 (* 10 j))
(format win "~A~A" (car (nth j choices)) (if (= i j) "*" ""))
(refresh win))))
(let* ((choices '(("Yes" . t) ("No" . nil)))
(n (length choices))
(i 0)) ; current choice
(move scr 0 0) (format scr "User, do you want?") (refresh scr)
(draw-menu scr choices i)
(event-case (scr event)
((:up :down :left :right #\tab) (setf i (mod (1+ i) n)) (draw-menu scr choices i))
(#\newline (return-from event-case (cdr (nth i choices)))))))))
(defun t19b ()
"Use the menu class, draw-menu and select functions."
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
(let* ((choices '("Choice 0" "Choice 11" "Choice 222" "Choice 3333" "Choice 44444" "Choice 555555" "Choice 6666666"))
(menu (make-instance 'menu-window :items choices :location (list 0 20) :title "t19b"
:cyclic-selection t :draw-border t :enable-function-keys t)))
(let ((result (select menu)))
(format scr "You chose ~A" result)
(touch scr)
(refresh scr)
(get-char scr))
(close menu))))
(defun t19b2 ()
"Use the select function with independent windows and menus"
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
(let* ((items1 '("Choice 0" "Choice 11" :choice22 "Choice 3333" "Choice 44444" "Choice 555555"
"Choice 6666666" "Choice 7" "Choice 88" "Choice 999"))
(menu1 (make-instance 'menu :items items1 :name "sub-menu 1" :max-item-length 50 :menu-location (list 5 10)))
(items2 (list "Item 0" menu1 "Item 1" "Item 2" "Item 3" "Item 4" "Item 5" "Item 6" "Item 7" "Item 8" "Item 9"))
(menu2 (make-instance 'menu :items items2 :name "sub-menu 2" :max-item-length 50 :menu-location (list 5 10)))
(items3 (list "Item 00" menu2 "Item 01" "Item 02" "Item 03" "Item 04" "Item 5" "Item 6" "Item 7" "Item 8" "Item 9"))
(menu3 (make-instance 'menu :items items3 :name "t19b2b" :max-item-length 50 :menu-location (list 5 10))))
;; associate the same window with all three menus.
(setf (window menu1) scr
(window menu2) scr
(window menu3) scr)
;; select an item and return it.
(select menu3))))
(defun t19c ()
"Improved t19b, the menu can be called repeatedly with the key a."
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
(let* ((choices '("Choice 0" "Choice 11" "Choice 222" "Choice 3333" "Choice 44444" "Choice 555555"
"Choice 6666666" "Choice 7" "Choice 88" "Choice 999"))
(menu (make-instance 'menu-window :items choices :location (list 0 25) :scrolled-layout (list 6 1)
:title "t19c" :draw-border t :enable-function-keys t)))
(event-case (scr event)
;; "a" draws the menu and enters a new menu-only event loop
(#\a (let ((result (select menu)))
(format scr "You chose ~A~%" result)
;; we have to touch scr in order to make the menu disappear.
(touch scr)
(refresh scr)))
(#\q (return-from event-case)))
(close menu))))
(defun t19c2 ()
"Test the menu-item class for submenus."
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
(let* ((fun1 (make-instance 'menu-item :name "fun1" :value (lambda () (clear scr))))
(choices (list "Choice 0" 'choice11 fun1 "Choice 222" "Choice 3333" "Choice 44444" "Choice 555555"
"Choice 6666666" "Choice 7" "Choice 88" "Choice 999"))
;; First, create a menu
;; TODO: how to determine the location of the sub-menu depending on the parent menu?
(sub-menu2 (make-instance 'menu-window
:items choices ;; here we only have strings
:location (list 2 57) :scrolled-layout (list 6 1)
;; for hex triplets to work, we need to start sbcl with:TERM=xterm-256color lisp.sh
;;:color-pair (list :black #x666666)
:bgcolor :red
:name "submenu2" :title t :draw-border t :enable-function-keys t :visible nil))
;; then add that sub-menu menu as an item to the next menu, and so on.
(sub-menu1 (make-instance 'menu-window
:items (cons sub-menu2 choices) ;; first item is a submenu
:location (list 1 41) :scrolled-layout (list 6 1)
;;:color-pair (list :black #x999999)
:fgcolor :green
:name "submenu1" :title nil :draw-border t :enable-function-keys t :visible nil))
;; finally, create the main menu containing sub-menu1 as an item
(menu (make-instance 'menu-window
:items (cons sub-menu1 choices) ;; first item is a submenu
:location (list 0 25) :scrolled-layout (list 6 1)
;;:color-pair (list :black #xcccccc)
:fgcolor :blue :bgcolor :yellow
:name "menu" :title nil :draw-border nil :enable-function-keys t :visible nil)))
;; add the menus and submenus to a window stack
;; scr has to be stacked too so we can make the menus disappear.
(setf (stackedp scr) t
(stackedp menu) t
(stackedp sub-menu1) t
(stackedp sub-menu2) t)
(setf (background scr) (make-instance 'complex-char :simple-char :board :color-pair (list :black :white)))
(refresh-stack)
(event-case (scr event)
;; "a" draws the menu and enters a new menu-only event loop by calling select
(#\a (let ((result (select menu)))
(format scr "You chose ~A~%" result)
(format scr "~A~%" (color-pair menu))
(format scr "Stack ~A~%" (length de.anvi.croatoan::*window-stack*))
(refresh-stack) ))
;; TODO: doesnt repaint the border, because the border already has attributes.
;; setting a background doesnt change existing attributes, only existing backgrounds.
(#\b (setf (color-pair menu) (list :red :black)))
;; "q" exits the function and all menus and submenus.
(#\q (return-from event-case)))
(close menu)
(close sub-menu1)
(close sub-menu2))))
(defun t19c3 ()
"Menu with checkbox items."
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
(let* ((choices '("Choice 0" "Choice 11" "Choice 222" "Choice 3333" "Choice 44444" "Choice 555555"
"Choice 6666666" "Choice 7" "Choice 88" "Choice 999"))
(menu (make-instance 'menu-window :items choices :location (list 0 25) :scrolled-layout (list 6 1)
:title "t19c" :draw-border t :enable-function-keys t
:menu-type :checklist
:max-item-length 20
:color-pair (list :yellow :red) )))
(event-case (scr event)
;; "a" draws the menu and enters a new menu-only event loop
(#\a (let ((result (select menu)))
(format scr "You chose ~A~%" (mapcar #'value result))
;; we have to touch scr in order to make the menu disappear.
(touch scr)
(refresh scr)))
(#\q (return-from event-case)))
(close menu))))
(defun t19d ()
"Use the arrow keys to pick a value from an 2D array menu, given as a layout parameter."
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
(let* ((items (loop for i below 200 collect (format nil "Item ~A" i)))
(menu (make-instance 'menu-window
:items items :location (list 0 0) :layout (list 20 10) :scrolled-layout (list 10 4)
:cyclic-selection nil :max-item-length 9 :title "t19d" :draw-border t :enable-function-keys t)))
(event-case (scr event)
;; "a" draws the menu and enters a new menu-only event loop
(#\a (let ((result (select menu)))
(format scr "You chose ~A~%" result)
;; we have to touch scr in order to make the menu disappear.
(touch scr)
(refresh scr)))
(#\q (return-from event-case)))
(close menu))))
(defun t19e ()
"A one-line menu without a title and border resembling a menu bar."
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
(let* ((items '("Item 0" "Item 1" "Item 2" "Item 3" "Item 4" "Item 5" "Item 6" "Item 7" "Item 8" "Item 9"))
(menu (make-instance 'menu-window :input-blocking t :items items :location (list 0 0)
:layout (list 1 (length items))
:scrolled-layout (list 1 6)
;;:color-pair (list :black :yellow)
:max-item-length 10 :width (width scr) :draw-border t :enable-function-keys t)))
;; start the output below the menu
(move scr 4 0)
;; exit the infinite loop by exiting the menu with q.
(loop named menu-case
do
(let ((result (select menu)))
(unless result (return-from menu-case))
(format scr "You chose ~A~%" result)
(refresh scr)))
(close menu))))
(defun t19e2 ()
"A menu bar and submenus."
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
(let* ((items1 (list "Choice 0" "Choice11" "Choice 222" "Choice 3333" "Choice 44444" "Choice 555555"
"Choice 6666666" "Choice 7" "Choice 88" "Choice 999"))
(sub-menu1 (make-instance 'menu-window :items items1 :location (list 2 30) :scrolled-layout (list 6 1)
:title nil :name "submenu1" :draw-border t :enable-function-keys t :visible nil :menu-type :selection))
(sub-menu2 (make-instance 'menu-window :items items1 :location (list 2 45) :scrolled-layout (list 6 1)
:title nil :name "submenu2" :draw-border t :enable-function-keys t :visible nil :menu-type :checklist))
(fun1 (make-instance 'menu-item :name "fun1" :value (lambda () (clear scr))))
(items2 (list "Item 0" fun1 sub-menu1 sub-menu2))
(menu (make-instance 'menu-window :input-blocking t :items items2 :location (list 0 0) :layout (list 1 (length items2))
:max-item-length 15 :width (width scr) :draw-border t :enable-function-keys t)))
(setf (stackedp scr) t
(stackedp menu) t
(stackedp sub-menu1) t
(stackedp sub-menu2) t)
(move scr 4 0) ;; start the output at line 4, below the menu bar.
(refresh-stack)
(loop named menu-case
do (let ((result (select menu)))
(unless result (return-from menu-case))
(format scr "You chose ~A~%" result)
(refresh-stack)))
(close menu)
(close sub-menu1)
(close sub-menu2) )))
(defun t19f ()
"A more fancy version of t19a, a yes-no dialog using the class dialog-window."
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
(let* ((items (list "Yes" "No" "OK" 'cancel))
(menu (make-instance 'dialog-window
:input-blocking t
:items items
;; when center is t for a dialog window, we do not need to pass the location explicitely.
;;:location (list 5 15)
:center t
:layout (list 1 4)
:max-item-length 12
:current-item-mark "> "
:color-pair (list :yellow :red)
:width 60
:draw-border t
:enable-function-keys t
:name "t19f"
:title t
;; if the title is given as a string, it overrides the default title = name
;; :title "this is a selection dialog"
:message-height 2
:message-text "Press <- or -> to choose. Enter to confirm choice.~%Press q to exit.")))
(setf (background scr) (make-instance 'complex-char :simple-char :board :color-pair (list :black :white)))
(refresh scr)
(loop named menu-case
do (let ((result (select menu)))
(unless result (return-from menu-case))
(format scr "You chose ~A~%" result)
(refresh scr)))
(close menu))))
(defun t19g ()
"A checkbox dialog."
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t :use-terminal-colors t)
(let* ((items (list "Yes" "No" "OK" 'cancel "Maybe"))
(menu (make-instance 'dialog-window
:input-blocking t
:items items
;; when a menu or dialog type is a checklist, items can be checked and unchecked with x/space.
:menu-type :checklist
;; a dialog window can be automatically centered in the terminal window.
:center t
:layout (list 5 1)
;; TODO: the size of the dialog window should depend on scrolled layout when it is defined.
;;:scrolled-layout (list 3 1)
:max-item-length 56
:color-pair (list :yellow :red)
;; we do not need an item mark in a checklist
:current-item-mark ""
:width 60 :draw-border t :enable-function-keys t
:title "this is a checkbox dialog"
:message-height 2
:message-text "Press <- or -> to choose. Enter to confirm choice.~%Press q to exit.")))
;; #x2592 = :board
(setf (background scr) (make-instance 'complex-char :simple-char #x2592 :color-pair (list :white :black)))
(refresh scr)
(loop named menu-case
do (let ((result (select menu)))
;; TODO: returning an empty list exits the loop.
(unless result (return-from menu-case))
(format scr "You chose ~A~%" (mapcar #'value result))
(refresh scr)))
(close menu))))
;; Passing the color attribute directly to a character.
(defun t20 ()
"Display a randomly created carpet of the seven default ANSI colors, except for black."
(with-screen (scr :input-echoing nil :input-blocking nil :enable-colors t :cursor-visible nil)
(let ((colors '(:red :green :yellow :blue :magenta :cyan :white)))
(event-case (scr event)
(#\q (return-from event-case))
((nil)
(sleep 0.01)
(echo scr #\space
:y (random (height scr))
:x (random (width scr))
:color-pair (list nil (nth (random 7) colors))))))))
(defun t20a ()
"Display a randomly created carpet of the 16 default xterm colors, except for black.
This only works with TERM=xterm-256color in xterm and gnome-terminal."
(with-screen (scr :input-echoing nil :input-blocking nil :enable-colors t :cursor-visible nil)
(let ((colors '( :maroon :green :olive :navy :purple :teal :silver
:gray :red :lime :yellow :blue :magenta :cyan :white)))
(bind scr #\q 'exit-event-loop)
(bind scr nil
(lambda (win e)
(echo win #\space
:y (random (height win))
:x (random (width win))
:bgcolor (nth (random 15) colors))))
(setf (frame-rate scr) 1000)
(run-event-loop scr))))
;; it is just a coincidence that "echo-char" works here.
;; echo-char is a chtype-function, and chtype allows only 1 byte for color pairs, which means 256 color pairs.
;; it works only because the foreground color is always black in the example.
;; echo-wide-char has to be used.
(defun t20b ()
"Display the 256 supported colors. This only works with TERM=xterm-256color in xterm and gnome-terminal."
(with-screen (scr :input-echoing nil :input-blocking t :enable-colors t :cursor-visible nil)
;; 0-15: 8 ANSI colors and 8 bold ANSI colors
;; only those first 16 colors are named.
;; note that the naming of the first 8 ANSI colors is not the same as the first 8 xterm colors.
(loop for i from 0 to 7 do
(loop for j from 0 to 1 do
(loop for k from 0 to 2 do
(echo scr #\space
:y j
:x (+ (* i 3) k)
:color-pair (list :black
(list :number (+ i (* j 8))))) )))
;; 16-231: 6x6x6 color cube
(loop for n from 0 to 1 do
(loop
for m from 0 to 2
for a1 = (+ 16 (* m 36))
for a2 = (+ 16 (* m 36) 5)
for a3 = (* m 19)
do (loop for i from a1 to a2 do
(loop for j from 0 to 5 do
(loop for k from 0 to 2 do
(echo scr #\space
:y (+ 3 j (* n 7))
:x (+ (* (- i a1) 3) k a3)
:color-pair (list :black
(list :number (+ (* n 108) i (* j 6)))) ))))))
;; 232-255: 24 shades of gray, without black and white
(loop for i from 232 to 255 do
(loop for k from 0 to 2 do
(echo scr #\space
:y 17
:x (+ (* (- i 232) 3) k)
:color-pair (list :black
(list :number i)))))
(get-char scr)))
(defun t20c ()
"Display the 256 supported colors. This only works with TERM=xterm-256color in xterm and gnome-terminal."
(with-screen (scr :input-echoing nil :input-blocking t :enable-colors t :cursor-visible nil)
;; 0-15: 8 ANSI colors and 8 bold ANSI colors
(loop for i from 0 to 7 do
(loop for j from 0 to 1 do
(loop for k from 0 to 2 do
(add scr #\space
:y j
:x (+ (* i 3) k)
:bgcolor (list :number (+ i (* j 8))))) ))
;; 16-231: 6x6x6 color cube
(loop for n from 0 to 1 do
(loop
for m from 0 to 2
for a1 = (+ 16 (* m 36))
for a2 = (+ 16 (* m 36) 5)
for a3 = (* m 19)
do (loop for i from a1 to a2 do
(loop for j from 0 to 5 do
(loop for k from 0 to 2 do
(add scr #\space
:y (+ 3 j (* n 7))
:x (+ (* (- i a1) 3) k a3)
:bgcolor (list :number (+ (* n 108) i (* j 6)))) )))))
;; 232-255: 24 shades of gray, without black and white
(loop for i from 232 to 255 do
(loop for k from 0 to 2 do
(add scr #\space
:y 17
:x (+ (* (- i 232) 3) k)
:bgcolor (list :number i))))
(refresh scr)
(get-char scr)))
;; Tests for insert-char, insert-string, extract-char.
(defun t21 ()
(with-screen (scr :cursor-visible nil)
(move scr 0 0) (add-char scr #\a)
;; overwrite b over a
(move scr 0 0)
(add-char scr
(make-instance 'complex-char :simple-char #\b :attributes (list :bold :underline) :color-pair '(:blue :yellow)))
(echo scr
(make-instance 'complex-char :simple-char #\e :attributes (list :underline) :color-pair '(:blue :yellow)))
;; insert pi before b
(move scr 0 0) (insert-char scr :pi :color-pair '(:yellow :red))
;; insert d before pi
(move scr 0 0) (insert-string scr "d ")
;; change the attributes of the d
(move scr 0 0) (change-attributes scr 1 '(:underline) :color-pair '(:green :black))
;; extract the complex d from the window, then print its properties.
(let ((e (extract-char scr)))
(move scr 1 0)
;; format uses print-object specialized on complex-chars
(format scr "~S~%~S ~S ~S" e (simple-char e) (attributes e) (color-pair e))
;; print back the extracted char
(move scr 3 0)
(add-char scr (simple-char e) :attributes (attributes e) :color-pair (color-pair e))
;; directly print the complex char
(add-char scr e :y 5 :x 10 :n -1)
;; print strings with attributes and colors
;; attributes dont work (yet) when a long string gets wrapped around the last column.
(add-string scr "string with attributes" :y 11 :x 10 :attributes '(:underline) :color-pair '(:black :green))
;; test adding both chars and strings with a single wrapper routine.
(add scr #\a :y 13 :x 0 :attributes '(:underline) :color-pair '(:yellow :red) :n -1)
(add scr "string with attributes" :y 15 :x 0 :n 11 :attributes '(:underline :bold) :color-pair '(:yellow :green)))
;; test print-object specialisation on complex-string
(let ((str (make-instance 'complex-string :string "complex-string" :color-pair '(:blue :white) :attributes '(:underline))))
(format scr "~%~S~%" str)
(add-string scr str :y 18 :x 73 :n -1)
(add scr str :y 19 :x 70 :n -1))
;; extract the first line as a complex-string, then reprint it at line 22
(let ((str (extract-complex-string scr :y 0 :x 0 :n 4)))
(move scr 22 1)
(add scr str :n -1))
;; extract the first line as a simple string, then reprint it at line 23
(let ((str (extract-string scr :y 0 :x 0 :n 5)))
(move scr 23 1)
(add scr str))
(refresh scr)
(get-char scr)))
;; temporarily end the main screen, write something to the repl, and then return to the screen.
;; the predicate closed-p can be used to check whether the screen has been temporarily closed.
(defun t22 ()
(let ((scr (make-instance 'screen :input-blocking t :input-echoing nil)))
(unwind-protect
(progn
(clear scr)
;; should yield NIL, since we didnt endwin yet
(format scr "1. screen before ending: ~A~%" (closed-p scr))
(get-char scr)
(close scr)
;; this shouldnt be visible, but it is visible after the later refresh
;; obviously ncurses doesnt destroy the screen window after it is closed
(format scr "2. screen after ending, we dont see it till after the refresh: ~A~%" (closed-p scr))
;; should yield T, because we closed the screen
(format t "3. in the repl, after ending the screen: ~A~%" (closed-p scr))
(force-output *standard-output*)
(sleep 5)
(refresh scr)
;; should yield NIL, since we refreshed
(format scr "4. screen after refreshing: ~A~%" (closed-p scr))
(get-char scr))
(close scr))))
(defun t23 ()
"Use save-excursion to return the cursor to its initial location."
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible t :enable-colors t)
(move scr 0 0)
(princ "1. hello" scr)
(save-excursion scr
(move scr 5 5)
(princ "2. there" scr))
(princ "3. dear john" scr)
(get-char scr)))
(defun t24 ()
"Test usage of insert-line and delete-line."
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible t :enable-colors t)
(loop for i from 0 to (- (height scr) 1)
do
(move scr i 0)
(format scr "~A" i))
(refresh scr)
(event-case (scr event)
((:up :down) (move-direction scr event) (refresh scr))
(#\d (delete-line scr) (refresh scr))
(#\i (insert-line scr) (refresh scr))
(#\q (return-from event-case)))
(get-char scr)))
(defun t25 ()
"Test initialisation and refreshing of pads and sub-pads."
(with-screen (scr :input-blocking t :cursor-visible nil :enable-colors t)
(let* ((p (make-instance 'pad :height 100 :width 100))
(sp (make-instance 'sub-pad :parent p :height 5 :width 10 :location (list 10 10))))
;; populate the pad with numbers.
(loop for j from 0 to 99
do (loop for i from 0 to 99
do
(move p j i)
(format p "~D" (mod (* i j) 10))))
;; populate the sub-pad with letters.
(loop for j from 0 to 4
do (loop for i from 0 to 9
do
(move sp j i)
(add-char sp #\X) ))
;; we have to modify the sub-window attributes first because apparently once
;; chars have attributes, they can not be changed by subsequent background changes.
(setf (background sp) (make-instance 'complex-char :color-pair '(:white :red))
(background p) (make-instance 'complex-char :color-pair '(:green :white)))
(let ((pad-min-y 0)
(pad-min-x 0)
(screen-min-y 0)
(screen-min-x 0)
(screen-max-y 5)
(screen-max-x 10))
;; the background screen has to be touched and refreshed on every move,
;; otherwise we will see parts of the previously displayed pad still there.
(touch scr)
(refresh scr)
(refresh p pad-min-y pad-min-x screen-min-y screen-min-x screen-max-y screen-max-x)
(event-case (scr event)
(:up (decf pad-min-y) (decf screen-min-y) (decf screen-max-y) (touch scr) (refresh scr)
(refresh p pad-min-y pad-min-x screen-min-y screen-min-x screen-max-y screen-max-x))
(:down (incf pad-min-y) (incf screen-min-y) (incf screen-max-y) (touch scr) (refresh scr)
(refresh p pad-min-y pad-min-x screen-min-y screen-min-x screen-max-y screen-max-x))
(:left (decf pad-min-x) (decf screen-min-x) (decf screen-max-x) (touch scr) (refresh scr)
(refresh p pad-min-y pad-min-x screen-min-y screen-min-x screen-max-y screen-max-x))
(:right (incf pad-min-x) (incf screen-min-x) (incf screen-max-x) (touch scr) (refresh scr)
(refresh p pad-min-y pad-min-x screen-min-y screen-min-x screen-max-y screen-max-x))
(#\q (return-from event-case))
(otherwise nil)))
(close p)
(close sp))))
(defun t26 ()
"Test accessors of window and cursor locations."
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible t :enable-colors t :stacked t)
(let ((win (make-instance 'window :height 5 :width 20 :location (list 0 0) :stacked t)))
(setf (background scr) (make-instance 'complex-char :color-pair '(:white :red))
(background win) (make-instance 'complex-char :color-pair '(:black :yellow)))
(refresh-stack)
(get-char scr)
(setf (location win) (list 2 4))
(refresh-stack)
(get-char scr)
(setf (location-y win) 4)
(refresh-stack)
(get-char scr)
(setf (location-x win) 8)
(refresh-stack)
(get-char scr)
(setf (cursor-position win) (list 0 0))
(princ "a" win)
(refresh-stack)
(get-char scr)
(setf (cursor-position-y win) 2)
(princ "b" win)
(refresh-stack)
(get-char scr)
(setf (cursor-position-x win) 4)
(princ "c" win)
(refresh-stack)
(get-char scr))))
(defun t27 ()
"Use run-event-loop and bind instead of event-case to handle keyboard events."
(with-screen (scr :input-echoing nil :input-blocking t)
;; q ends the loop.
(bind scr #\q 'exit-event-loop)
;; The event handler function has to take two arguments, the window and the event.
;; a and s add a string to the window.
(bind scr #\a (lambda (win event) (format win "Hello there.~%")))
(bind scr #\s (lambda (win event) (format win "Dear John.~%")))
;; d clears the window.
(bind scr #\d (lambda (win event) (clear win)))
;; u unbinds a
(bind scr #\u (lambda (win event) (unbind scr #\a)))
(clear scr)
(add-string scr "Type a, s or d. Type q to quit.")
(refresh scr)
(run-event-loop scr)))
(defun t28-hello (win event)
(format win "Hello there ~A.~%" event))
(defun t28-clear (win event)
(declare (ignore event))
(clear win))
(defparameter *t28-map*
(make-instance 'keymap :bindings-plist
(list
#\q 'exit-event-loop
#\a 't28-hello
#\d 't28-clear))
"Define a keymap separately and then set it as a window's event handlers before running the event loop.")
(defun t28 ()
"Use run-event-loop and a pre-defined event handler alist. Use a default handler."
(with-screen (scr :input-echoing nil :input-blocking t)
;; add the separately defined keymap to the window object.
(setf (keymap scr) *t28-map*)
;; add another event handler
(bind scr #\s (lambda (win event) (format win "Dear John ~A~%" event)))
;; t is the default handler for all events without defined handlers.
;; The default event handler should not be used to handle the nil event when input-blocking is nil
(bind *t28-map* t (lambda (win event) (format win "Default event handler ~A~%" event)))
(clear scr)
(add-string scr "Type a, s or d. Type q to quit.")
(refresh scr)
(run-event-loop scr)))
(defun t28a ()
"Test the use of the run-event-loop with non-blocking events."
(with-screen (scr :input-echoing nil :input-blocking nil)
(setf (keymap scr) *t28-map*)
(bind scr #\s (lambda (win event) (format win "Dear John ~A~%" event)))
;; The handler function for the nil event will be called between keyboard events.
(bind scr nil (lambda (win event) (format win "sleep ~A " event)))
(clear scr)
(add-string scr "Type a, s or d. Type q to quit.")
(refresh scr)
;; Set the rate at which the nil event will be handled in fps (frames per second).
;; For the same effect as frame-rate of 1, you can set :input-blocking to 1000 ms.
(setf (frame-rate scr) 1)
(run-event-loop scr)))
(defun draw-t29-shapes (window shapes &optional squarify)
"Draw a list of shapes to window."
(clear window)
(unless (listp shapes) (setf shapes (list shapes)))
(dolist (s shapes)
(draw-shape window s squarify)))
;; Assumes an 80x24 terminal
(defun t29 ()
"Draw an ASCII-art tree to illustrate the use of shapes."
(with-screen (scr :input-blocking t :enable-colors t :input-echoing nil :cursor-visible nil :input-buffering nil)
(let* ((leaf-char (make-instance 'complex-char :simple-char #\O :color-pair '(:green :black)))
(trunk-char (make-instance 'complex-char :simple-char #\H :color-pair '(:white :black)))
(ground-char (make-instance 'complex-char :simple-char #\i :color-pair '(:green :black)))
(sun-char (make-instance 'complex-char :simple-char #\o :color-pair '(:yellow :black)))
(tree-trunk (rectangle 19 27 5 4 :filled t :char trunk-char))
(upper-crown (triangle 4 29 12 22 12 35 :filled t :char leaf-char))
(lower-crown (triangle 8 29 18 20 18 37 :filled t :char leaf-char))
(tree-crown (merge-shapes upper-crown lower-crown))
(ground (line 23 0 23 80 :char ground-char))
(sun (circle 5 6 2 :char sun-char :filled t)))
(draw-t29-shapes scr (list tree-trunk tree-crown ground sun) t)
;; wait for a keypress
(get-char scr)
;; redraw with squarify nil
(draw-t29-shapes scr (list tree-trunk tree-crown ground sun) nil)
(get-char scr) )))
(defun t30 ()
"Test color pair completion for style parameters."
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
(with-windows ((w1 :height 5 :width 5 :location '(0 0))
(w2 :height 5 :width 5 :location '(0 5))
(w3 :height 5 :width 5 :location '(0 10)))
(setf (background w1) (make-instance 'complex-char :color-pair '(:black :cyan))
(background w2) (make-instance 'complex-char :color-pair '(:black :magenta))
(background w3) (make-instance 'complex-char :color-pair '(:black :white)))
;; the character style contains only one color
;; the fg/bg of the target window is used to complete the color pair.
(let ((s1 (list :attributes '(:bold) :fgcolor :red))
(s2 (list :attributes '(:bold) :bgcolor :yellow)))
(add w1 #\a :y 2 :x 2 :style s1)
(add w2 #\b :y 2 :x 2 :style s1)
(add w3 #\c :y 2 :x 2 :style s2))
(refresh w1)
(refresh w2)
(refresh w3)
(get-char w1))))
;; the ncurses window background property and the color-pair apparently set the same internal variable
;; setting one to nil sets the other to nil.
;; croatoan does not do that. here, the window color if it is set overrides the background property.
;; if a color parameter is directly passed, it overrides both the window color-pair and background.
(defun t31 ()
"Test color pair completion for fgcolor and bgcolor parameters."
(with-screen (scr :input-echoing nil :input-blocking t :cursor-visible nil :enable-colors t)
(add-string scr "default colors" :y 0 :x 0) (refresh scr) (get-char scr)
(setf (color-pair scr) '(:yellow :blue))
(add-string scr "color-pair yellow on blue" :y 1 :x 0) (refresh scr) (get-char scr)
;; text is not printed red on white because the color-pair overrides the background color.
(setf (background scr nil) (make-instance 'complex-char :simple-char #\- :color-pair '(:red :white)))
(add-string scr " background red on white" :y 2 :x 0) (refresh scr) (get-char scr)
(setf (color-pair scr) '(:blue :yellow))
(add-string scr "color-pair blue on yellow" :y 3 :x 0) (refresh scr) (get-char scr)
;; text is not printed red on white because the color-pair overrides the background color.
(setf (background scr nil) (make-instance 'complex-char :simple-char #\space :color-pair '(:white :red)))
(add-string scr " background white on red" :y 4 :x 0) (refresh scr) (get-char scr)
;; now when we set the color-pair to nil, the text is printed in background colors.
(setf (color-pair scr) nil)
(add-string scr "color-pair nil" :y 5 :x 0) (refresh scr) (get-char scr)
;; when we then also set the background to nil, the text is displayed in default white on black.
(setf (background scr nil) nil)
(add-string scr " background nil" :y 6 :x 0) (refresh scr) (get-char scr)
(add-string scr "fgcolor red bgcolor cyan" :y 7 :x 0 :fgcolor :red :bgcolor :cyan) (refresh scr) (get-char scr)
(add-string scr "fgcolor red (bgcolor default)" :y 8 :x 0 :fgcolor :red) (refresh scr) (get-char scr)
(add-string scr "(fgcolor default) bgcolor cyan" :y 9 :x 0 :bgcolor :cyan) (refresh scr) (get-char scr)
(setf (color-pair scr) '(:yellow :blue))
;; the color parameters override the color-pair window property
(add-string scr "color pair yellow on blue, then fgcolor red" :y 10 :x 0 :fgcolor :red) (refresh scr) (get-char scr)
(add-string scr "color pair yellow on blue, then bgcolor cyan" :y 11 :x 0 :bgcolor :cyan) (refresh scr) (get-char scr)
;; the background is ignored because color-pair is set
(setf (background scr nil) (make-instance 'complex-char :simple-char #\. :color-pair '(:white :red)))
(add-string scr "fgcolor red" :y 12 :x 0 :fgcolor :red) (refresh scr) (get-char scr)
(add-string scr "bgcolor cyan" :y 13 :x 0 :bgcolor :cyan) (refresh scr) (get-char scr)
;; when the pair is set to nil, the background is used again.
(setf (color-pair scr) nil)
(add-string scr "fgcolor magenta" :y 14 :x 0 :fgcolor :magenta) (refresh scr) (get-char scr)
(add-string scr "bgcolor cyan" :y 15 :x 0 :bgcolor :cyan) (refresh scr) (get-char scr) ))