
;; This is a modified version of the original "gui.ss" teachpack:
;;   - Instead of having one window, each call to "create-window"
;;     returns a window
;;   - Message items stretch horizontally to fill the window, 
;;     avoiding the need for long initialization messages

#cs
(module gui mzscheme
  (require (lib "error.ss" "htdp")
           (lib "mred.ss" "mred")
           (lib "class.ss")
           (lib "list.ss")
           (lib "etc.ss")
           (lib "prim.ss" "lang"))
  
  (provide-primitives
   create-window ; (listof (listof GUI-ITEM)) -> window
   show-window   ; window -> true
   hide-window   ; window -> true
   make-text     ; Str -> GUI-ITEM
   text-contents ; GUI-ITEM[text%] -> Str
   make-choice   ; (listof Str) -> GUI-ITEM
   choice-index  ; GUI-ITEM[choice%] -> Str
   ; make-button ; Str (Event% -> Boolean) -> GUI-ITEM
   make-message  ; Str -> GUI-ITEM
   draw-message  ; GUI-ITEM[message%] Str -> true
   )
  
  (provide-higher-order-primitive make-button (_ call-back))

  #| ------------------------------------------------------------------
  Students build a window from a "matrix" list of GUI-ITEMS. 
  To build GUI-ITEMs, they need to use make-text, make-choice, 
  make-button, make-choice, or make-message. A GUI-ITEM can be 
  added to the window only once. 
  
  |#
  
  ;; INFRASTRUCTURE OPERATIONS:
  ;; ------------------------------------------------------------------

  (define-struct window (get-frame))
  
  ;; show-window : -> true
  ;; effect: to show the window
  (define (show-window w)
    (check-arg 'show-window (window? w) "window" "its" w)
    (send ((window-get-frame w)) show true)
    true)
  
  ;; hide-window : X -> true
  ;; effect: to hide the window 
  (define (hide-window w)
    (check-arg 'hide-window (window? w) "window" "its" w)
    (send ((window-get-frame w)) show false)
    true)
  
  ;; MAKING ITEMS:
  ;; ------------------------------------------------------------------
  
  (define-struct gui-item (builder))
  ;; A gui-item[C < control%] is a structure: (make-gui-item f)
  ;; where f is a function: (panel% -> C)
  
  ;; create-gui-item : ((union panel% #f) -> C[< control%])
  ;; to create a memoizing gui-item
  ;; create-window is the only caller that can pass in a panel
  ;; exceptions: ;; if the gui-item is already "panel'ed", error
  ;; all other callers must pass in #f
  (define (create-gui-item builder)
    (let ([C false])
      (make-gui-item
       (lambda (p)
         (cond
           [(and p C)
            (error 'create-window "item added to window twice")]
           [(and p (not C)) (set! C (builder p)) C]
           [(and (not p) C) C]
           [(and (not p) (not C))
            (error 'gui "gui-items must be added to window before use (see create-window)")])))))
  
  ;; create-window : (listof (listof gui-item)) -> true
  ;; to add gui-items to the window and to show window
  (define (create-window loi)
    (check-list-list 'create-window (listoflistof? gui-item? "gui-item" loi) "gui-items" loi)

    (let ([the-frame (make-object frame% "GUI" false 10 10)])
      (for-each (lambda (loi)
		  (let ((p (make-object horizontal-pane% the-frame)))
		    (send p set-alignment 'center 'center)
		    (for-each (lambda (i) ((gui-item-builder i) p)) loi)))
		loi)
      (let ([w (make-window (lambda () the-frame))])
	(show-window w)
	w)))
  
  ;; (_ -> Boolean) String X -> (union String true)
  (define (listoflistof? pred? pred given)
    (cond
      [(not (list? given)) (format NONLIST given)]
      [(find-non list? given)
       => (lambda (non-list)
	    (format NONLISTLIST non-list))]
      [(ormap identity (map (lambda (ll) (find-non pred? ll)) given))
       => (lambda (non-x)
	    (format NONX pred non-x))]
      [else #t]))
  
  (define NONLIST "list expected, given: ~e")
  (define NONLISTLIST "list of lists expected, given list with ~e")
  (define NONX "list of lists of ~a expected, given list of lists with ~e")
  
  
  #| Tests ------------------------------------------------------------------
  (listoflistof? number? "number" '((1 2 3) (4 5 6)))
  
  (string=? (format NONX "number" 'a)
            (listoflistof? number? "number" '((1 2 3) (4 a 6))))
  
  (string=? (format NONLISTLIST 1)
            (listoflistof? number? "number" '(1 (2 3) (4 5 6))))
  |#
  
  ;; make-text : str -> gui-item
  ;; to create a text-item with label lbl
  (define (make-text lbl)
    (check-arg 'make-text (string? lbl) "string" "first" lbl)
    (create-gui-item
     (lambda (the-panel) 
       (make-object text-field% lbl the-panel void))))
  
  ;; make-message : str -> gui-item
  ;; to create a message-item with current contents txt
  (define (make-message txt)
    (check-arg 'make-message (string? txt) "string" "first" txt)
    (create-gui-item 
     (lambda (the-panel)
       (new message% [label txt] [parent the-panel] [stretchable-width #t]))))
  
  ;; make-button : str (event% -> boolean) -> gui-item
  ;; to create a button-item with label and call-back function 
  (define (make-button label call-back)
    (check-arg 'make-button (string? label) "string" 'first label)
    (check-proc 'make-button call-back 1 'second "1 argument")
    (create-gui-item 
     (lambda (the-panel)
       (make-object button% label
         the-panel
         (lambda (b e)
           (check-result 'button-callback boolean? "Boolean" (call-back e)))))))
  
  ;; make-choice : (listof str) -> gui-item
  ;; to create a choice-item that permits users to choose from the
  ;; alternatives on loc
  (define (make-choice loc)
    (check-arg 'make-choice (and (list? loc) (andmap string? loc)) "list of strings" "first" loc)
    (create-gui-item 
     (lambda (the-panel)
       (make-object choice% "" loc the-panel void))))
  
  ;; DISPLAYING MESSAGES:
  ;; ------------------------------------------------------------------
  
  ;; draw-message : gui-item[message%] str -> true
  ;; to change the current contents of a message field 
  (define (draw-message msg txt)
    (check-arg 'draw-message (gui-item? msg) "gui-item" "first" msg)
    (check-arg 'draw-message (string? txt) "string" "second" txt)   
    (let* ([o ((gui-item-builder msg) #f)])
      (when (<= (send o min-width) (string-length txt))
        (let*-values ;; MF: I couldn't think of a better way of doing this
            ([(m) (new message% [parent (new frame% [label "hello"])][label txt])]
             [(x y) (send m get-graphical-min-size)])
          (send o min-width x)))
      (send o set-label txt)
      true))
  
  ;; PROBING ITEMS:
  ;; ------------------------------------------------------------------
  
  ;; text-contents : gui-item[text-field%] -> str
  ;; to determine the contents of a text-item 
  (define (text-contents a-text-gui)
    (check-arg 'text-contents (gui-item? a-text-gui) "gui-item" "first" a-text-gui)
    (send ((gui-item-builder a-text-gui) #f) get-value))
  
  ;; choice-index : gui-item[choice%] -> number
  ;; to determine which choice is currently selected in a choice-item 
  (define (choice-index a-choice)
    (check-arg 'choice-index (gui-item? a-choice) "gui-item" "first" a-choice)      
    (send ((gui-item-builder a-choice) #f) get-selection))
  
  )
