;; virtual-door.gwm --- Doors for the virtual screen in "virtual.gwm"
;;
;; Author: Anders Holst  (aho@sans.kth.se)  
;; Copyright (C) 1996  Anders Holst
;; Version: virtual-1.0
;; Last change: 31/7 1996
;;
;; This file is copyrighted under the same terms as the rest of GWM
;; (see the X Inc license for details). There is no warranty that it
;; works. 
;;
;; --------------------------------------------------------------------- 
;;
;; This file defines "doors" on the virtual screen, ie. buttons that
;; when pressed moves the real screen somewhere on the virtual screen.
;; The position to move to can be fixed, or given by an expression.
;;
;; It also defines some very simple "door managing" functions, to add
;; or remove doors dynamically.
;;  '(add-door NAME)' adds a door to the next door-less area.
;;  '(add-door NAME 'free)' adds a door to an area free from windows.
;;  '(maybe-add-door NAME)' adds a door (to a window-free area) only
;; if a door of that name does not exist.
;;  '(goto-door NAME)' moves through a door.
;;  '(remove-door NAME)' removes a door. 
;;
;; There are some functions dealing with automatic generation and
;; removal of doors, suitable to call in menus for example. The doors
;; will have names starting with 'door-auto-string' and a number.
;;  '(fresh-door)' creates a new automatic door in an empty area.
;;  '(clean-doors)' removes automatic doors for empty areas.
;;  '(restore-doors)' adds automatic doors for non-empty areas.
;; Also, if you call '(make-free-area-door)' when you set up your
;; initial doors, you will get a "magic" door which when used
;; creates a new door in place of itself and then moves.
;; 


(declare-screen-dependent
  initial-doors
  door-font
  door-background
  door-foreground
  door-borderwidth
  door-xsize
  door-ysize
  door-mgr-dir-horiz
  door-mgr-dir-len
  door-mgr-mdir-horiz
  door-mgr-mdir-len
  door-mgr-xpos
  door-mgr-ypos
  door-mgr-tile
  door-mgr
  door-mgr-no-outer-border
  door-context
  door-auto-number
  door-auto-string
  door-free-area-string
  door-free-area-color
  door-free-area-cleans
)

;;
;;    USER CUSTOMIZABLE VARIABLES
;;    ---------------------------  
;;    Adjust these in your own profile
;;
(for screen (list-of-screens)
     (defaults-to 
       initial-doors ()               ; Doors to create on startup
       door-font (font-make "8x13")   ; Font in door buttons                
       door-background white          ; Background color of door buttons
       door-foreground black          ; Foreground color of door buttons
       door-borderwidth 2             ; Border width of door buttons
       door-xsize 90                  ; Door button size
       door-ysize 16                  ;  - '' -
                               
       door-mgr-dir-horiz t    ; Controls mapping of doors on virtual screen,
       door-mgr-dir-len 2      ; e.g. two screenfulls in a (horizontal) row.
       door-mgr-mdir-horiz t   ; Controls position of door buttons, e.g.
       door-mgr-mdir-len 2     ; place two buttons in each (horizontal) row.
       door-mgr-xpos 0         ; Upper left corner of door manager
       door-mgr-ypos 0         ;  - '' -
       door-mgr-tile t         ; tile of empty positions, t = transparent 
       door-mgr-no-outer-border t  ; Leave outermost borders to decoration
       door-context ()         ; p-list of customizations per door name

       door-auto-string "Area "       ; Name prefix of auto-generated doors
       door-free-area-string "Free Area" ; Name of magic free area door
       door-free-area-color ()        ; Special color of free area door
       door-free-area-cleans t        ; Let free area door clean empty doors
       )
)


