mreddesigner-properties-widget.ss
;; ##################################################################################
;; # ============================================================================== #
;; # MrEd Designer - properties-widget.ss                                           #
;; # http://mreddesigner.lozi.org                                                   #
;; # Copyright (C) Lozi Jean-Pierre, 2004 - mailto:[email protected]             #
;; # Copyright (C) Peter Ivanyi, 2007                                               #
;; # ============================================================================== #
;; #                                                                                #
;; # This program is free software; you can redistribute it and/or                  #
;; # modify it under the terms of the GNU General Public License                    #
;; # as published by the Free Software Foundation; either version 2                 #
;; # of the License, or (at your option) any later version.                         #
;; #                                                                                #
;; # This program is distributed in the hope that it will be useful,                #
;; # but WITHOUT ANY WARRANTY; without even the implied warranty of                 #
;; # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the                  #
;; # GNU General Public License for more details.                                   #
;; #                                                                                #
;; # You should have received a copy of the GNU General Public License              #
;; # along with this program; if not, write to the Free Software                    #
;; # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.    #
;; #                                                                                #
;; ##################################################################################


(module mreddesigner-properties-widget mzscheme
  (require (lib "class.ss")  
           (lib "mred.ss" "mred")
           (file "mreddesigner-misc.ss")
  )

; ------------------------------------------------------------------------------
; Single value option
; ------------------------------------------------------------------------------

; define a new object for property window
; single value field
; public interfaces:
; - is-editable? , set-editable? , set-name , get-name, set-value , get-value
(define property-value%
  (class horizontal-panel%
    (init-field
      (name   #f)
      (value #f)
      (callback #f)
      (name-width #f)
      (editable? #t)
    )
    
    ; variable to store the current value
    ; we need this storage so when the user leaves this widget without
    ; pressing ENTER then we restore the value to this stored value
    (define current #f)
    
    ; variables to store the internal widgets
    (define name-editor #f)
    (define name-canvas #f)
    (define value-editor #f)
    (define value-canvas #f)
    
    (define blue  (make-object color% 10 36 106))
    (define white (make-object color% 255 255 255))
    (define grey  (make-object color% 200 200 200))
    (define black (make-object color% 0 0 0))
    
    (define delta-normal   (make-object style-delta%))
    (define delta-select   (make-object style-delta%))
    (define delta-inactive (make-object style-delta%))
    
    (public is-editable?)
    (define (is-editable?)
      editable?
    )
    
    (public set-editable?)
    (define (set-editable? bool)
      (set! editable? bool)
      (if bool
        (begin
          (send value-editor hide-caret #f)
          (send value-editor change-style delta-normal 0 'end)
        )
        (begin
          (send value-editor hide-caret #t)
          (send value-editor change-style delta-inactive 0 'end)
        )
      )
    )
    
    (public set-name)
    (define (set-name name)
      (send name-editor erase)
      (send name-editor insert name 0 'same #t)
    )
    
    (public get-name)
    (define (get-name)
      (send name-editor get-text 0 'eof #f #f)
    )
    
    (public set-value)
    (define (set-value val)
      (send value-editor erase)
      (send value-editor insert val 0 'same #t)
      (if (not editable?)
        (send value-editor change-style delta-inactive 0 'end)
      )
    )
    
    (public get-value)
    (define (get-value)
      (send value-editor get-text 0 'eof #f #f)
    )
    
    ; this function changes the style when the field is focused and
    ; restores the default style after unfocus
    (define (property-focus on?)
      (if on?
        (begin
          (send (send name-canvas get-editor) change-style delta-select 0 'end)
          (send name-canvas set-canvas-background blue)
          ; store the current value
          (set! current (get-value))
        )
        (let
          ((val (get-value)))
          ; check whether it is an already accepted value (by ENTER)
          (if (and current (not (equal? val current)))
            (set-value current)
          )
          (set! current #f)
          (send (send name-canvas get-editor) change-style delta-normal 0 'end)
          (send name-canvas set-canvas-background white)
        )
      )
    )
    
    (public unfocus)
    (define (unfocus)
      (set! current #f)
    )
    
    ; subclass the editor so
    ; - we can handle ENTER and call a function
    ; - we can handle focus and unfocus
    ; - we can handle double clicking
    (define property-editor%
      (class editor-canvas%
        
        ; handle ENTER
        (define/override (on-char event)
          (case (send event get-key-code)
            ((#\return)
             (let ((ok? #t))
               ; if ENTER is pressed and there is a function call it
              (if (and callback (procedure? callback))
                 (callback (get-value))
               )
               (if ok?
                 (set! current (get-value))
               )
             )
             ; we swallow this key
            )
            (else
             (super on-char event)
            )
          )
        )
        
        ; signal focus by making the name field blue
        (define/override (on-focus on?)
          (property-focus on?)
          (super on-focus on?)
        )
        (super-new)
        
        ; double clicking
        (let*
          ((editor (send this get-editor))
           (keymap (send editor get-keymap)))
          (send keymap add-function "all-text-select"
            (lambda (edit event) (send edit select-all)))
          (send keymap map-function "leftbuttondouble" "all-text-select")
        )
      )
    )
    
    ; this function initializes the object
    (define (init)
      ; style deltas for normal name or selected name
      (send delta-normal set-delta-foreground "black")
      (send delta-select set-delta-foreground "white")
      (send delta-inactive set-delta-foreground grey)
      
      ; editors for name and value
      (set! name-editor  (new text%))
      (set! value-editor (new text%))
      
      ; widgets to display
      (set! name-canvas (new editor-canvas%
                             (parent this)
                             (editor name-editor)
                             (style '(no-border no-hscroll no-vscroll))
                             (line-count 1)
                             (stretchable-height #f)
                             (vert-margin 1)
                             (horiz-margin 1)
                             (vertical-inset 0)
                             (horizontal-inset 0)
                             (enabled #f)
                             (min-width name-width)
                             (stretchable-width #f)
                       ))
      (set! value-canvas (new property-editor%
                              (parent this)
                              (editor value-editor)
                              (style '(no-border no-hscroll no-vscroll))
                              (line-count 1)
                              (stretchable-height #f)
                              (vert-margin 1)
                              (horiz-margin 1)
                              (vertical-inset 0)
                              (horizontal-inset 0)
                       ))
      
      ; set the name and the value
      (set-name  name)
      (set-value value)
      ; set the editable state
      (set-editable? editable?)
      ; to ensure that this widget has always a single line
      (send this stretchable-height #f)
    )
    
    (super-new)
    ;
    (init)
  )
)

; ------------------------------------------------------------------------------
; Choice
; ------------------------------------------------------------------------------

(define property-choice-icon (make-object bitmap% (build-path "images" "down.png") 'png #f))

; define a new object for property window
; field with a selection,
; the list of choices cannot be changed later with the current interface
;
; public interfaces:
; - set-name , get-name , get-value , set-selection
(define/provide property-choice%
  (class horizontal-panel%
    (init-field
      (name   #f)
      (choices #f)
      (selection 0)
      (callback #f)
      (name-width #f)
    )
    
    (define name-editor #f)
    (define name-canvas #f)
    (define value-editor #f)
    (define value-canvas #f)
    (define button #f)
    
    (define blue  (make-object color% 10 36 106))
    (define white (make-object color% 255 255 255))
    (define black (make-object color% 0 0 0))
    
    (define delta-normal (make-object style-delta%))
    (define delta-select (make-object style-delta%))
    
    ; this is private for this class
    (define (set-value val)
      (send value-editor erase)
      (send value-editor insert val 0 'same #t)
    )
    
    (public get-value)
    (define (get-value)
      (send value-editor get-text 0 'eof #f #f)
    )
    
    (public get-name)
    (define (get-name)
      (send name-editor get-text 0 'eof #f #f)
    )
    
    (public set-name)
    (define (set-name name)
      (send name-editor erase)
      (send name-editor insert name 0 'same #t)
    )
    
    (public set-selection)
    (define (set-selection select)
      (if (<= 0 select (- (length choices) 1))
        (set-value (list-ref choices select))
      )
    )
    
    ; this function changes the style when the field is focused and
    ; restores the default style after unfocus
    (define (property-focus on?)
      (if on?
        (begin
          (send (send name-canvas get-editor) change-style delta-select 0 'end)
          (send name-canvas set-canvas-background blue)
        )
        (begin
          (send (send name-canvas get-editor) change-style delta-normal 0 'end)
          (send name-canvas set-canvas-background white)
        )
      )
    )
        
    (public unfocus)
    (define (unfocus)
      (void)
    )

    
    ; ignore keyboard events
    ; handle focusing, when focused make name field blue
    (define property-editor%
      (class editor-canvas%
        (define/override (on-char event)
          (void)
        )
        (define/override (on-focus on?)
          (property-focus on?)
          (super on-focus on?)
        )
        (super-new)
      )
    )
    
    ; handle focusing, when focused make name field blue
    (define property-button%
      (class button%
        (define/override (on-focus on?)
          (property-focus on?)
          (super on-focus on?)
        )
        (super-new)
      )
    )
    
    ; this function initializes the object
    (define (init)
      (send delta-normal set-delta-foreground "black")
      (send delta-select set-delta-foreground "white")
      
      (set! name-editor  (new text%))
      (set! value-editor (new text%))
      
      (set! name-canvas (new editor-canvas%
                             (parent this)
                             (editor name-editor)
                             (style '(no-border no-hscroll no-vscroll))
                             (line-count 1)
                             (stretchable-height #f)
                             (vert-margin 0)
                             (horiz-margin 1)
                             (vertical-inset 0)
                             (horizontal-inset 0)
                             (enabled #f)
                             (min-width name-width)
                             (stretchable-width #f)
                       ))
      (set! value-canvas (new property-editor%
                              (parent this)
                              (editor value-editor)
                              (style '(no-border no-hscroll no-vscroll))
                              (line-count 1)
                              (stretchable-height #f)
                              (vert-margin 1)
                              (horiz-margin 1)
                              (vertical-inset 0)
                              (horizontal-inset 0)
                              (enabled #t)
                       ))
      ; makes the widget look like a disabled widget
      (send value-editor hide-caret #t)
      
      (letrec
        ((popup (new popup-menu%)))
        (set! button (new property-button% 
                          (label property-choice-icon)
                          (parent this)
                          (vert-margin 0)
                          (horiz-margin 0)
                          (callback 
                           (lambda (but e)
                             (let 
                               ((w (send value-canvas get-width))
                                (h (send value-canvas get-height))
                                (x (send value-canvas get-x))
                                (y (send value-canvas get-y))
                                )
                               (send popup set-min-width (+ (- w 5) (send button get-width)))
                               (send value-canvas 
                                     popup-menu popup 
                                     0
                                     (+ y (- h 1)))
                             )
                           )
                         )
                     )
        )
        ; list of menu items
        (for-each
          (lambda (x)
            (new menu-item% 
                 (parent popup)
                 (label x)
;                 (label (to-string x))
                 (callback (lambda (m e)
                             (set-value x)
                             (if (and callback (procedure? callback))
                               (callback x)
                             )
                           )))
          )
          choices
        )
      )
      (set-name  name)
      (set-selection selection)
      (send this stretchable-height #f)
    )
    
    (super-new)
    ;
    (init)
  )
)

; ------------------------------------------------------------------------------
; Option list
; ------------------------------------------------------------------------------

; public interfaces:
; - empty-allowed-get , empty-allowed-set , get-choices , set-choices
(define/provide property-edit-list%
  (class dialog%
    
    (init-field
      (empty-allowed? #f)
    )
    
    ; determines whether the Cancel or the OK button has been pressed
    (define cancel? #f)
    ; list box widget
    (define list-box-data #f)
    
    (public empty-allowed-get)
    (define (empty-allowed-get)
      empty-allowed?
    )
    
    (public empty-allowed-set)
    (define (empty-allowed-set bool)
      (set! empty-allowed? bool)
    )
    
    (public get-choices)
    (define (get-choices)
      (define (get-choices-aux i n)
        (cond
          ((= i n) '())
          (else
           (cons (send list-box-data get-string i)
                 (get-choices-aux (add1 i) n)))))
      (if (not cancel?)
        (get-choices-aux 0 (send list-box-data get-number))
        #f
      )
    )
    
    (public set-choices)
    (define (set-choices lst)
      (for-each
        (lambda (x)
          (if (not (string? x))
            (error "invalid choice for property-edit%: " x)
          )
        )
        lst
      )
      ; FIXME: clear here the text field
      (send list-box-data set lst)
    )
    
    (define (init)
      (letrec 
        ((horizontal-panel-data (new horizontal-panel%
                                     (parent this)
                                     (style '(border))
                                     ))
         (list-box-panel (new horizontal-panel% (parent horizontal-panel-data)))
         (vertical-panel-modify (new vertical-panel%
                                     (parent horizontal-panel-data)
                                     (alignment '(left top))
                                     (stretchable-width #f)))
         (message (new message% 
                       (label "Value")
                       (parent vertical-panel-modify)))
         (text-field-data (new text-field%
                               (parent vertical-panel-modify)
                               (min-width 100)
                               (init-value "")
                               (style '(single))
                               (label #f)
                               (stretchable-height #f)))
         (horizontal-panel-add (new horizontal-panel% 
                                    (parent vertical-panel-modify)))

         (button-add (new button%
                          (parent horizontal-panel-add)
                          (label "Add")
                          (stretchable-width #t)
                          (stretchable-height #f)
                          (min-width 50)
                          (callback 
                            (lambda (b e)
                              (let 
                                ((text (send text-field-data get-value))
                                 (idx  (send list-box-data get-selection))
                                )
                                ; if something is selected, unselect it
                                (if idx
                                  (send list-box-data select idx #f)
                                )
                                ; if something is typed in append to the list
                                (if (> (string-length text) 0)
                                  (begin
                                    (send list-box-data append text))))))))
         (button-edit (new button%
                           (parent horizontal-panel-add)
                           (label "Edit")
                           (stretchable-width #t)
                           (stretchable-height #f)
                           (min-width 50)
                           (callback 
                            (lambda (b e)
                              (let 
                                ((text (send text-field-data get-value))
                                 (idx  (send list-box-data get-selection))
                                )
                                ; if something is selected and there is a typed text
                                (if (and idx (> (string-length text) 0))
                                  (begin
                                    (send list-box-data set-string idx text))))))))
         (horizontal-panel-move (new horizontal-panel% 
                                     (parent vertical-panel-modify)))
         (button-up     (new button%
                             (parent horizontal-panel-move)
                             (label "Up")
                             (stretchable-width #t)
                             (stretchable-height #f)
                             (min-width 50)
                             (vert-margin 0)
                             (callback 
                              (lambda (b e)
                                (let 
                                  ((idx  (send list-box-data get-selection)))
                                  ; if there is a selection and it is not the first element
                                  (if (and idx (> idx 0))
                                    (let
                                      ((prev (send list-box-data get-string (- idx 1)))
                                       (curr (send list-box-data get-string idx))
                                      )
                                      (send list-box-data set-string idx prev)
                                      (send list-box-data set-string (- idx 1) curr)
                                      (send list-box-data set-selection (- idx 1))
                                      )))))))
         (button-down   (new button%
                             (parent horizontal-panel-move)
                             (label "Down")
                             (stretchable-width #t)
                             (stretchable-height #f)
                             (min-width 50)
                             (vert-margin 0)
                             (callback 
                              (lambda (b e)
                                (let 
                                  ((idx  (send list-box-data get-selection))
                                   (n    (send list-box-data get-number))
                                  )
                                  ; if there is a selection and it is not the last element
                                  (if (and idx (< idx (- n 1)))
                                    (let
                                      ((next (send list-box-data get-string (+ idx 1)))
                                       (curr (send list-box-data get-string idx))
                                      )
                                      (send list-box-data set-string idx next)
                                      (send list-box-data set-string (+ idx 1) curr)
                                      (send list-box-data set-selection (+ idx 1))
                                      )))))))
         (button-delete (new button%
                             (parent vertical-panel-modify)
                             (stretchable-width #t)
                             (label "Delete")
                             (stretchable-height #f)
                             (callback 
                              (lambda (b e)
                                (let 
                                  ((text (send text-field-data get-value))
                                   (idx  (send list-box-data get-selection))
                                   (n    (send list-box-data get-number))
                                  )
                                  ; if there is a selection and
                                  ;   empty list is allowed or
                                  ;   empty list is not allowed and there are more than 1 element
                                  (if (and idx
                                           (or empty-allowed?
                                               (and (not empty-allowed?)
                                                    (> n 1))))
                                    (begin
                                      (send text-field-data set-value "")
                                      (send list-box-data select idx #f)
                                      (send list-box-data delete idx))))))))
         (horizontal-panel-buttons (new horizontal-panel%
                                        (parent this)
                                        (alignment '(center center))
                                        (vert-margin 2)
                                        (stretchable-height #f)
                                        (stretchable-width #t)))
         (button-ok (new button%
                         (parent horizontal-panel-buttons)
                         (min-width 70)
                         (label "OK")
                         (stretchable-width #f)
                         (stretchable-height #f)
                         (callback
                           (lambda (b e)
                             ; Ok was pressed, clear text field and hide window
                             (set! cancel? #f)
                             (send text-field-data set-value "")
                             (send this show #f)))))
         (button-cancel (new button%
                             (parent horizontal-panel-buttons)
                             (stretchable-width #f)
                             (min-width 70)
                             (label "Cancel")
                             (stretchable-height #f)
                             (callback
                               (lambda (b e)
                                 ; Cancel was pressed, clear text field and hide window
                                 (set! cancel? #t)
                                 (send text-field-data set-value "")
                                 (send this show #f)))))
        )
        (set! list-box-data (new list-box%
                                 (parent list-box-panel)
                                 (choices '())
                                 (selection #f)
                                 (style '(single vertical-label))
                                 (label "List of choices")
                                 (callback
                                   (lambda (l e)
                                     (let*
                                       ((idx (send l get-selection)))
                                       ; if there is a selection set the text in the edit box
                                       (if idx
                                         (send text-field-data 
                                               set-value 
                                               (send l get-string idx))))))))
        ; double clicking for the text field
        (let*
          ((editor (send text-field-data get-editor))
           (keymap (send editor get-keymap)))
          (send keymap add-function "all-text-select"
            (lambda (edit event) (send edit select-all)))
          (send keymap map-function "leftbuttondouble" "all-text-select")
        )
      )
    )
    
    (super-new (style '(no-caption))
               (width 300)
               (height 150)
               (border 4)
    )
    ;
    (init)
  )
)

(define property-option-icon (make-object bitmap% (build-path "images" "dots.png") 'png #f))

; define a new object for property window
; field with modifiable selection
(define property-option-list%
  (class horizontal-panel%
    (init-field
      (name   #f)
      (choices #f)
      (selection 0)
      (callback #f)
      (width #f)
      (empty-allowed? #f)
    )
    
    (define name-editor #f)
    (define name-canvas #f)
    (define value-editor #f)
    (define value-canvas #f)
    (define button #f)
    (define popup #f)
    
    (define blue  (make-object color% 10 36 106))
    (define white (make-object color% 255 255 255))
    (define black (make-object color% 0 0 0))
    
    (define delta-normal (make-object style-delta%))
    (define delta-select (make-object style-delta%))
    
    (public empty-allowed-get)
    (define (empty-allowed-get)
      (let ((allowed? (send popup empty-allowed-get)))
        (set! empty-allowed? allowed?)
        allowed?
      )
    )
    
    (public empty-allowed-set)
    (define (empty-allowed-set bool)
      (set! empty-allowed? bool)
      (send popup empty-allowed-set bool)
    )
    
    (public get-name)
    (define (get-name)
      (send name-editor get-text 0 'eof #f #f)
    )
    
    (public set-name)
    (define (set-name name)
      (send name-editor erase)
      (send name-editor insert name 0 'same #t)
    )
    
    (public set-choices)
    (define (set-choices lst)
      (set! choices lst)
      (send popup set-choices lst)
    )
    
    ; this function changes the style when the field is focused and
    ; restores the default style after unfocus
    (define (property-focus on?)
      (if on?
        (begin
          (send (send name-canvas get-editor) change-style delta-select 0 'end)
          (send name-canvas set-canvas-background blue)
        )
        (begin
          (send (send name-canvas get-editor) change-style delta-normal 0 'end)
          (send name-canvas set-canvas-background white)
        )
      )
    )
        
    (public unfocus)
    (define (unfocus)
      (void)
    )

    ; ignore keyboard events
    ; handle focusing, when focused make name field blue
    (define property-editor%
      (class editor-canvas%
        (define/override (on-char event)
          (void)
        )
        (define/override (on-focus on?)
          (property-focus on?)
          (super on-focus on?)
        )
        (super-new)
      )
    )
    
    ; handle focusing, when focused make name field blue
    (define property-button%
      (class button%
        (define/override (on-focus on?)
          (property-focus on?)
          (super on-focus on?)
        )
        (super-new)
      )
    )
    
    ; this function initializes the object
    (define (init)
      (send delta-normal set-delta-foreground "black")
      (send delta-select set-delta-foreground "white")
      
      (set! name-editor  (new text%))
      (set! value-editor (new text%))
      
      (set! name-canvas (new editor-canvas%
                             (parent this)
                             (editor name-editor)
                             (style '(no-border no-hscroll no-vscroll))
                             (line-count 1)
                             (stretchable-height #f)
                             (vert-margin 0)
                             (horiz-margin 1)
                             (vertical-inset 0)
                             (horizontal-inset 0)
                             (enabled #f)
                             (min-width width)
                             (stretchable-width #f)
                       ))
      (set! value-canvas (new property-editor%
                              (parent this)
                              (editor value-editor)
                              (style '(no-border no-hscroll no-vscroll))
                              (line-count 1)
                              (stretchable-height #f)
                              (vert-margin 1)
                              (horiz-margin 1)
                              (vertical-inset 0)
                              (horizontal-inset 0)
                              (enabled #t)
                       ))
      (send value-editor hide-caret #t)
      (set! popup (new property-edit-list% 
                       (label "Choices")
                       (parent #f)
                       (empty-allowed? empty-allowed?)
                       ))
      (set! button (new property-button% 
                        (label property-option-icon)
                        (parent this)
                        (vert-margin 0)
                        (horiz-margin 0)
                        (callback 
                         (lambda (but e)
                           (let*
                             ((h (send name-canvas get-height))
                              (x (send name-canvas get-x))
                              (y (send name-canvas get-y))
                              )
                             (let-values
                               (((xx yy) (send name-canvas client->screen (- x 1) (+ y h))))
                               ; position the popup window under the current widget
                               (send popup move xx yy)
                               (send popup set-choices choices)
                               ; show the dialog window
                               (send popup show #t)
                               ; after the dialog window has been closed
                               (let
                                 ((lst (send popup get-choices)))
                                 (if lst
                                   (begin
                                     (set! choices (send popup get-choices))
                                     (if (and callback (procedure? callback))
                                       (callback choices)))))))))))
      (set-name  name)
      (send this stretchable-height #f)
    )
    
    (super-new)
    ;
    (init)
  )
)

; ------------------------------------------------------------------------------
; Property panel
; ------------------------------------------------------------------------------

(provide property-panel%)
(define property-panel%
  (class vertical-panel%
    (init-field
      (callback #f)
    )
    (unless (or (not callback) 
                (procedure-arity-includes? callback 2))
      (raise-type-error 'property-panel%
                        "procedure of arity 1"
                        callback)
    )
    
    (define widget-table #f)
    (define widget-empty #f)
    (define widget-necessary #f)
    (define widget-optional #f)
    
    (public empty)
    (define (empty)
      (send this import #f #f '())
    )
    
    (define horiz-alignment-options '("left" "center" "right"))
    (define vert-alignment-options  '("top" "center" "bottom"))
    (define boolean-true "true")
    (define boolean-false "false")
    (define boolean-options (list boolean-true boolean-false))
    (define single-option "single")
    (define multiple-option "multiple")
    (define extended-option "extended")
    (define selection-options (list single-option multiple-option extended-option))
    (define text-field-options (list single-option multiple-option))
    (define horizontal-option "horizontal")
    (define vertical-option "vertical")
    (define direction-options (list horizontal-option vertical-option))
    (define horizontal-label-option "horizontal-label")
    (define vertical-label-option "vertical-label")
    (define label-direction-options (list horizontal-label-option vertical-label-option))
    
    
    
    (define (bool-choice-value w name val)
      (cond
        ((member name '(enabled value stretchable-width stretchable-height))
         (if val
           (send w
                 set-selection
                 (- (length boolean-options)
                    (length (member boolean-true boolean-options))))
           (send w
                 set-selection
                 (- (length boolean-options)
                    (length (member boolean-false boolean-options))))
         )
        )
      )
    )
    
    (define (style-options type value-list)
      (letrec
        ((true  (- (length boolean-options)
                   (length (member boolean-true boolean-options))))
         (false (- (length boolean-options)
                   (length (member boolean-false boolean-options))))
         (w-lst '())
         ; function for boolean type
         (widget-bool-set 
          (lambda (type prop)
            (let
              ((w (hash-table-get widget-table type #f)))
              (if (member prop value-list)
                (send w set-selection true)
                (send w set-selection false)
              )
              (set! w-lst (cons w w-lst))
            )
          )
         )
         ; function for boolean type,
         ; but the actual style parameter is reversed
         ; for example the question was "border?" but it sets "no-border" property
         (widget-bool-set-not
          (lambda (type prop)
            (let
              ((w (hash-table-get widget-table type #f)))
              (if (member prop value-list)
                (send w set-selection false)
                (send w set-selection true)
              )
              (set! w-lst (cons w w-lst))
            )
          )
         )
        )
        ; deleted
        (if (member type '(button radio-box check-box
                           panel horizontal-panel vertical-panel
                           tab-panel group-box-panel
                           message slider gauge list-box
                           choice text-field canvas))
          (widget-bool-set 'style-deleted 'deleted)
        )
        ; border and no-border
        (if (member type '(button panel horizontal-panel vertical-panel
                           tab-panel ; actually this has 'no-border' style
                           canvas))
          (if (equal? type 'tab-panel)
            (widget-bool-set-not 'style-border 'no-border)
            (widget-bool-set 'style-border 'border)
          )
        )
        ; style for frames
        (if (equal? type 'frame)
          (begin
            (widget-bool-set-not 'style-no-resize-border 'no-resize-border)
            (widget-bool-set-not 'style-no-caption 'no-caption)
            (widget-bool-set-not 'style-no-system-menu 'no-system-menu)
            (widget-bool-set     'style-toolbar-button 'toolbar-button)
            (widget-bool-set     'style-hide-menu-bar 'hide-menu-bar)
            (widget-bool-set     'style-float 'float)
            (widget-bool-set     'style-metal 'metal)
          )
        )
        ; horizontal or vertical direction
        (if (member type '(radio-box slider gauge))
          (let
            ((w (hash-table-get widget-table 'style-direction #f)))
            (cond 
              ((member 'horizontal value-list)
               (send w set-selection (- (length direction-options)
                                        (length (member horizontal-option direction-options)))))
              ((member 'vertical value-list)
               (send w set-selection (- (length direction-options)
                                        (length (member vertical-option direction-options)))))
            )
            (set! w-lst (cons w w-lst))
          )
        )
        ; horizontal or vertical labels
        (if (member type '(radio-box slider gauge list-box choice text-field))
          (let
            ((w (hash-table-get widget-table 'style-label-direction #f)))
            (cond 
              ((member 'vertical-label value-list)
               (send w set-selection (- (length label-direction-options)
                                        (length (member vertical-label-option label-direction-options)))))
              (else
               (send w set-selection (- (length label-direction-options)
                                        (length (member horizontal-label-option label-direction-options)))))
            )
            (set! w-lst (cons w w-lst))
          )
        )
        (if (equal? type 'slider)
          (widget-bool-set 'style-plain 'plain)
        )
        (if (member type '(text-field canvas))
          (widget-bool-set 'style-hscroll 'hscroll)
        )
        (if (equal? type 'canvas)
          (begin
            (widget-bool-set 'style-vscroll 'vscroll)
            (widget-bool-set 'style-control-border 'control-border)
            (widget-bool-set 'style-resize-corner 'resize-corner)
            (widget-bool-set 'style-gl 'gl)
            (widget-bool-set-not 'style-no-autoclear 'no-autoclear)
            (widget-bool-set 'style-transparent 'transparent)
          )
        )
        (if (equal? type 'text-field)
          (let
            ((w (hash-table-get widget-table 'style-text-field #f)))
            (if (member 'single value-list)
              (send w set-selection (- (length text-field-options)
                                       (length (member single-option text-field-options))))
              (send w set-selection (- (length text-field-options)
                                       (length (member multiple-option text-field-options))))
            )
            (widget-bool-set 'style-password 'password)
            
            (set! w-lst (cons w w-lst))
          )
        )
        (if (equal? type 'list-box)
          (let
            ((w (hash-table-get widget-table 'style-selection #f)))
            (cond 
              ((member 'single value-list)
               (send w set-selection (- (length selection-options)
                                        (length (member single-option selection-options)))))
              ((member 'multiple value-list)
               (send w set-selection (- (length selection-options)
                                        (length (member multiple-option selection-options)))))
              ((member 'extended value-list)
               (send w set-selection (- (length selection-options)
                                        (length (member extended-option selection-options)))))
            )
            (set! w-lst (cons w w-lst))
          )
        )
        w-lst
      )
    )
    
    ;/****f*
    ;* NAME
    ;*   import
    ;* DESCRIPTION
    ;*   This function imports a property list into the property window
    ;* ARGUMENTS
    ;*   id - the identification string of the item,
    ;*        this id is also used in the hierarchy widget
    ;*   type - the type of the widget
    ;*   prop-lst - the list of properties as an association list,
    ;*        every element in the list contains a name and a value,
    ;*        but the value can be a list as well. If the value is a list
    ;*        then a property-choice% window is created.
    ;******/
    (public import)
    (define (import id type prop-lst)
        ; DEBUG:
        (printf "import: prop-lst:\n~a\n" prop-lst)
      (if (null? prop-lst)
        (send this change-children (lambda (lst) (list widget-empty)))
        (let
          ((necessary-list '())
           (optional-list '())
           (necessary (assoc 'necessary prop-lst))
           (id-w (hash-table-get widget-table 'id #f))
          )
          ; which ever widget has focus, unfocus it
          (hash-table-for-each
            widget-table
            (lambda (key w)
              (send w unfocus)
            )
          )
          (if (equal? id "project")
            (send id-w set-editable? #f)
            (send id-w set-editable? #t)
          )
          (if necessary (set! necessary (cadr necessary))) ; ???  for id ? (LO)
          (for-each
            (lambda (prop)
              (let* 
                ((type (car prop))
                 (val (cadr prop))
                 (w (hash-table-get widget-table type #f))
                 ; list of specially handled properties
                 (except '(selection style alignment choices))
                 (is-except (member type except))
                )
                (if (and w (not is-except))
                  (begin
                    ; single value properties
                    (if (is-a? w property-value%)
                      (send w set-value (to-string val))
                    )
                    ; multiple value properties, with choices
                    (if (is-a? w property-choice%)
                      (bool-choice-value w type val)
                    )
                    ; determine whether it is a necessary or an optional property
                    (if (and necessary (member (car prop) necessary))
                      (set! necessary-list (cons w necessary-list))
                      (set! optional-list  (cons w optional-list))
                    )
                  )
                 ;; else widget not found for type
                  ; (unless is-except
                      ; (begin
                        ; (printf "widget not found for type: ~a~n" type)
                        ; (add-property-widget type "plop" val #t)
                        ; ))
                )
                ; properties that are handled separately
                (cond
                  ; alignment
                  ((equal? (car prop) 'alignment)
                   (let
                     ((horiz-align (symbol->string (caadr prop)))
                      (vert-align  (symbol->string (cadadr prop)))
                      (horiz-w     (hash-table-get widget-table 'halignment #f))
                      (vert-w      (hash-table-get widget-table 'valignment #f))
                     )
                     (send horiz-w
                           set-selection
                           (- (length horiz-alignment-options)
                              (length (member horiz-align horiz-alignment-options))))
                     (send vert-w
                           set-selection
                           (- (length vert-alignment-options)
                              (length (member vert-align vert-alignment-options))))
                     (set! optional-list (append (list horiz-w vert-w) optional-list))
                   )
                  )
                  ; selection
                  ((equal? (car prop) 'selection)
                   (let
                     ((w (hash-table-get widget-table 'selection #f)))
                     (if (cadr prop)
                       (send w set-value (to-string (cadr prop)))
                       (send w set-value "")
                     )
                   )
                   (set! optional-list (cons w optional-list))
                  )
                  ; style
                  ((equal? (car prop) 'style)
                   (set! optional-list (append optional-list (style-options type (cadr prop))))
                  )
                  ; choices
                  ((equal? (car prop) 'choices)
                   (let
                     ((w (hash-table-get widget-table 'choices #f)))
                     (send w set-choices (cadr prop))
                   )
                   (set! optional-list (cons w optional-list))
                  )
                )
              )
            )
            prop-lst
          )
          (send id-w set-value id)
          (set! necessary-list (append (list widget-necessary id-w)
                                       (reverse necessary-list)))
          (if (not (null? optional-list))
            (set! optional-list (cons widget-optional (reverse optional-list)))
            (set! optional-list (reverse optional-list))
          )
          (send this show #f)
          (send this 
                change-children 
                (lambda (lst) (append necessary-list optional-list)))
          (send this show #t)
        )
      )
    )
    
    (public set-value)
    (define (set-value prop val)
      (let
        ((w (hash-table-get widget-table prop #f)))
        (cond
          ((is-a? w property-value%)
           (send w set-value (to-string val))
          )
          ((is-a? w property-choice%)
           (send w set-selection val)
          )
          (else
            (error (string-append "unhandled setting of property '"
                                  (symbol->string prop) "'"))
          )
        )
      )
    )
    
    (public get-text)
    (define (get-text prop)
      (let
        ((w (hash-table-get widget-table prop #f)))
        (if (not (is-a? w property-value%))
          (error (string-append "trying to get wrong property '"
                                (symbol->string prop) "'"))
          (send w get-value)
        )
      )
    )
    
    (define (add-property-widget type text vals edit?)
      (cond
        ((list? vals)
         (hash-table-put! widget-table
                          type
                          (new property-choice%
                               (parent this)
                               (name text)
                               (choices vals)
                               (selection 0)
                               (callback (lambda (val)
                                           (if callback
                                             (callback type val))))
                               (name-width 140)))
        )
        ((string? vals)
         (hash-table-put! widget-table
                          type
                          (new property-value%
                               (parent this)
                               (name text)
                               (value vals)
                               (callback (lambda (val)
                                           (if callback
                                             (callback type val))))
                               (name-width 140)
                               (editable? edit?)))
        )
        (else
         (hash-table-put! widget-table
                          type
                          (new property-option-list%
                               (parent this)
                               (name text)
                               (choices '())
                               (callback (lambda (val)
                                           (if callback
                                             (callback type val))))
                               (width 140)
                               (empty-allowed? edit?)))
        )
      ))
    
    (define (init)
      (set! widget-table (make-hash-table 'equal))
      (set! widget-empty 
            (new canvas%
                 (parent this) 
                 (min-height 300)
                 (paint-callback 
                   (lambda (c dc)
                     (let*
                       ((w  (send c get-width))
                        (h  (send c get-height))
                        (k-bold-font  (make-object font% 11 'system 'normal 'bold #f 'smoothed #t))
                        (k-gray-brush (make-object brush% (get-panel-background) 'solid))
                        (k-invisible-pen (make-object pen% 
                                                      (make-object color% 0 0 0) 
                                                      0 
                                                      'transparent))
                       )
                       (send dc clear)
                       (send dc set-pen k-invisible-pen)
                       (send dc set-brush k-gray-brush)
                       (send dc draw-rectangle 0 0 w h)
                       (send dc set-font k-bold-font)
                       (send dc 
                             draw-text 
                             "<No selected element>"
                             (- (/ w 2) 70) (- (/ h 2) 10))
                     )))))
                                   
      (letrec
        ()
        (set! widget-necessary (new horizontal-panel% (parent this) (stretchable-height #f)))
        (new message% (label "Necessary")(parent widget-necessary))
        
        (set! widget-optional (new horizontal-panel% (parent this) (stretchable-height #f)))
        (new message% (label "Optional")(parent widget-optional))
        
        (for-each
          (lambda (x)
            (let
              ((type    (list-ref x 0))
               (text    (list-ref x 1))
               (vals    (list-ref x 2))
               (edit?   (list-ref x 3))
              )
              (add-property-widget type text vals edit?)
            )
          )
          (list '(id "id" "" #t)
                '(label "label" "" #t)
                '(width "width" "" #t)
                '(height "height" "" #t)
                '(x "x" "" #t)
                '(y "y" "" #t)
                 (list 'enabled "enabled?" boolean-options #f)
                '(border "border" "" #t)
                '(spacing "spacing" "" #t)
                 (list 'halignment "horizontal alignment" horiz-alignment-options #f)
                 (list 'valignment "vertical alignment" vert-alignment-options #f)
                '(min-width "min width" "" #t)
                '(min-height "min height" "" #t)
                 (list 'stretchable-width "stretchable width?" boolean-options #f)
                 (list 'stretchable-height "stretchable height?" boolean-options #f)
                '(vert-margin "vertical margin" "" #t)
                '(horiz-margin "horizontal margin" "" #t)
                 (list 'value "value?" boolean-options #f)
                '(min-value "min value" "" #t)
                '(max-value "max value" "" #t)
                '(init-value "init value" "" #t)
                '(range "range" "" #t)
                '(selection "selection" "" #t)
                ; style options
                 (list 'style-deleted "deleted?" boolean-options #f)
                 (list 'style-border "border?" boolean-options #f)
                 (list 'style-no-resize-border "resize border?" boolean-options #f)
                 (list 'style-no-caption "caption?" boolean-options #f)
                 (list 'style-no-system-menu "system menu?" boolean-options #f)
                 (list 'style-toolbar-button "toolbar button?" boolean-options #f)
                 (list 'style-hide-menu-bar "hide menu bar?" boolean-options #f)
                 (list 'style-float "float?" boolean-options #f)
                 (list 'style-metal "metal?" boolean-options #f)
                 (list 'style-plain "plain?" boolean-options #f)
                 
                 (list 'style-selection "selection mode?" selection-options #f)
                 (list 'style-text-field "style?" text-field-options #f)
                 (list 'style-direction "direction?" direction-options #f)
                 (list 'style-label-direction "label direction?" label-direction-options #f)
                 
                 (list 'style-hscroll "horizontal scroll?" boolean-options #f)
                 (list 'style-vscroll "vertical scroll?" boolean-options #f)
                 (list 'style-password "password?" boolean-options #f)
                 (list 'style-control-border "control border?" boolean-options #f)
                 (list 'style-resize-corner "resize corner?" boolean-options #f)
                 (list 'style-gl "OpenGL?" boolean-options #f)
                 (list 'style-no-autoclear "autoclear?" boolean-options #f)
                 (list 'style-transparent "transparent?" boolean-options #f)
                 ; choices
                 (list 'choices "choices" #f #f)
           )
        )
      )
    )
    (super-new)
    ;
    (init)
    (send this min-width 300)
;    (send this min-height 300)
  )
)
  
) ; end of module