#lang racket/base
(require racket/class
         ffi/unsafe
          "../../syntax.rkt"
          "../../lock.rkt"
          "../common/freeze.rkt"
          "../common/queue.rkt"
          "widget.rkt"
          "window.rkt"
          "utils.rkt"
          "types.rkt")

(provide
 (protect-out menu-bar%
              gtk_menu_item_new_with_mnemonic
              gtk_menu_shell_append
              fixup-mnemonic))

(define-gtk gtk_menu_bar_new (_fun -> _GtkWidget))
(define-gtk gtk_menu_shell_append (_fun _GtkWidget _GtkWidget -> _void))
(define-gtk gtk_menu_item_new_with_mnemonic (_fun _string -> _GtkWidget))
(define-gtk gtk_menu_item_set_submenu (_fun _GtkWidget (_or-null _GtkWidget) -> _void))
(define-gtk gtk_container_remove (_fun _GtkWidget _GtkWidget -> _void))
(define-gtk gtk_label_set_text_with_mnemonic (_fun _GtkWidget _string -> _void))
(define-gtk gtk_bin_get_child (_fun _GtkWidget -> _GtkWidget))

(define-gtk gtk_widget_set_usize (_fun _GtkWidget _int _int -> _void)
  #:fail (lambda () gtk_widget_set_size_request))

(define-gtk ubuntu_menu_proxy_get _fpointer
  #:fail (lambda () #f))

(define (fixup-mnemonic title)
  (mnemonic-string (regexp-replace #rx"\t.*$" title "")))

(define-signal-handler connect-select "select"
  (_fun _GtkWidget -> _void)
  (lambda (gtk)
    (let ([wx (gtk->wx gtk)])
      (when wx
        (let ([frame (send wx get-top-window)])
          (when frame
            (constrained-reply (send frame get-eventspace)
                               (lambda () (send frame on-menu-click))
                               (void))))))))

(define-signal-handler connect-ubuntu-local "notify::ubuntu-local"
  (_fun _GtkWidget -> _void)
  (lambda (gtk)
    (let ([wx (gtk->wx gtk)])
      (when wx
	(send wx reset-menu-height)))))

(define top-menu%
  (class widget%
    (init-field parent)
    (define/public (get-top-window) (send parent get-top-window))
    (super-new)))

(define-signal-handler connect-menu-key-press "key-press-event"
  (_fun _GtkWidget _GdkEventKey-pointer -> _gboolean)
  (lambda (gtk event)
    (let ([wx (gtk->wx gtk)])
      (or (not wx)
          (other-modal? wx)))))

(define-signal-handler connect-menu-button-press "button-press-event"
  (_fun _GtkWidget _GdkEventButton-pointer -> _gboolean)
  (lambda (gtk event)
    (let ([wx (gtk->wx gtk)])
      (or (not wx)
          (other-modal? wx)))))

(defclass menu-bar% widget%
  (inherit install-widget-parent)

  (define menus null)

  (define gtk (as-gtk-allocation (gtk_menu_bar_new)))
  (super-new [gtk gtk])

  (define/public (get-gtk) gtk)

  (connect-menu-key-press gtk)
  (connect-menu-button-press gtk)

  ;; Ubuntu patches Gtk so that a menu bar starts
  ;; as "remote" instead of appearing in a frame.
  ;; For configurations that put the menu bar in a frame,
  ;; the "notify::ubuntu-local" signal is issued.
  (connect-ubuntu-local gtk)

  ; (gtk_menu_set_accel_group gtk the-accelerator-group)

  (define top-wx #f)

  (define/public (set-top-window top)
    (set! top-wx top)
    (when ubuntu_menu_proxy_get
      (send top treat-focus-out-as-menu-click))
    (install-widget-parent top)
    (fix-menu-height))

  (define/public (reset-menu-height)
    (when (and (not gtk3?) top-wx)
      (send top-wx reset-menu-height (fix-menu-height))))

  (define/private (fix-menu-height)
    ;; a menu to make sure there is one,
    ;; and force the menu bar to be at least that tall always
    (atomically
     (define item 
       (and (null? menus)
	    (gtk_menu_item_new_with_mnemonic "Xyz")))
     (when item
       (gtk_menu_shell_append gtk item)
       (gtk_widget_show item))
     (define req (make-GtkRequisition 0 0))
     (gtk_widget_size_request gtk req)
     (define height (GtkRequisition-height req))
     (gtk_widget_set_usize gtk -1 height)
     (when item
       (gtk_container_remove gtk item))
     height))

  (define/public (get-top-window)
    top-wx)

  (define/public (get-dialog-level) 
    (send top-wx get-dialog-level))

  (define/public (set-label-top pos str)
    (let ([l (list-ref menus pos)])
      (let ([item-gtk (car l)])
	(gtk_label_set_text_with_mnemonic (gtk_bin_get_child item-gtk) 
                                          (fixup-mnemonic str)))))
    
  (define/public (enable-top pos on?)
    (gtk_widget_set_sensitive (car (list-ref menus pos)) on?))

  (define/public (delete which pos)
    (atomically
     (set! menus (let loop ([menus menus]
                            [pos pos])
                   (cond
                    [(null? menus) menus]
                    [(zero? pos) 
                     (gtk_container_remove gtk (caar menus))
                     (gtk_menu_item_set_submenu (caar menus) #f)
                     (cdr menus)]
                    [else (cons (car menus)
                                (loop (cdr menus)
                                      (sub1 pos)))])))))

  (public [append-menu append])
  (define (append-menu menu title)
    (send menu set-parent this)
    (atomically
     (let* ([item (let ([title (fixup-mnemonic title)])
                    (as-gtk-allocation
                     (gtk_menu_item_new_with_mnemonic title)))]
            [item-wx (new top-menu% [parent this] [gtk item])])
       (connect-select item)
       (set! menus (append menus (list (list item menu item-wx))))
       (let ([gtk (send menu get-gtk)])
         (g_object_ref gtk)
         (gtk_menu_item_set_submenu item gtk))
       (gtk_menu_shell_append gtk item)
       (gtk_widget_show item))))

  (define/public (activate-item menu)
    ;; Gtk takes care of menu activation as appropriate;
    ;; return #f to indcate that the key wasn't handled
    #f
    #;
    (let loop ([menus menus])
      (cond
       [(null? menus) (void)]
       [(eq? menu (cadar menus))
	(gtk_menu_shell_select_item gtk (caar menus))]
       [else (loop (cdr menus))]))))
