#lang at-exp racket/base

;; Generates the framework code for standard menus, and the code for
;; their documentation.  This is done in a naive way (replaces older
;; code that generated files), should really be made into proper macros
;; instead.

(require (for-syntax racket/base racket/list racket/match syntax/strip-context
                     racket/pretty "standard-menus-items.rkt"))

;; build-before-super-item-clause : an-item -> (listof clause)
(define-for-syntax (build-before-super-item-clause item)
  `((define/public ,(an-item->callback-name item) ,(an-item-proc item))
    (define/public (,(an-item->get-item-name item))
      ,(an-item->item-name item))
    (define/public (,(an-item->string-name item))
      ,(an-item-menu-string item))
    (define/public (,(an-item->help-string-name item))
      ,(an-item-help-string item))
    (define/public ,(an-item->on-demand-name item) ,(an-item-on-demand item))
    (define/public (,(an-item->create-menu-item-name item))
      ,(an-item-create item))))

;; build-before-super-clause : ((X -> sym) (X sexp) -> X -> (listof clause))
(define-for-syntax ((build-before-super-clause ->name -procedure) obj)
  `((define/public ,(->name obj)
      ,(case (-procedure obj)
         [(nothing) '(λ (menu) (void))]
         [(separator) '(λ (menu) (make-object separator-menu-item% menu))]
         [(nothing-with-standard-menus)
          '(λ (menu)
             (unless (current-eventspace-has-standard-menus?)
               (make-object separator-menu-item% menu)))]
         [else (error 'gen-standard-menus "unknown between sym: ~e" (-procedure obj))]))))

;; build-before-super-between-clause : between -> (listof clause)
(define-for-syntax build-before-super-between-clause
  (build-before-super-clause between->name between-procedure))

;; build-before-super-before/after-clause : before/after -> (listof clause)
(define-for-syntax build-before-super-before/after-clause
  (build-before-super-clause before/after->name before/after-procedure))

;; build-after-super-item-clause : an-item -> (list clause)
(define-for-syntax (build-after-super-item-clause item)
  (let* ([callback-name (an-item->callback-name item)]
         [create-menu-item-name (an-item->create-menu-item-name item)]
         [callback-name-string (symbol->string callback-name)]
         [key (an-item-shortcut item)])
    `((define ,(an-item->item-name item)
        (and (,create-menu-item-name)
             ,(if (a-submenu-item? item)
                `(new (get-menu%)
                      [label (,(an-item->string-name item))]
                      [parent ,(menu-item-menu-name item)]
                      [help-string (,(an-item->help-string-name item))]
                      [demand-callback (λ (menu-item) (,(an-item->on-demand-name item) menu-item))])
                `(new ,(if (a-checkable-item? item)
                         '(get-checkable-menu-item%)
                         '(get-menu-item%))
                      [label (,(an-item->string-name item))]
                      [parent ,(menu-item-menu-name item)]
                      [callback (let ([,callback-name (λ (item evt) (,callback-name item evt))])
                                  ,callback-name)]
                      [shortcut ,key]
                      [shortcut-prefix ,(an-item-shortcut-prefix item)]
                      [help-string (,(an-item->help-string-name item))]
                      [demand-callback (λ (menu-item) (,(an-item->on-demand-name item) menu-item))])))))))

;; build-after-super-clause : ((X -> symbol) -> X -> (listof clause))
(define-for-syntax ((build-after-super-clause ->name) between/after)
  `((,(->name between/after)
     (,(menu-name->get-menu-name between/after)))))

;; build-after-super-between-clause : between -> (listof clause)
(define-for-syntax build-after-super-between-clause
  (build-after-super-clause between->name))
;; build-after-super-before/after-clause : before/after -> (listof clause)
(define-for-syntax build-after-super-before/after-clause
  (build-after-super-clause before/after->name))

;; build-after-super-generic-clause : generic -> (listof clause)
(define-for-syntax (build-after-super-generic-clause x)
  (cond [(generic-private-field? x)
         `((define ,(generic-name x) ,(generic-initializer x)))]
        [(generic-override? x) '()]
        [(generic-augment? x) '()]
        [(generic-method? x) '()]))

;; build-before-super-generic-clause : generic -> (listof clause)
(define-for-syntax (build-before-super-generic-clause generic)
  (cond [(generic-private-field? generic)
         '()]
        [(generic-override? generic)
         `((define/override ,(generic-name generic)
             ,(generic-initializer generic)))]
        [(generic-augment? generic)
         `((define/augment ,(generic-name generic)
             ,(generic-initializer generic)))]
        [(generic-method? generic)
         `((define/public ,(generic-name generic)
             ,(generic-initializer generic)))]))

(provide generate-standard-menus-code
         generate-standard-menus-interface-code)
(define-syntax (generate-standard-menus-interface-code stx)
  (datum->syntax
   stx
   `(define frame:standard-menus<%>
      (interface (frame:basic<%>)
        ,@(append-map
           (λ (x)
             (cond [(an-item? x)
                    (list (an-item->callback-name x)
                          (an-item->get-item-name x)
                          (an-item->string-name x)
                          (an-item->help-string-name x)
                          (an-item->on-demand-name x)
                          (an-item->create-menu-item-name x))]
                   [(between? x)
                    (list (between->name x))]
                   [(or (after? x) (before? x))
                    (list (before/after->name x))]
                   [(generic? x)
                    (if (generic-method? x) (list (generic-name x)) '())]))
           items)))))

(define-syntax (generate-standard-menus-code stx)
  (datum->syntax
   stx
   `(begin
      (define standard-menus-mixin
        (mixin (basic<%>) (standard-menus<%>)
          (inherit on-menu-char on-traverse-char)
          (define remove-prefs-callback
            (preferences:add-callback
             'framework:menu-bindings
             (λ (p v)
               (let loop ([menu (get-menu-bar)])
                 (when (is-a? menu menu:can-restore<%>)
                   (if v
                     (send menu restore-keybinding)
                     (send menu set-shortcut #f)))
                 (when (is-a? menu menu:can-restore-underscore<%>)
                   (if v
                     (send menu restore-underscores)
                     (send menu erase-underscores)))
                 (when (is-a? menu menu-item-container<%>)
                   (for-each loop (send menu get-items)))))))
          (inherit get-menu-bar show can-close? get-edit-target-object)
          ,@(append-map
             (λ (x)
               (cond [(between? x) (build-before-super-between-clause x)]
                     [(or (after? x) (before? x))
                      (build-before-super-before/after-clause x)]
                     [(an-item? x) (build-before-super-item-clause x)]
                     [(generic? x) (build-before-super-generic-clause x)]))
             items)
          (super-instantiate ())
          ,@(append-map
             (λ (x)
               (cond [(between? x) (build-after-super-between-clause x)]
                     [(an-item? x) (build-after-super-item-clause x)]
                     [(or (after? x) (before? x))
                      (build-after-super-before/after-clause x)]
                     [(generic? x) (build-after-super-generic-clause x)]))
             items)
          (reorder-menus this))))))

(define-for-syntax (generate-item-docs x)
  (cond
    [(generic/docs? x)
     (filter-not string? (generic/docs-documentation x))]
    [(before/after? x)
     `(@defmethod[(,(before/after->name x) [menu (is-a?/c menu-item%)]) void?]{
         This method is called @,(if (before? x) "before" "after") the addition of the
         @tt[,(format "~a" (before/after-name x))] menu-item. Override it to add additional
         menu items at that point. })]
    [(between? x)
     `(@defmethod[(,(between->name x) [menu (is-a?/c menu-item%)]) void?]{
         This method is called between the addition of the
         @tt[,(format "~a" (between-before x))] and the @tt[,(format "~a" (between-after x))] menu-item. 
         Override it to add additional menu items at that point.

      @unquote[(if (equal? (between-procedure x) 'separator)
                   `@list{Defaults to creating a @racket[separator-menu-item%].}
                   "")]
      })]
    [(an-item? x)
     `(@defmethod[(,(an-item->get-item-name x)) (or/c false/c (is-a?/c menu-item%))]{
         This method returns the @racket[menu-item%] object corresponding
         to this menu item, if it has been created (as controlled by
         @method[frame:standard-menus<%> ,(an-item->create-menu-item-name x)]).}
       @defmethod[(,(an-item->create-menu-item-name x)) boolean?]{
         The result of this method determines if the corresponding
         menu item is created. Override it to control the creation of the menu item. 
         @||
         Defaults to @racket[,(an-item-create x)].}
       ,(match (an-item-proc x)
          [`(λ (,item-name ,evt-name) ,bodies ...)
           `@defmethod[(,(an-item->callback-name x)
                        [,item-name (is-a?/c menu-item%)]
                        [,evt-name (is-a?/c control-event%)])
                       void?]{
              Defaults to
              @racketblock[,(if (= 1 (length bodies)) (car bodies) `(begin ,@bodies))] }])
       ,(match (an-item-on-demand x)
          [`(λ (,item-name) ,body)
           `@defmethod[(,(an-item->on-demand-name x)
                        [,item-name (is-a?/c menu-item%)])
                       void?]{
              The menu item's on-demand proc calls this method. 
              @||
              Defaults to @racketblock[,body]}])
       @defmethod[(,(an-item->string-name x)) string?]{
          The result of this method is used as the name of the @racket[menu-item%].
          @||
          Defaults to @racket[,(an-item-menu-string x)].}
       @defmethod[(,(an-item->help-string-name x)) string?]{
         The result of this method is used as the help string
         when the @racket[menu-item%] object is created.
         @||
         Defaults to @racket[,(an-item-help-string x)].})]
    [else '()]))

(provide generate-standard-menus-docs)
(define-syntax (generate-standard-menus-docs stx)
  (define docs
    `(definterface frame:standard-menus<%> (frame:basic<%>)
       ,@(append-map generate-item-docs items)))
  ;; HACK: the rendering includes location information from the source, so
  ;; generating a location-less syntax doesn't work.  Instead, use the pretty
  ;; printer to display the whole thing, then read the output as a syntax with
  ;; location.  Note that the 80 width is a guess that works, since indentation
  ;; includes the documentation forms too (starting from the toplevel
  ;; `definterface' and on.)
  (define o (open-output-string))
  (parameterize ([pretty-print-columns 80] [current-output-port o])
    (pretty-write docs))
  (parameterize ([port-count-lines-enabled #t])
    (replace-context
     stx
     (read-syntax 'gen-standard-menus
                  (open-input-string (get-output-string o))))))
