#|
Summary


Convert a pointer interface into a selection interface.
 Each point is then available as next or prior.
 Eliminate traversal of a vast void, and never miss
 a point.

Convert "drag and drop" into "select and put"
 by adding pointer button activation commands
 for toggling the press or release of
 the primary pointer button.

Write a list of marked points to a document,
 then later read and apply that list of points
 to restore them for use again.


First, move the Xserver cursor by whatever means available,
 then mark the location for returning the cursor to it later.

* Collect and mark the coordinates
  of the Xserver cursor.

* Select the next/prior sequential point
  and the cursor is moved to it,
  even when points are hidden.

* Activate a pointer button from the buttonboard
  at location of cursor.
  (Independent of points.  Consider adding commands
  for pointer button activation.

* Hide all points.  Show all points
  and the cursor is immediately moved
  to the most recently selected point.

* Remove a visible point when no longer needed,
  then the next point becomes the selected point
  (which also moves the cursor to it).

Showing and hiding points is respected as purposely done,
 rather than automated.  Moving the cursor to each point
 is possible regardless of visibility of the points.

Marking a new point is possible when no points are marked,
 f.e. after removal of final point.  Thereafter, marking
 is restricted to only when the points are visible,
 just like when removing a point.
Thereby, the points remain hidden as requested
 rather than automatically revealed.  There is
 no need of wrestling for an unobtrusive interface
 transparently overlaying the programs.


A collection of Xserver cursor coordinates is retained
 as a foldable list within the 'points.nu property
 of the plist of the root Xwindow for a screen.

The #'points-write.nu command prompts for a document name
 and writes a foldy of the points to that document.
Either add a buttonboard binding for the command,
 or use  Control-t ;  from StumpWM to type the command.

The #'points-read.nu command prompts for a document name
 and reads a foldy of points from that document.
 It then removes all points currently marked,
 applies the new points, and moves the cursor
 to the current point for that foldy.
Either add a buttonboard binding for the command,
 or use  Control-t ;  from StumpWM to type the command.
|#


#|
This emerges in the universe on its own, as all has.
|#


#|
Content outline


* Requirements

* The visual shape for a point

* General function use and naming conventions


* Ancillary functions for a foldable list, a.k.a. foldy.
  (Independent from symbols of this document.)

 * defun fold-one.nu
    Move first item of second half to first half.

 * defun fold-all.nu
    Move all items from second half to first half.

 * defun unfold-one.nu
    Move first item of first half to second half.

 * defun unfold-all.nu
    Move all items from first half to second half.


* CLX root Xwindow plist property 'points.nu

 * Description and access for 'points.nu

 * All functions that directly modify 'points.nu

 * defun points-init.nu
    Initiate 'points.nu for the screen.

 * defun points-drawing.nu
    Initiate the point for the screen.


* Complementary functions.
  These isolate instructions particular to 'points.nu
   and were consistently repeated in other functions.

 * defun points-viewablep.nu
    Determine whether points are viewable.

 * defun points-prioritize.nu
    Highest priority for all points.

 * defun points-draw.nu
    Draw the collected points of current screen.

 * defun points-move-cursor.nu
    Move Xserver cursor to current point.


* Functions for StumpWM hooks.

 * defun points-new-window-hook.nu
    Ensure viewable points are viewable.

    stumpwm:*new-window-hook*:
     A hook called whenever a window is added
     to the window list.  This includes a genuinely
     new window as well as bringing a withdrawn window
     back into the window list.
     (Section 15 of the StumpWM manual)


* Commands for marking and removing points,
  hiding and revealing, and moving cursor to a point.

 * stumpwm:defcommand points-mark.nu
    Place a new point for current screen at cursor.

 * stumpwm:defcommand points-remove.nu
    Remove currently selected point of current screen.

 * stumpwm:defcommand points-reveal-toggle.nu
    Hide or reveal all points.

 * stumpwm:defcommand points-next.nu
    Move cursor to next point in sequence.

 * stumpwm:defcommand points-prior.nu
    Move cursor to prior point in sequence.


* Commands for writing and reading points
  for current screen, for later use.

 * stumpwm:defcommand points-write.nu
   Write all coordinates from 'points.nu to a document.

 * stumpwm:defcommand points-read.nu
   Replace coordinates for 'points.nu from a document.


* Buttonboard bindings for commands
|#


#|#
Requirements.


This was experienced within:

* stumpwm tiled-view manager
  It was a lisp image of SBCL
  (Steel Bank Common Lisp 2.1.0).
  stumpwm.github.io
  sbcl.org

* Common Lisp X Interface (CLX) 0.7.5
  The CLX had the XTEST extension (or similar)
  for activating the pointer in the Xserver.
  github.com/sharplispers/clx

* Xserver
  x.org
|#


#|#
The visual shape for a point.


The hot spot for the Xserver cursor must have clearance
 when activating a pointer button.  A point is drawn
 as a white perimeter of a square with a black mask,
 made from four Xwindows.
    ______        _   __   _
   |  __  |      | | |__| | |
   | |  | |  ==  | |      | |
   | |__| |      | |  __  | |
   |______|      |_| |__| |_|


The left and right sides each have a black background,
 and a subwindow with a black background and a white
 border as thick as the line for the square perimeter.

For example, the left side (though taller than this):

     left
    window              -->   cropped
    ______                    ______
   |  ____|__                |  ____|<-mask
   | |       |               | |    |
   |b| w _   |               |b| w _|<-perimeter
   |l| h| |  |               |l| h| |
   |a| i| |  |               |a| i| |<-black mask
   |c| t|_|  |               |c| t|_|
   |k| e     |               |k| e  |
   | |_______|subwindow      | |____|
   |______|                  |______|

The width of a subwindow is as narrow as the black mask
 because its background becomes the right side
 of the mask for the square perimeter (white).
The height of a subwindow is short enough for the
 top and bottom edges of the window to become the parts
 for the black mask.
The subwindow for the left side is offset from the origin
 by the intended width of the mask, allowing for the
 black background of the window to become the
 top, left, and bottom edges for the black mask.
Similarly for the subwindow of the right side.


The top and bottom sides each have a black background,
 and a subwindow with a white background as wide
 as the sides and as tall as the perimeter height.

    top or bottom
       window
    ____________
   |____________|<-black mask
   |subwindow   |<-white perimeter
   |____________|
   |____________|<-black mask

|#


#|
Hereafter, there be code.
|#

#|#
General function use and naming conventions.


"points": Collective prefix for symbols.

".nu": Arbitrary suffix for preventing name collisions,
       as in "named uniquely" or "now unique".


Trace the source of variables by prefixing a letter
 representing that source.

   defun args prefix:  F for "function"
     let args prefix:  L for "let"
  lambda args prefix:  A for "anonymous function"

Combine A and L prefixes by depth, thereby revealing scope
 and discouraging perplexation.  For example:

                       defun args  ->  F prefix
                 defun > let args  ->  L prefix
  defun > let > let > lambda args  ->  LLA prefix


Favor using the special operator 'multiple-value-call
 instead of the macro 'multiple-value-list for results
 from Common Lisp funicles returning multiple values.
Consider replacing any funicles returning multiple values
 with new funicles returning a list, thereby eliminating
 the use of 'multiple-value-call, too.


The stumpwm source tends to have variables and functions
 prefixed with an "x" when referring to an instance from
 the Common Lisp X Interface library, like an xlib class
 or structure.  For example: xwin.  Do the same.


See also:
* Notes from the documentation for stumpwm and CLX.
|#


#|
Ancillary functions for a foldable list, a.k.a. foldy.

From the independent ancillary funicular foldy functions.
|#


 ;#
(defun
fold-one.nu
(Ffoldy)
"Move first item of second half to first half.

Moves an item only when cdr is non-nil.

Modifies original, but also returns foldy
 as a convenience."
 ;;Ensure there is an item in second half to move.
(and(cdr Ffoldy)
 ;;;Move first item of second half to first half.
(push(pop(cdr Ffoldy))(car Ffoldy)))
 ;;Return foldy as convenience for further use.
Ffoldy)


 ;#
(defun
fold-all.nu
(Ffoldy)
"Move all items from second half to first half.

Moves an item only when cdr is non-nil.

Modifies original, but also returns foldy
 as a convenience."
(do
();no variables
 ;;;Common Lisp #'do is actually "do until non-nil".
 ;;; Stop when no more items to move, t.i. cdr is nil.
((not(cdr Ffoldy)))
 ;;;Move first item of second half to first half.
(push(pop(cdr Ffoldy))(car Ffoldy)))
 ;;Return foldy as convenience for further use.
Ffoldy)


 ;#
(defun
unfold-one.nu
(Ffoldy)
"Move first item of first half to second half.

Moves an item only when car is non-nil.

Modifies original, but also returns foldy
 as a convenience."
 ;;Ensure there is an item in first half to move.
(and(car Ffoldy)
 ;;;Move first item of first half to second half.
(push(pop(car Ffoldy))(cdr Ffoldy)))
 ;;Return foldy as convenience for further use.
Ffoldy)


 ;#
(defun
unfold-all.nu
(Ffoldy)
"Move all items from first half to second half.

Moves an item only when car is non-nil.

Modifies original, but also returns foldy
 as a convenience."
(do
();no variables
 ;;;Common Lisp #'do is actually "do until non-nil".
 ;;; Stop when no more items to move, t.i. car is nil.
((not(car Ffoldy)))
 ;;;Move first item of first half to second half.
(push(pop(car Ffoldy))(cdr Ffoldy)))
 ;;Return foldy as convenience for further use.
Ffoldy)


#|#
CLX root Xwindow plist property 'points.nu


A collection of Xserver cursor coordinates
 is retained as the 'points.nu property
 of the plist of the root Xwindow for a screen.

It is a foldable list for maintaining the current point.
 A foldable list (foldy) is simply a cons of two lists.
   '(() . ())  ==  '(() . nil)  ==  '(())  ==  '(nil)


After the list in the car of the foldy,
 each additional item is a cons of coordinates
 with Xwindows, t.i. (coordinates . Xwindows).

 * Coordinates for the point,
   which is itself a cons of x and y.

     '(x . y)

 * The Xwindows for drawing the point,
   which is itself a cons of paired Xwindows,
   which each is a cons for each pair.

     '((left . right) . (top . bottom))
     == '((left . right) top . bottom)


For example, a foldy with its first item
 as the current item:
  '(
    ;; car:  List for items prior to current item.
    ()

    ;; cadr: Current item.
    ((x1 . y1)
     . ((Xwindow-1-left . Xwindow-1-right)
        . (Xwindow-1-top . Xwindow-1-bottom)))

    ;; cddr: The next item and remnants.
    ((x2 . y2)
     . (Xwindows...))
    ((x3 . y3)
     . (Xwindows...))
   )


  List of points prior to
   current selected point:  car
  Current selected point:   cadr
  Remnants:                 cddr


  Each point.
    Coordinates:              car
              x:       car of car  ==  caar
              y:       cdr of car  ==  cdar

    Xwindows:                 cdr
      left and right:  car of cdr  ==  cadr
                left:  car of cadr
               right:  cdr of cadr

      top and bottom:  cdr of cdr  ==  cddr
                 top:  car of cddr
              bottom:  cdr of cddr