(for screen (list-of-screens)
     (setq door-mgr '(() () () ()))
     (setq door-auto-number 0)
)

(setq door-fsm
  (fsm-make 
    (state-make
      (on (buttonpress any alone)
      (with (
          pos (# 'pos wob-property)
          action (# 'action wob-property)
          lst (if (and pos (= (type pos) 'list))
            pos
            (= (type pos) 'quoted-expr)
            (eval (eval pos))
            ()))
        (if lst
          (virtual-move-to (# 0 lst) (# 1 lst)))
        (eval action)
      ))
)))

(defun door-make (name xpos ypos gotopos)
  (process-events)
  (with (background door-background
         foreground door-foreground
         borderpixel door-foreground
         borderwidth door-borderwidth
         menu-min-width door-xsize
         menu-max-width door-xsize
         bar-min-width door-ysize
         bar-max-width door-ysize
         direction vertical
         reenter-on-opening ())
    (place-menu "door"
                (with (borderwidth 0
                       fsm door-fsm
                       property (+ (list 'pos gotopos) property)) 
                  (menu-make 
                   (bar-make
                    ()
                    (plug-make
                     (label-make name door-font))
                    ())))
                xpos ypos)))

(defun door-make-plug (name gotopos)
  (with (background door-background
      foreground door-foreground
      tile ()
      borderwidth 0
      bar-min-width door-xsize
      bar-max-width door-xsize
      context (# (atom name) door-context)
      door-action ()
      door-icon ()
      property (+ (list 'pos gotopos 'name name) property)
      fsm door-fsm)
    (with context
      (if door-action 
          (setq property (+ (list 'action door-action) property)))
      (bar-make 
        (with (bar-min-width door-ysize
               bar-max-width door-ysize
               property ()
               fsm ())
          (bar-make 
            ()
            (plug-make
             (if door-icon
                 door-icon
               (label-make name door-font))
             )
            ()))))))

(defun door-make-space ()
  (with (background door-background
         foreground door-foreground
         borderwidth 0
         bar-min-width door-xsize
         bar-max-width door-xsize
         tile ()
         fsm ())
    (bar-make 
      (with (bar-min-width door-ysize
             bar-max-width door-ysize
             tile door-mgr-tile)
        (bar-make )))))

(defun door-make-vborder (ele1 ele2)
  (with (background door-foreground
         borderwidth 0
         bar-min-width door-borderwidth
         bar-max-width door-borderwidth
         tile (if (and (eq door-mgr-tile t)
                       (not (or ele1 ele2)))
                  t)
         fsm ())
    (bar-make )))

(defun door-make-hborder-aux (len tl)
  (with (bar-min-width len
         bar-max-width len
         tile tl)
    (list (bar-make ))))

(defun door-make-hborder (lst i1 i2 step num)
  (with (background door-foreground
         borderwidth 0
         bar-min-width door-borderwidth
         bar-max-width door-borderwidth
         tile ()
         fsm ())
    (if (eq door-mgr-tile t)
        (with (tlst (list-make num)
               blst ()
               n 1
               i 0)
          (while (< i num)
            (## i tlst (not (or (# i1 lst) (# i2 lst))))
            (setq i (+ i 1))
            (setq i1 (+ i1 step))
            (setq i2 (+ i2 step)))
          (setq i 0)
          (while (< i num)
            (if (and (< (+ i 1) num) (= (# i tlst) (# (+ i 1) tlst)))
                (setq n (+ n 1))
              (not (# i tlst))
                (progn
                  (setq blst (+ blst (door-make-hborder-aux 
                                      (+ (* n (+ door-xsize
                                                 door-borderwidth))
                                         (if (and (= (+ i 1) n)
                                                  door-mgr-no-outer-border)
                                             (- door-borderwidth) 0)
                                         (if (and (= (+ i 1) num)
                                                  door-mgr-no-outer-border)
                                             (- door-borderwidth) 0)
                                         door-borderwidth)
                                      ())))
                  (setq n 1))
                (progn
                  (setq blst (+ blst (door-make-hborder-aux
                                      (+ (* n (+ door-xsize
                                                 door-borderwidth))
                                         (if (and (= (+ i 1) n)
                                                  (not door-mgr-no-outer-border))
                                             door-borderwidth 0)
                                         (if (and (= (+ i 1) num)
                                                  (not door-mgr-no-outer-border))
                                             door-borderwidth 0)
                                         (- door-borderwidth))
                                      t)))
                  (setq n 1)))
            (setq i (+ i 1)))
          (apply bar-make blst))
        (bar-make ))))

(defun door-mgr-show ()
  (process-events)
  (if (and door-mgr
           (# 0 door-mgr)
           (wob-is-valid (# 0 door-mgr)))
      (with (wob (# 0 door-mgr)
             xpos (# 1 door-mgr)
             ypos (# 2 door-mgr))
        (setq xpos (if (and xpos (< xpos 0))
                       (+ wob-x window-client-x window-client-borderwidth 
                          (- screen-width) (width wob))
                     (+ wob-x window-client-x window-client-borderwidth)))
        (setq ypos (if (and ypos (< ypos 0))
                       (+ wob-y window-client-y window-client-borderwidth
                          (- screen-height) (height wob))
                     (+ wob-y window-client-y window-client-borderwidth)))
        (## 1 door-mgr xpos)
        (## 2 door-mgr ypos)
        (if (wob-is-valid wob-parent)
            (delete-window wob-parent))))
  (if (and door-mgr
           (# 3 door-mgr)
           (> (door-mgr-find-last (# 3 door-mgr)) 0))
      (with (background door-background
             foreground door-foreground
             borderpixel door-foreground
             bar-separator 0
             plug-separator 0
             borderwidth 0
             direction vertical
             reenter-on-opening ()
             bar-list (door-mgr-construct-bar-list (# 3 door-mgr))
             mgr (apply menu-make bar-list)
             xpos (or (# 1 door-mgr) (# 1 (## 1 door-mgr door-mgr-xpos)))
             ypos (or (# 2 door-mgr) (# 2 (## 2 door-mgr door-mgr-ypos)))
             xpos (if (< xpos 0)
                      (- (+ screen-width xpos) (with (wob (menu-wob mgr))
                                                 (width wob)))
                    xpos)
             ypos (if (< ypos 0)
                      (- (+ screen-height ypos) (with (wob (menu-wob mgr))
                                                  (height wob)))
                    ypos))
        (## 0 door-mgr (menu-wob mgr))
        (place-menu "door-mgr"
                    mgr
                    xpos ypos))))
  
(defun door-mgr-find-last (lst)
  (with (i (- (length lst) 1))
    (while (and (> i -1) (not (# i lst)))
      (setq i (- i 1)))
    (+ i 1)))

(defun door-mgr-remove-nil (lst)
  (with (n 0
         res ()
         i 0)
    (for e lst (if (not e) (setq n (+ n 1))))
    (if (= n 0)
        lst
      (progn
        (setq res (list-make (- (length lst) n)))
        (for e lst (if e (progn (## i res e) (setq i (+ i 1)))))
        res))))

(defun door-mgr-construct-bar-list (door-lst)
  (with (num (door-mgr-find-last door-lst)
         rows (if door-mgr-mdir-horiz
                  (+ (/ (- num 1) door-mgr-mdir-len) 1)
                (min num door-mgr-mdir-len))
         cols (if door-mgr-mdir-horiz
                  (min num door-mgr-mdir-len)
                (+ (/ (- num 1) door-mgr-mdir-len) 1))
         step (if door-mgr-mdir-horiz
                  1 door-mgr-mdir-len)
         bstep (if door-mgr-mdir-horiz
                  door-mgr-mdir-len 1)
         len (+ (* 2 rows) 1)
         lst (list-make len)
         i 1
         n 0)
    (if (and (> door-borderwidth 0)
             (not door-mgr-no-outer-border))
        (## 0 lst (door-make-hborder door-lst (- bstep) 0 step cols)))
    (while (< i len)
      (## i lst (apply bar-make (door-mgr-construct-plug-list door-lst n
                                                              step cols)))
      (if (and (> door-borderwidth 0)
               (not (and door-mgr-no-outer-border
                         (> (+ i 3) len)))) 
          (## (+ i 1) lst (door-make-hborder door-lst n (+ n bstep)
                                             step cols)))
      (setq i (+ i 2))
      (setq n (+ n bstep)))
    (door-mgr-remove-nil lst)))

(defun door-mgr-construct-plug-list (door-lst n step num)
  (with (len (+ (* 2 num) 1)
         lst (list-make len)
         i 1)
    (if (and (> door-borderwidth 0)
             (not door-mgr-no-outer-border))
        (## 0 lst (door-make-vborder () (# n door-lst))))
    (while (< i len)
      (## i lst (with (door (# n door-lst))
                  (if door 
                      (door-make-plug (# 0 door) (door-virt-coord n))
                    (door-make-space))))
      (if (and (> door-borderwidth 0)
               (not (and door-mgr-no-outer-border
                         (> (+ i 3) len)))) 
          (## (+ i 1) lst (door-make-vborder (# n door-lst)
                                             (if (< (+ i 2) len)
                                                 (# (+ n step) door-lst)))))
      (setq i (+ i 2))
      (setq n (+ n step)))
    (door-mgr-remove-nil lst)))

(defun door-virt-coord (nr)
  (if door-mgr-dir-horiz
      (list (* screen-width (% nr door-mgr-dir-len))
            (* screen-height (/ nr door-mgr-dir-len)))
      (list (* screen-width (/ nr door-mgr-dir-len))
            (* screen-height (% nr door-mgr-dir-len)))))


;; Door Manager Functionality

(defun get-door (nr)
  (# nr (# 3 door-mgr)))

(defun set-door (nr ele)
  (if (not (> (length (# 3 door-mgr)) nr))
      (## 3 door-mgr (+ (# 3 door-mgr)
                        (list-make (- (+ 1 nr) (length (# 3 door-mgr)))))))
  (## nr (# 3 door-mgr) ele))

(defun door-empty-space (virtcoord)
  (with (left (+ (# 0 virtcoord) (# 0 virt-pos))
         right (+ left screen-width)
         top (+ (# 1 virtcoord) (# 1 virt-pos))
         bottom (+ top screen-height))
    (tag ret
         (for wob (list-of-windows 'window 'mapped)
              (if (not (virtual-nailed))
                  (with (midx (+ window-x (/ window-width 2))
                         midy (+ window-y (/ window-height 2)))
                    (if (and (> midx left)
                             (< midx right)
                             (> midy top)
                             (< midy bottom))
                        (exit ret ())))))
         t)))

(defun door-find-index (ind free movable)
  (if (and free movable)
      (while (or (get-door ind) (not (door-empty-space (door-virt-coord ind))))
        (setq ind (+ ind 1)))
      movable
      (while (get-door ind)
        (setq ind (+ ind 1)))
      free
      (while (or (not (door-empty-space (door-virt-coord ind)))
                 (and (get-door ind)
                      (not (# 2 (get-door ind)))))
        (setq ind (+ ind 1)))
      (while (and (get-door ind)
                  (or (not (# 2 (get-door ind)))
                      (not (door-empty-space (door-virt-coord ind)))))
        (setq ind (+ ind 1))))
  ind)

(defun door-find-name (name)
  (with (ind 0
         ele (get-door ind)
         len (length (# 3 door-mgr)))
    (while (and (< ind len)
                (not (= name (# 0 ele))))
      (setq ind (+ 1 ind))
      (setq ele (get-door ind)))
    (if (< ind len)
        ind
      ())))

(defun add-door args
  (with (name (# 0 args)
         startind (if (= (type (# 1 args)) 'number) (# 1 args) 0)
         free (member 'free args)
         movable (member 'movable args)
         ind (door-find-index startind free movable)
         virtpos (door-virt-coord ind)
         ele (list name free movable))
    (while (get-door ind)
      (with (startind (+ 1 ind)
             oldele (get-door ind)
             newind (door-find-index startind (# 1 oldele) ()))
        (set-door ind ele)
        (setq ele oldele)
        (setq ind newind)))
    (set-door ind ele)
    (door-mgr-show)
    virtpos))

(defunq maybe-add-door args
  (with (ind (door-find-name (eval (# 0 args))))
    (if ind
        (door-virt-coord ind)
        (eval (+ (list 'add-door) args)))))

(defun goto-door (name)
  (with (ind (door-find-name name)
         pos (if ind (door-virt-coord ind)))
    (if ind
        (virtual-move-to (# 0 pos) (# 1 pos)))))

(defun remove-door (arg)
  (with (ind ())
    (if (= (type arg) 'string)
        (: ind (door-find-name arg))
        (= (type arg) 'number)
        (: ind arg))
    (if ind
        (with (ele (get-door ind))
          (set-door ind ())
          (if (door-empty-space (door-virt-coord ind))
              (with (newind (door-find-index (+ 1 ind) t ())
                     newele ())
                (while (setq newele (get-door newind))
                  (set-door ind newele)
                  (setq ind newind)
                  (setq newind (door-find-index (+ 1 ind) t ())))
                (set-door ind ())))))
    (door-mgr-show)))


(defun clean-doors ()
  (with (len (length (# 3 door-mgr))
         i (- len 1))
    (while (> i -1)
      (if (and (get-door i)
               (door-empty-space (door-virt-coord i))
               (not (member (# 0 (get-door i)) initial-doors))
               (not (= door-free-area-string (# 0 (get-door i)))))
          (remove-door i))
      (setq i (- i 1)))
    (while (and (> door-auto-number 0)
                (not (door-find-name (+ door-auto-string
                                        (itoa door-auto-number)))))
      (setq door-auto-number (- door-auto-number 1)))))

(defun restore-doors ()
  (with (len (+ (length (# 3 door-mgr)) (* 2 door-mgr-dir-len))
         i 0
         old-dn door-auto-number)
    (setq door-auto-number 0)
    (while (< i len)
      (if (and (not (get-door i))
               (not (door-empty-space (door-virt-coord i))))
          (progn
            (setq door-auto-number (+ 1 door-auto-number))
            (while (door-find-name (+ door-auto-string
                                      (itoa door-auto-number)))
              (setq door-auto-number (+ 1 door-auto-number)))
            (add-door (+ door-auto-string (itoa door-auto-number)) i)))
      (setq i (+ i 1)))
    (if (> old-dn door-auto-number)
        (setq door-auto-number old-dn))))

(defun fresh-door ()
  (setq door-auto-number (+ door-auto-number 1))
  (add-door (+ door-auto-string (itoa door-auto-number)) 'free))

(defun door-free-area-action ()
  (with (pos (door-find-name door-free-area-string))
    (if pos
        (progn
          (setq wob root-window)
          (if door-free-area-cleans
              (clean-doors))
          (setq door-auto-number (+ door-auto-number 1))
          (add-door (+ door-auto-string (itoa door-auto-number)) pos)))))

(defun make-free-area-door ()
  (if (not (# (atom door-free-area-string) door-context))
      (setq door-context (+ (list (atom door-free-area-string) 
                                  '(door-action '(door-free-area-action)
                                    background (or door-free-area-color
                                                   door-background)))
                            door-context)))
  (add-door (atom door-free-area-string) 'free 'movable))

(defun door-add-initial ()
  (for door initial-doors 
       (if (= (type door) 'list)
           (if (and (= (type (# 0 door)) 'atom)
                    (boundp (# 0 door))
                    (member (type (eval (# 0 door)))
                            '(subr fsubr expr fexpr)))
               (eval door)
             (apply add-door door)) 
         (add-door door))))

