#lang syndicate ;; Driver for Racket's GUI. (require racket/class) (require racket/gui) (provide (struct-out widget) (struct-out widget-prop) (struct-out widget-object) (struct-out widget-event) assert-widget) (module+ implementation-details (provide (struct-out widget-query) (struct-out frames-present) (struct-out frame-closing))) (struct widget (id type) #:prefab) ;; assertion (struct widget-prop (id key value) #:prefab) ;; assertion (struct widget-object (id object) #:prefab) ;; assertion (struct widget-event (id class type event) #:prefab) ;; message (struct widget-query (object key ch) #:prefab) ;; message (struct frames-present () #:prefab) ;; assertion (struct frame-closing (object) #:prefab) ;; message (define-syntax-rule (assert-widget id type [prop init] ...) (begin (assert (widget id type)) (assert (widget-prop id 'prop init)) ...)) (define *widget-types* (make-hasheq)) (define (define-widget-type! type proc) (hash-set! *widget-types* type proc)) (define-syntax-rule (define-widget-type (type id) body ...) (define-widget-type! 'type (lambda (id _type) body ...))) (define-syntax defprop (syntax-rules () [(_ w id prop-key #:widget setter-expr) (begin (define/query-value -wid- #f (widget-prop id 'prop-key $wid-value) wid-value) (define/query-value prop-key #f (widget-object (-wid-) $o) o #:on-add (let ((prop-key (prop-key))) (log-info "Invoking setter for ~a widget-prop ~a: ~a" id 'prop-key prop-key) setter-expr)))] [(_ w id prop-key default setter-expr) (define/query-value prop-key default (widget-prop id 'prop-key $prop-value) prop-value #:on-add (let ((prop-key (prop-key))) (log-info "Invoking setter for ~a prop ~a: ~a" id 'prop-key prop-key) setter-expr))])) (define-syntax-rule (with-widget-props w id [[defprop-items ...] ...] body ...) (begin (defprop w id defprop-items ...) ... (on-start (flush!) ;; allow queries to have a go at the dataspace (react body ...)))) (define-syntax-rule (maybe-send receiver selector args ...) (and receiver (send receiver selector args ...))) (define syndicate-frame% (class* frame% () (super-new) (define/augment (can-close?) (define ch (make-channel)) (send-ground-message (widget-query this 'can-close? ch)) (define answers (channel-get ch)) (not (set-member? answers #f))) (define/augment (on-close) (send-ground-message (frame-closing this))))) (define-widget-type (frame id) (define outermost-facet (current-facet-id)) (define w #f) (on-stop (maybe-send w show #f)) (with-widget-props w id [[label "" (maybe-send w set-label label)] [parent #:widget (maybe-send w reparent parent)]] (parameterize ((current-eventspace (make-eventspace))) (set! w (new syndicate-frame% [label (label)] [parent (parent)] )) (send w show #t)) (on (message (inbound (frame-closing w))) (stop-facet outermost-facet)) (assert (frames-present)) (assert (widget-object id w)))) (define-syntax-rule (during-parent [id parent-id-var parent-var] body ...) (during (widget-prop id 'parent $parent-id) (during (widget-object parent-id $parent) (log-info "~a --parent--> ~a" id parent-id) (let ((parent-id-var parent-id) (parent-var parent)) body ...)))) (define (control-event-callback id) (lambda (_widget e) (send-ground-message (widget-event id 'control (send e get-event-type) e)))) (define-syntax-rule (on-stop-delete-child w) (on-stop (maybe-send (maybe-send w get-parent) delete-child w))) (define-widget-type (button id) (during-parent [id parent-id parent] (define w #f) (on-stop-delete-child w) (with-widget-props w id [[label "" (maybe-send w set-label label)] [enabled #t (maybe-send w enable enabled)]] (set! w (new button% [label (label)] [parent parent] [enabled (enabled)] [callback (control-event-callback id)])) (assert (widget-object id w))))) (define-widget-type (message id) (during-parent [id parent-id parent] (define w #f) (on-stop-delete-child w) (with-widget-props w id [[label "" (maybe-send w set-label label)]] (set! w (new message% [label (label)] [parent parent])) (assert (widget-object id w))))) (define-syntax-rule (on-stop-delete-menu-item w) (on-stop (maybe-send w delete))) (define (get-menu-eventspace x) (cond [(is-a? x menu%) (get-menu-eventspace (send x get-parent))] [(is-a? x menu-bar%) (get-menu-eventspace (send x get-frame))] [(is-a? x frame%) (send x get-eventspace)])) (define-widget-type (menu-bar id) (during-parent [id parent-id parent] (define w #f) ;; (on-stop-delete w) ;; It turns out there is no way to remove a menu-bar% from a frame% ! (with-widget-props w id [] (parameterize ((current-eventspace (get-menu-eventspace parent))) (set! w (new menu-bar% [parent parent]))) (assert (widget-object id w))))) (define-widget-type (menu id) (during-parent [id parent-id parent] (define w #f) (on-stop-delete-menu-item w) (with-widget-props w id [[label "" (maybe-send w set-label label)]] (parameterize ((current-eventspace (get-menu-eventspace parent))) (set! w (new menu% [label (label)] [parent parent]))) (assert (widget-object id w))))) (define-widget-type (menu-item id) (during-parent [id parent-id parent] (define w #f) (on-stop-delete-menu-item w) (with-widget-props w id [[label "" (maybe-send w set-label label)]] (parameterize ((current-eventspace (get-menu-eventspace parent))) (set! w (new menu-item% [label (label)] [parent parent] [callback (control-event-callback id)]))) (assert (widget-object id w))))) ;;--------------------------------------------------------------------------- (spawn #:name 'widget-server (during/spawn (widget $id $type) #:name (widget id type) (match (hash-ref *widget-types* type #f) [#f (error 'widget "Invalid widget type ~a (ID ~a, parent-id ~a)" type id)] [proc (proc id type)])) (during (frames-present) (on (message (inbound (widget-event $id $class $type $event))) (send! (widget-event id class type event))) (on (message (inbound (widget-query $w $prop-key $ch))) (react (define f (current-facet-id)) (during (widget-object $id w) (on-start (channel-put ch (immediate-query (query-set (widget-prop id prop-key $v) v))) (stop-facet f)))))))