#lang scheme/base (require (for-syntax scheme/base) scheme/list scheme/class scheme/gui) (provide define/listen field/notify notify-methods connect-to-pref connect-to-pref/readonly override/return-false notify-box% notify-box/pref menu-option/notify-box menu-group/notify-box check-box/notify-box choice/notify-box) (define-for-syntax (join . args) (define (->string x) (cond [(string? x) x] [(symbol? x) (symbol->string)] [(identifier? x) (symbol->string (syntax-e x))] [else (error '->string)])) (string->symbol (apply string-append (map ->string args)))) (define-syntax override/return-false (syntax-rules () [(override/return-false m ...) (begin (define/override (m) #f) ...)])) (define-syntax (field/notify stx) (syntax-case stx () [(field/notify name value) (with-syntax ([init-name (datum->syntax #'name (join "init-" #'name))] [get-name (datum->syntax #'name (join "get-" #'name))] [set-name (datum->syntax #'name (join "set-" #'name))] [listen-name (datum->syntax #'name (join "listen-" #'name))]) #'(begin (field [name (init-name)]) (define/public (init-name) value) (define/public-final (get-name) (send name get)) (define/public-final (set-name new-value) (send name set new-value)) (define/public-final (listen-name listener) (send name listen listener))))])) (define-syntax (notify-methods stx) (syntax-case stx () [(notify-methods name) (with-syntax ([init-name (datum->syntax #'name (join "init-" #'name))] [get-name (datum->syntax #'name (join "get-" #'name))] [set-name (datum->syntax #'name (join "set-" #'name))] [listen-name (datum->syntax #'name (join "listen-" #'name))]) #'(begin (field [name (init-name)]) (define/public (init-name) (new notify-box% (value #f))) (define/public-final (get-name) (send name get)) (define/public-final (set-name new-value) (send name set new-value)) (define/public-final (listen-name listener) (send name listen listener))))])) (define-syntax (connect-to-pref stx) (syntax-case stx () [(connect-to-pref name pref) (with-syntax ([init-name (datum->syntax #'name (join "init-" #'name))]) #'(define/override (init-name) (notify-box/pref pref)))])) (define-syntax (connect-to-pref/readonly stx) (syntax-case stx () [(connect-to-pref/readonly name pref) (with-syntax ([init-name (datum->syntax #'name (join "init-" #'name))]) #'(define/override (init-name) (notify-box/pref/readonly pref)))])) (define-syntax (define/listen stx) (syntax-case stx () [(define/listen name value) (unless (identifier? #'name) (raise-syntax-error 'define/listen "expected identifier" #'name)) (with-syntax ([get-name (datum->syntax #'name (join "get-" #'name))] [set-name (datum->syntax #'name (join "set-" #'name))] [listen-name (datum->syntax #'name (join "listen-" #'name))]) #'(begin (define name value) (define listeners null) (define/public-final (get-name) name) (define/public-final (set-name new-value) (set! name new-value) (for-each (lambda (listener) (listener new-value)) listeners)) (define/public-final (listen-name listener) (set! listeners (cons listener listeners)))))])) (define notify-box% (class object% (init value) (define v value) (define listeners null) ;; get : -> value ;; Fetch current value (define/public (get) v) ;; set : value -> void ;; Update value and notify listeners (define/public (set nv) (set! v nv) (for-each (lambda (p) (p nv)) listeners)) ;; listen : (value -> void) -> void ;; Add a listener (define/public (listen p) (set! listeners (cons p listeners))) ;; remove-listener : (value -> void) -> void (define/public (remove-listener p) (set! listeners (remq p listeners))) ;; remove-all-listeners : -> void (define/public (remove-all-listeners) (set! listeners null)) (super-new))) (define (notify-box/pref pref) (define nb (new notify-box% (value (pref)))) (send nb listen pref) nb) (define (notify-box/pref/readonly pref) (new notify-box% (value (pref)))) (define (menu-option/notify-box parent label nb) (define menu-item (new checkable-menu-item% (label label) (parent parent) (checked (send nb get)) (callback (lambda _ (send nb set (send menu-item is-checked?)))))) (send nb listen (lambda (value) (send menu-item check value))) menu-item) (define (check-box/notify-box parent label nb) (define checkbox (new check-box% (label label) (parent parent) (value (send nb get)) (callback (lambda (c e) (send nb set (send c get-value)))))) (send nb listen (lambda (value) (send checkbox set-value value))) checkbox) (define (choice/notify-box parent label choices nb) (define choice (new choice% (label label) (parent parent) (style '(horizontal-label)) (choices choices) (callback (lambda (c e) (send nb set (send c get-string-selection)))))) (send choice set-string-selection (send nb get)) (send nb listen (lambda (value) (send choice set-string-selection value))) choice) (define (menu-group/notify-box parent labels nb) (map (lambda (option) (define label (if (pair? option) (car option) option)) (define menu-item (new checkable-menu-item% (label label) (parent parent) (checked (eq? (send nb get) option)) (callback (lambda _ (send nb set option))))) (send nb listen (lambda (value) (send menu-item check (eq? value option)))) menu-item) labels))