|#


#|#
All functions that directly modify 'points.nu


Initiating:
 points-init.nu

Commands:
 points-mark.nu
 points-remove.nu
 points-next.nu
 points-prior.nu
 points-read.nu
|#


 ;#
(defun
points-init.nu
(Froot)
"Initiate 'points.nu for the screen.

Froot is the root Xwindow of a screen."
 ;;Remove prior Xwindows for points, when existing.
(and
(getf(xlib:window-plist Froot)'points.nu)
(mapc
(lambda(Apoint)"Destroy existing Xwindows of point."
(let((ALxwins(cdr Apoint)))
(when ALxwins
(xlib:destroy-window(caar ALxwins))
(xlib:destroy-window(cdar ALxwins))
(xlib:destroy-window(cadr ALxwins))
(xlib:destroy-window(cddr ALxwins)))))
 ;;;List of points for the screen.
(cdr
(unfold-all.nu
(getf(xlib:window-plist Froot)'points.nu)))))
 ;;Initiate with an empty foldy.
#|?
Using '(() . ()) or '(nil) with setf
 fails to replace former value
 unless former value was only ().
 Might be to do with #'xlib:window-plist.
Using (cons () ()) succeeds.
?|#
(setf
(getf(xlib:window-plist Froot)'points.nu)
(cons()()))
 ;;Return 'points.nu for immediate use.
(getf(xlib:window-plist Froot)'points.nu))


 ;#
(defun
points-drawing.nu
(Fpoint Froot)
"Make drawing instructions for the point for the screen.

Fpoint is a consed pair of coordinates,
 t.i. '(x . y)
Froot is the root Xwindow of a screen.

Create the drawing instructions for the point,
 but without applying the instructions yet.

Return the set for inclusion as cdr of a point
 for the 'points.nu property."
#|
Create four Xwindows centered around the point
 and return the consed set.

Full height of square perimeter with mask is 14 pixels.
Width of white perimeter is 2 pixels.  Black mask
 is one pixel, both inside and outside.  Total is: 4.

The white border of a subwindow for left and right sides
 is same as the perimeter: 2.  The width is one pixel.

The height for a subwindow for left and right sides
 is full height of window minus the twice the mask
 width, and minus twice the border of the subwindow.
 Total: 14 - (2 * 1) - (2 * 2) = 8 pixels.
|#
(let
( ;;;;Each coordinate for point.
(Lx(car Fpoint))
(Ly(cdr Fpoint))
 ;;;;Function for a vertical side.
(Lvertical-side
(lambda(LAorigin LAsub-origin)
"Create the Xwindow for a vertical side.
LAorigin is a cons of the coordinates
 for the origin of the side.
LAsub-origin is a cons of the coordinates
 for the origin of its subwindow.
See diagram in the visual shape notes."
(let*
( ;;;;;;;;The vertical side.
(LALvertical-side
(xlib:create-window :parent Froot
:x(car LAorigin)
:y(cdr LAorigin)
:background
(xlib:alloc-color(xlib:window-colormap Froot)"black")
:width 4 :height 14)))
 ;;;;;;;Add the subwindow to the vertical side.
(xlib:map-window
(xlib:create-window :parent LALvertical-side
:x(car LAsub-origin)
:y(cdr LAsub-origin)
:border
(xlib:alloc-color(xlib:window-colormap Froot)"white")
:border-width 2
:background
(xlib:alloc-color(xlib:window-colormap Froot)"black")
:width 1 :height 8))
 ;;;;;;;Return the vertical side.
LALvertical-side)))
 ;;;;Function for a horizontal side.
(Lhorizontal-side
(lambda(LAorigin)
"Create the Xwindow for a horizontal side.
LAorigin is a cons of the coordinates
 for the origin of the side.
See diagram in the visual shape notes."
(let*
( ;;;;;;;;The horizontal side.
(LALhorizontal-side
(xlib:create-window :parent Froot
:x(car LAorigin)
:y(cdr LAorigin)
:background
(xlib:alloc-color(xlib:window-colormap Froot)"black")
:width 6 :height 4)))
 ;;;;;;;Add the subwindow to the horizontal side.
(xlib:map-window
(xlib:create-window :parent LALhorizontal-side
:x 0 :y 1
:background
(xlib:alloc-color(xlib:window-colormap Froot)"white")
:width 6 :height 2))
 ;;;;;;;Return the vertical side.
LALhorizontal-side))))
 ;;;Create the sides, pair them together,
 ;;; and return the set.
(cons
 ;;;;Left and right sides
(cons
 ;;;;;Left side.
(funcall Lvertical-side
(cons(- Lx 7)(- Ly 7))(cons 1 1))
 ;;;;;Right side.
(funcall Lvertical-side
(cons(+ Lx 3)(- Ly 7))(cons -2 1)))
 ;;;;Top and bottom sides.
(cons
 ;;;;;Top side.
(funcall Lhorizontal-side(cons(- Lx 3)(- Ly 7)))
 ;;;;;Bottom side.
(funcall Lhorizontal-side(cons(- Lx 3)(+ Ly 3)))))))


#|
Complementary functions.


These isolate instructions particular to 'points.nu
 and were consistently repeated in other functions.
Intended for use in functions in only this document.
 Remove any that are used only once.

Movement of the Xserver cursor is separated from drawing
 the points in order to respect purposefully hiding them.

Prioritizing has no affect on viewability, but does
 ensure visibility for mapped Xwindows, therefore
 it is safe to prioritize unmapped Xwindows.

Determining viewability is shared by many tasks,
 such as marking a point, removing a point,
 and toggling the hiding and revealing of all points.
|#


 ;#
(defun
points-viewablep.nu
();no options
"Determine whether points are viewable.

This test is for determining purposefully viewable
 or hidden points, t.i. mapped or unmapped by
 the #'points-reveal-toggle.nu command.

The Xserver declares the word viewable to mean mapped.
 Points might remain unseen when they have less priority."
(let
( ;;;;The Xwindows [cdr]
 ;;;; of the current point [cadr]
 ;;;; of all points of current screen [getf].
(Lcurrent-point-xwins
(cdr
(cadr
(getf
(xlib:window-plist
(stumpwm:screen-root(stumpwm:current-screen)))
'points.nu)))))
(and
 ;;;;Ensure the Xwindows of current point exist...
Lcurrent-point-xwins
 ;;;...and that its Xwindows are viewable.
(eq :viewable
 ;;;;Just the left part [caar] for current point.
(xlib:window-map-state
(caar Lcurrent-point-xwins))))))


 ;#
(defun
points-prioritize.nu
();no options
"Highest priority for all points.

Re-prioritize order of all points
 without changing viewability."
 ;;Nothing to do unless current point exists.
(when
(cadr
(getf
(xlib:window-plist
(stumpwm:screen-root(stumpwm:current-screen)))
'points.nu))
(let*
( ;;;;The root Xwindow of current screen.
(Lroot
(stumpwm:screen-root(stumpwm:current-screen)))
 ;;;;All points for current screen.
(Lpoints
(getf(xlib:window-plist Lroot)'points.nu))
 ;;;;Function for prioritizing the Xwindows of a point.
(Lprioritize-xwins
(lambda(LApoint)
"Highest priority for the Xwindows of point."
(let
( ;;;;;;;;Xwindows for the point,
 ;;;;;;;; or nil when non-existent.
(LALxwins(cdr LApoint)))
(and LALxwins
(setf
(xlib:window-priority(caar LALxwins)):above
(xlib:window-priority(cdar LALxwins)):above
(xlib:window-priority(cadr LALxwins)):above
(xlib:window-priority(cddr LALxwins)):above))))))
 ;;;Update priority of prior points.
 ;;; Reversed so most immediate prior point
 ;;; is prioritized more than others.
(mapc Lprioritize-xwins(reverse(car Lpoints)))
 ;;;Update priority of later points, then current point.
 ;;; Reversed so most immediate next point
 ;;; is prioritized more than others,
 ;;; and current point prioritized more than all.
(mapc Lprioritize-xwins(reverse(cdr Lpoints))))))


 ;#
(defun
points-draw.nu
();no options
"Draw the collected points of current screen.

Points are prioritized from current point outward,
 t.i. current, next/prior, next-after/prior-after,
 and so on."
 ;;Draw all points and prioritize current point.
(let*
( ;;;;The root Xwindow for current screen.
(Lroot
(stumpwm:screen-root(stumpwm:current-screen)))
 ;;;;Function for prioritizing and drawing.
(Lprioritize-draw-xwins
(lambda(LApoint)"Prioritize and map point."
 ;;;;;;Point already has instructions for drawing [cdr],
 ;;;;;; or set them.
(or(cdr LApoint)
(setf(cdr LApoint)
(points-drawing.nu
(car LApoint)
(stumpwm:screen-root(stumpwm:current-screen)))))
(let
( ;;;;;;;;The Xwindows for point.
(LALxwins(cdr LApoint)))
(setf
(xlib:window-priority(caar LALxwins)):above
(xlib:window-priority(cdar LALxwins)):above
(xlib:window-priority(cadr LALxwins)):above
(xlib:window-priority(cddr LALxwins)):above)
(xlib:map-window(caar LALxwins))
(xlib:map-window(cdar LALxwins))
(xlib:map-window(cadr LALxwins))
(xlib:map-window(cddr LALxwins))))))
 ;;;Points prior to current point.
 ;;; Reversed so most immediate prior point
 ;;; is prioritized more than others.
(mapc Lprioritize-draw-xwins
(reverse
(car(getf(xlib:window-plist Lroot)'points.nu))))
 ;;;Current and later points.
 ;;; Reversed so most immediate next point
 ;;; is prioritized more than others,
 ;;; and current point prioritized more than all.
(mapc Lprioritize-draw-xwins
(reverse
(cdr(getf(xlib:window-plist Lroot)'points.nu))))))


 ;#
(defun
points-move-cursor.nu
();no options
"Move Xserver cursor to current point.

Re-prioritize order of all points
 without changing viewability."
 ;;Nothing to do unless current point exists.
(when
(cadr
(getf
(xlib:window-plist
(stumpwm:screen-root(stumpwm:current-screen)))
'points.nu))
(let*
( ;;;;The root Xwindow of current screen.
(Lroot
(stumpwm:screen-root(stumpwm:current-screen)))
 ;;;;Coordinates [car] for current point [cadr].
(Lxy
(car
(cadr(getf(xlib:window-plist Lroot)'points.nu)))))
 ;;;Move cursor to current point.
(xlib:warp-pointer Lroot(car Lxy)(cdr Lxy))
 ;;;Prioritize all points just in case they are viewable.
(points-prioritize.nu))))


#|
Functions for StumpWM hooks.
|#


 ;#
(defun
points-new-window-hook.nu
#|?
Mystery option, undocumented in manual.
 Research the StumpWM source code
 for *new-window-hook*.
 Okay for now as there is no need for it.
?|#
(Fwhats-the-fantasy-for-this?)
"Ensure viewable points are viewable.

Starting a program might create a new Xwindow
 with higher priority than the points.  The points
 are technically still :viewable even when obscured.

Make points have highest priority after any window
 has been given highest priority.
Prioritized without mapping, thereby respecting
 purposeful hiding of points.

Intended for 'stumpwm:new-window-hook."
 ;;Prioritize all points, but without mapping
 ;; just in case they were purposefully hidden.
(points-prioritize.nu))


 ;Add hook for 'stumpwm:*new-window-hook*.
 ; Note the StumpWM manual has an error
 ; in one of its examples about add-hook.
 ; Be sure to use no ' before its own hook names.
(stumpwm:add-hook stumpwm:*new-window-hook*
'points-new-window-hook.nu)


#|
Commands for marking and removing points,
 hiding and revealing, and moving cursor to a point.
|#


 ;#
(stumpwm:defcommand
points-mark.nu
();no options
();no prompt
"Place a new point for current screen at cursor.

Use the cursor coordinates for a new point
 of current screen.  The new point becomes
 the selected point.

Mark a new point only when points are shown,
 or when there are no other points.
See #'points-reveal-toggle.nu for revealing points."
#|
Draw a shape at the coordinates of the Xserver cursor.

Store coordinates and its shape in 'points.nu
 of the root Xwindow for the current screen.

Irrelevant when there is already a point,
 t.i. no movement of cursor, no change of selection.
 Also the points must be viewable rather than hidden.
 Otherwise, this command is ignored.
|#
(when
 ;;;Mark a point only when points are already visible,
 ;;; or no existing points.
(let
( ;;;;;All points, if any, for current screen.
(Lpoints
(getf
(xlib:window-plist
(stumpwm:screen-root(stumpwm:current-screen)))
'points.nu)))
(or
 ;;;;;Either there is no 'points.nu for screen...
(not Lpoints)
 ;;;;; ...or there are no points...
(equal'(() . ())Lpoints)
 ;;;;; ...or the points are viewable.
(points-viewablep.nu)))
 ;;;Confirm cursor coordinates are a new point.
(let*
( ;;;;;The root Xwindow for current screen.
(Lroot
(stumpwm:screen-root(stumpwm:current-screen)))
 ;;;;;Coordinates of current point.
 ;;;;; Remove extra info later.
(Lpoint
(multiple-value-call'list
(xlib:global-pointer-position stumpwm:*display*)))
 ;;;;;The current points for testing.  Initiate as needed.
(Lpoints
(or
(getf(xlib:window-plist Lroot)'points.nu)
(points-init.nu Lroot)))
 ;;;;;Function for comparing coordinates of points.
(Lequal-point
(lambda(LApoint LApoint-xwin)
"Compare point with coordinates of point-xwin data.
Point is a cons of x and y.
Point-xwin is a cons of a point and an Xwindow.
Return t when coordinates are same."
 ;;;;;;;#'equal compares list items, like in a cons.
(equal LApoint(car LApoint-xwin)))))
 ;;;;Determine current cursor point, ignore extra info.
(setq Lpoint(cons(car Lpoint)(cadr Lpoint)))
 ;;;;Add point only when non-existing.
(unless
(or
 ;;;;;;Test prior points.
(member Lpoint(car Lpoints):test Lequal-point)
 ;;;;;;Test current point and later points.
(member Lpoint(cdr Lpoints):test Lequal-point))
 ;;;;;Make the current point the prior point.
(fold-one.nu
(getf(xlib:window-plist Lroot)'points.nu))
 ;;;;;Add new point as the currently selected point.
(push
(cons Lpoint(points-drawing.nu Lpoint Lroot))
(cdr
(getf(xlib:window-plist Lroot)'points.nu)))
 ;;;;;Draw the point.
(points-draw.nu)))))


 ;#
(stumpwm:defcommand
points-remove.nu
();no options
();no prompt
"Remove currently selected point of current screen.

Make next point in sequence the current point
 and move the cursor to it.

Remove the point only when points are shown.
See #'points-reveal-toggle for revealing points."
(let*
( ;;;;The root Xwindow for current screen.
(Lroot
(stumpwm:screen-root(stumpwm:current-screen)))
 ;;;;The Xwindows [cdr] of current point [cadr], or nil.
(Lxwins
(cdr(cadr(getf(xlib:window-plist Lroot)'points.nu)))))
 ;;;Remove current point only when points are viewable.
(when(points-viewablep.nu)
 ;;;;Destroy the Xwindows for current point.
(xlib:destroy-window(caar Lxwins))
(xlib:destroy-window(cdar Lxwins))
(xlib:destroy-window(cadr Lxwins))
(xlib:destroy-window(cddr Lxwins))
 ;;;;Remove current point [car of cdr] from the list.
(pop(cdr(getf(xlib:window-plist Lroot)'points.nu)))
 ;;;;Ensure there is still a current point.
(and
 ;;;;;There is no current point,
 ;;;;; and no later points...
(not
(cdr(getf(xlib:window-plist Lroot)'points.nu)))
 ;;;;; ...and there are prior points.
(car(getf(xlib:window-plist Lroot)'points.nu))
 ;;;;;So, make the prior point the current point.
(unfold-one.nu
(getf(xlib:window-plist Lroot)'points.nu)))
 ;;;;Move cursor to current point, when any.
(points-move-cursor.nu))))


 ;#
(stumpwm:defcommand
points-reveal-toggle.nu
();no options
();no prompt
"Hide or reveal all points.

Move cursor to current point when revealed."
#|
Determine whether points are viewable
 by simply testing current point.
|#
 ;;Nothing to do when no points.
(when
 ;;;Test for current point.
(cadr
(getf
(xlib:window-plist
(stumpwm:screen-root(stumpwm:current-screen)))
'points.nu))
 ;;;Points do exist.
(let*
( ;;;;;The points for current screen.
(Lpoints
(getf
(xlib:window-plist
(stumpwm:screen-root(stumpwm:current-screen)))
'points.nu)))
 ;;;;Determine whether points are viewable.
(if(points-viewablep.nu)
 ;;;;;Hide the points.
 ;+++;Could extract as a command, but no interest.
 ;+++; Toggle fits comfortably with overall experience.
(let
( ;;;;;;;Function for hiding a point.
(LLhide-xwins
(lambda(LLApoint)"Hide point."
(let((LLALxwins(cdr LLApoint)))
(xlib:unmap-window(caar LLALxwins))
(xlib:unmap-window(cdar LLALxwins))
(xlib:unmap-window(cadr LLALxwins))
(xlib:unmap-window(cddr LLALxwins))))))
 ;;;;;;Hide the prior points.
(mapc LLhide-xwins(car Lpoints))
 ;;;;;;Hide the current point and later points.
(mapc LLhide-xwins(cdr Lpoints)))
 ;;;;;Otherwise, reveal points and place cursor.
(progn
(points-draw.nu)
(points-move-cursor.nu))))))


 ;#
(stumpwm:defcommand
points-next.nu
();no options
();no prompt
"Move cursor to next point in sequence."
 ;;Nothing to do when no points.
(when
 ;;;Test for current point.
(cadr
(getf
(xlib:window-plist
(stumpwm:screen-root(stumpwm:current-screen)))
'points.nu))
 ;;;Maybe fold to next item,
 ;;; or maybe wrap to beginning.
(let*
( ;;;;;The root window.
(Lroot
(stumpwm:screen-root(stumpwm:current-screen)))
 ;;;;;The points.
(Lpoints
(getf(xlib:window-plist Lroot)'points.nu)))
(cond
( ;;;;;When next item exists...
(cddr Lpoints)
 ;;;;;...fold to it.
(fold-one.nu
(getf(xlib:window-plist Lroot)'points.nu)))
( ;;;;;Otherwise, when prior items exist...
(car Lpoints)
 ;;;;;...unfold all.
(unfold-all.nu
(getf(xlib:window-plist Lroot)'points.nu))))
 ;;;;Move cursor to current point, even when no change.
(points-move-cursor.nu))))


 ;#
(stumpwm:defcommand
points-prior.nu
();no options
();no prompt
"Move cursor to prior point in sequence."
 ;;Nothing to do when no points.
(when
 ;;;Test for current point.
(cadr
(getf
(xlib:window-plist
(stumpwm:screen-root(stumpwm:current-screen)))
'points.nu))
 ;;;Maybe unfold to prior item,
 ;;; or maybe wrap to beginning.
(let*
( ;;;;;The root window.
(Lroot
(stumpwm:screen-root(stumpwm:current-screen)))
 ;;;;;The points.
(Lpoints
(getf(xlib:window-plist Lroot)'points.nu)))
(cond
( ;;;;;When prior item exists...
(car Lpoints)
 ;;;;;...unfold to it.
(unfold-one.nu
(getf(xlib:window-plist Lroot)'points.nu)))
( ;;;;;Otherwise, when next items exist...
(cddr Lpoints)
 ;;;;;...fold to final item.
(unfold-one.nu
(fold-all.nu
(getf(xlib:window-plist Lroot)'points.nu)))))
 ;;;;Move cursor to current point, even when no change.
(points-move-cursor.nu))))


#|
Commands for writing and reading points
 for current screen, for later use.

For use with the #'stumpwm:colon command
 available by default within StumpWM
 as  Control-t ;  (yes, a semicolon).
|#


 ;#
(stumpwm:defcommand
points-write.nu
(Fwrite-to-doc)
((:string
"Write all points of current screen
 to document: "))
"Write all coordinates from 'points.nu to a document.

Write a foldy of the points for the current screen
 to the specified document.

Fwrite-to-doc is text for a path (t.i. directories)
 and a document name.

For example, save to a document named a-doc
 of the /tmp/ directory:

   /tmp/a-doc

For whatever reason, the login directory
 can be abbreviated to just a ~.  That means:

   /home/account-name/.stumpwm.d/custom/another-doc

 can be represented as:

   ~/.stumpwm.d/custom/another-doc

Path must already exist.  Document is created
 when non-existent.  Append, no overwrite.


The coordinates of all points for current screen
 are gathered.  No Xwindows because they can be
 recreated, so the value reduces to a foldy
 of only coordinates.

For example, a foldy that has yet to be folded:
   (NIL
    (x1 . y1)
    (x2 . y2)
   ; and so forth...
   )

A folded list remains folded, wherewith the point
 current at time of writing the value is maintained.


Supplementary to the #'points-read.nu command."
#|
This is just a basic convenience function
 that simply writes the value of a variable
 to a document.  Basicly:

   ; Safely open/close the specified document.
  (with-open-file
  (the-stream-name the-doc-path
  :direction :output
  :if-does-not-exist :create
  :if-exists :append)

   ;; Format the text for writing into document,
   ;; t.i. into the "stream of text" for it.
  (format the-stream-name

   ;;; A couple of blank lines within the double quotes.
   ;;; The ~s is the placeholder for the object, f.e. list.
  "

  ~s"

   ;;; Then do whatever is needed for creating the object.
  ...))
|#
#|+
Consider the #'ensure-directories-exist function (CLHS)
 for creating any nonexisting directories of path.
For now, directories must exist.
+|#
(cond
( ;;;;Ensure directories already exist, without creating.
(not
(probe-file
(directory-namestring Fwrite-to-doc)))
(stumpwm:message
"Nonexistent directory: ~s
No access to document."
(directory-namestring Fwrite-to-doc)))
( ;;;;Otherwise, attempt to write to file.
t
(with-open-file
 ;;;;;Name temporary stream for doc as WOFwrite-to-doc.
 ;;;;;  Then specify some options for opening.
(WOFwrite-to-doc Fwrite-to-doc
 ;;;;;;Only write to the doc, no need for reading.
:direction :output
 ;;;;;;Create when non-existent.
 ;;;;;; Append rather than overwrite.
:if-does-not-exist :create
:if-exists :append)
 ;;;;;Write only the coordinates of the points,
 ;;;;; No need for the Xwindows, they can be recreated.
(format WOFwrite-to-doc
 ;;;;;;Recall ~% is a newline for the #'format function,
 ;;;;;; but a newline within the double quotes works, too.
 ;;;;;; The ~s is a placeholder for an object,
 ;;;;;; t.i. the list of coordinates.
"

~s"
(let
( ;;;;;;;;The points for only the current screen.
(Lpoints-foldy
(getf
(xlib:window-plist
(stumpwm:screen-root(stumpwm:current-screen)))
'points.nu)))
 ;;;;;;;Two halves for the 'points.nu foldable list.
(cons
(and
 ;;;;;;;;;Nil is an empty list, so just return it as is.
(car Lpoints-foldy)
 ;;;;;;;;;Otherwise, create a new list [mapcar]
 ;;;;;;;;; with the coordinates of each point ['car]
 ;;;;;;;;; from the list in first half of foldy [car].
(mapcar'car(car Lpoints-foldy)))
(and
 ;;;;;;;;;Similarly for second half of foldy [cdr].
(cdr Lpoints-foldy)
(mapcar'car(cdr Lpoints-foldy))))))
 ;;;Provide a message as confirmation.
(stumpwm:message
"Appended points to:
 ~s"
Fwrite-to-doc)))))


 ;#
(stumpwm:defcommand
points-read.nu
(Fread-from-doc)
((:string
"Remove points from current screen
 and mark new points from document: "))
"Replace points for 'points.nu from a document.

Read a foldy of coordinates from the specified document.
 Remove all points marked for current screen,
 mark the new points, then move the Xserver cursor
 to the current point from that foldy.

Fread-from-doc is text for a path (t.i. directories)
 and a document name.

For example, save to a document named a-doc
 of the /tmp/ directory:

   /tmp/a-doc

For whatever reason, the login directory
 can be abbreviated to just a ~.  That means:

   /home/account-name/.stumpwm.d/custom/another-doc

 can be represented as:

   ~/.stumpwm.d/custom/another-doc

Supplementary to the #'points-write.nu command."
#|
When editing the points in a document
 to be read and applied by this command,
 note the points are without their Xwindows,
 but are still in a foldable list.
 The Xwindows will be recreated when needed.

Review the description of points.nu property
 to compare the differences.


Recall:
 * A foldy begins as a pair of lists: (() . ())
 * () is the same as NIL
 * A "proper" list is just an abbreviation
   for the same list consed with NIL.

     (item)  ==  (item . nil)  ==  (item . ())

     (())  ==  (() . NIL)  ==  (() . ())


For example, consider when everything
 is in the second half of the foldy.

   (NIL
    (100 . 100) (200 . 100) (300 . 100) (400 . 100))

Or, some points are in the first half of the foldy:

   (((100 . 100) (200 . 100))
    (300 . 100) (400 . 100))

Or, everything is in the first half of the foldy:

   (((100 . 100) (200 . 100) (300 . 100) (400 . 100)))


Otherwise, consider writing the current list of points
 with the #'points-write.nu command.
|#
(cond
( ;;;;Nothing to do when no path, t.i. when NIL.
(not Fread-from-doc)
(stumpwm:message
"No document name given, or command was canceled."))
( ;;;;Document needs to exist.
(not(probe-file Fread-from-doc))
(stumpwm:message
"No existing document: ~s"
Fread-from-doc))
( ;;;;Otherwise, attempt to load value from document.
t
(let
( ;;;;;;The root Xwindow for current screen.
(Lroot
(stumpwm:screen-root(stumpwm:current-screen)))
 ;;;;;;Read first parenthesized form from document.
(Lnew-value
(with-open-file
(WOFread-from-doc Fread-from-doc
 ;;;;;;Only read from the doc, no need for writing.
:direction :input)
(read WOFread-from-doc))))
 ;;;;;Properly initiate 'points.nu property.
(points-init.nu Lroot)
 ;;;;;Replace the points in 'points.nu for current screen.
(setf
(getf(xlib:window-plist Lroot)'points.nu)
 ;;;;;;Prepare the points in the foldy by making
 ;;;;;; a list with each point, for each half.
(cons
(and
 ;;;;;;;;NIL is an empty list, so just return it as is.
(car Lnew-value)
 ;;;;;;;;Otherwise, create a new list [mapcar]
 ;;;;;;;; by putting each point in its own list ['list]
 ;;;;;;;; from the list in first half of foldy [car].
(mapcar'list(car Lnew-value)))
(and
 ;;;;;;;;Similarly for second half of foldy.
(cdr Lnew-value)
(mapcar'list(cdr Lnew-value))))))
 ;;;;Display points for confirming success.
(points-draw.nu)
(points-move-cursor.nu)
(stumpwm:message
"Read and applied points from:
 ~s"
Fread-from-doc))))


#|#
Bindings are for the numeric pad.
 Consider coordinating with commands
 for pointer button activation.


Use the #'stumpwm:describe-key command
 to discover the names of the buttons.
 Default keybinding for it is Control-t h k,
 as revealed with help (Control-t ?).

For whatever reason, the numbers were available
 with the Shift button, f.e. Shift-7 is "KP_7"
 and 7 is "KP_Home".


  c = / *
  7 8 9 -                M R
  4 5 6 +         =>     P 1 N  T
  1 2 3 enter             1t 3
  _0_ . enter

 "0" is                 "M" is "points-mark".
  double-wide.          "R" is "points-remove".
 "enter" is             "P" is "points-previous".
  double-tall.          "N" is "points-next".
 "c" is "clear",        "T" is "points-reveal-toggle".
  but is Num_Lock.      "1" activates primary button.
                        "1t" toggles primary button.
                        "3" is the secondary button.

See also:
* Numeric pad layout for a buttonboard.
|#


 ;Mark a point at cursor position.
(stumpwm:define-key stumpwm:*top-map*
(stumpwm:kbd"KP_7")"points-mark.nu")

 ;Next point in series.
(stumpwm:define-key stumpwm:*top-map*
(stumpwm:kbd"KP_6")"points-next.nu")

 ;Prior point in series.
(stumpwm:define-key stumpwm:*top-map*
(stumpwm:kbd"KP_4")"points-prior.nu")

 ;Hide or reveal the points.
(stumpwm:define-key stumpwm:*top-map*
(stumpwm:kbd"S-KP_Add")"points-reveal-toggle.nu")

 ;Remove currently selected point.
(stumpwm:define-key stumpwm:*top-map*
(stumpwm:kbd"KP_8")"points-remove.nu")


#|
Quick reload for trying out new changes.
 Consider editing a copy.
|#

#|
(stumpwm:defcommand
reload.points
();no options
();no prompt
"Reload points-later.lisp.html document."
#|
The #'stumpwm:message function uses the Common Lisp
 #'format function (CLHS 22.4).

The ~s is for printing an object, like quoted text.
 See section 22.3.11 Examples of format (CLHS) for more.
|#
(let
( ;;;;Be sure to change path or document name
 ;;;; after making a new copy, when needed.
(Ldoc
"~/.stumpwm.d/custom/points-later.lisp.html"))
 ;;Load the lisp document.
(load Ldoc)
 ;;Provide a message as confirmation.
(stumpwm:message
"Loaded:
 ~s"
Ldoc)))


 ;Bind load command for quick reload.
(stumpwm:define-key stumpwm:*top-map*
(stumpwm:kbd"F19")"reload.points")
|#