#lang racket/base
(require ffi/unsafe
	 racket/class
	 racket/draw
	 racket/draw/unsafe/bstr
         "../../syntax.rkt"
         "../common/freeze.rkt"
         "../common/queue.rkt"
         "../common/event.rkt"
         "../common/local.rkt"
         "../../lock.rkt"
         "utils.rkt"
         "types.rkt"
         "const.rkt"
         "wndclass.rkt"
         "queue.rkt"
         "theme.rkt"
         "cursor.rkt"
         "key.rkt"
	 "dc.rkt"
         "font.rkt")

(provide
 (protect-out window%
              queue-window-event
              queue-window-refresh-event
              location->window
              flush-display
	      get-default-control-font

              GetWindowRect
              GetClientRect

              _NMHDR))

(define (unhide-cursor) (void))

(define WM_PRINT                        #x0317)
(define WM_PRINTCLIENT                  #x0318)

(define MK_LBUTTON          #x0001)
(define MK_RBUTTON          #x0002)
(define MK_SHIFT            #x0004)
(define MK_CONTROL          #x0008)
(define MK_MBUTTON          #x0010)
(define MK_XBUTTON1         #x0020)
(define MK_XBUTTON2         #x0040)

(define HTHSCROLL           6)
(define HTVSCROLL           7)

(define-user32 GetWindowRect (_wfun _HWND (rect : (_ptr o _RECT)) -> (r : _BOOL) ->
                                    (if r rect (failed 'GetWindowRect))))
(define-user32 GetClientRect (_wfun _HWND (rect : (_ptr o _RECT)) -> (r : _BOOL) ->
                                    (if r rect (failed 'GetClientRect))))

(define-user32 ClientToScreen (_wfun _HWND _POINT-pointer -> (r : _BOOL)
                                     -> (unless r (failed 'ClientToScreen))))
(define-user32 ScreenToClient (_wfun _HWND _POINT-pointer -> (r : _BOOL)
                                     -> (unless r (failed 'ClientToScreen))))

(define-gdi32 CreateFontIndirectW (_wfun _LOGFONTW-pointer -> _HFONT))

(define-shell32 DragAcceptFiles (_wfun _HWND _BOOL -> _void))

(define _HDROP _pointer)
(define-shell32 DragQueryFileW (_wfun _HDROP _UINT _pointer _UINT -> _UINT))
(define-shell32 DragFinish (_wfun _HDROP -> _void))

(define-user32 SetCapture (_wfun _HWND -> _HWND))
(define-user32 ReleaseCapture (_wfun -> _BOOL))

(define-user32 WindowFromPoint (_wfun _POINT -> _HWND))
(define-user32 GetParent (_wfun _HWND -> _HWND))
(define-user32 SetParent (_wfun _HWND _HWND -> (r : _HWND)
                                -> (unless r (failed 'SetParent))))

(define-user32 SetCursorPos (_wfun _int _int -> _BOOL))

(define-cstruct _NMHDR
  ([hwndFrom _HWND]
   [idFrom _pointer]
   [code _UINT]))

(define-user32 GetDialogBaseUnits (_fun -> _LONG))
(define measure-dc #f)

(define theme-hfont #f)

#;
(define-values (dlu-x dlu-y)
  (let ([v (GetDialogBaseUnits)])
    (values (* 1/4 (bitwise-and v #xFFFF))
	    (* 1/8 (arithmetic-shift v -16)))))

(define-cstruct _LOGBRUSH
  ([lbStyle _UINT]
   [lbColor _COLORREF]
   [lbHatch _pointer]))

(define BS_NULL 1)
(define transparent-logbrush (make-LOGBRUSH BS_NULL 0 #f))

(define-gdi32 CreateBrushIndirect (_wfun _LOGBRUSH-pointer -> _HBRUSH))

(define TRANSPARENT 1)
(define-gdi32 SetBkMode (_wfun _HDC _int -> (r : _int)
                               -> (when (zero? r) (failed 'SetBkMode))))

(define-user32 BeginPaint (_wfun _HWND _pointer -> _HDC))
(define-user32 EndPaint (_wfun _HDC _pointer -> _BOOL))

(define WM_IS_GRACKET (cast (scheme_register_process_global "PLT_WM_IS_GRACKET" #f)
                            _pointer
                            _UINT_PTR))
(define GRACKET_GUID (cast (scheme_register_process_global "PLT_GRACKET_GUID" #f)
                           _pointer
                           _bytes))
(define-cstruct _COPYDATASTRUCT
  ([dwData _pointer]
   [cbData _DWORD]
   [lpData _pointer]))

(define-cstruct _TRACKMOUSEEVENT
  ([cbSize _DWORD]
   [dwFlags _DWORD]
   [hwndTrack _HWND]
   [dwHoverTime _DWORD]))

(define TME_LEAVE #x02)
(define TME_NONCLIENT #x10)

(define-user32 TrackMouseEvent (_wfun _TRACKMOUSEEVENT-pointer -> (r : _BOOL)
                                      -> (unless r (failed 'TrackMouseEvent))))
(define-user32 GetCursorPos (_wfun _POINT-pointer -> _BOOL))

(defclass window% object%
  (init-field parent hwnd)
  (init style
        [extra-hwnds null])

  (define enabled? #t)
  (define parent-enabled? #t)

  (super-new)
  
  (define eventspace (if parent
                         (send parent get-eventspace)
                         (current-eventspace)))

  (set-hwnd-wx! hwnd this)
  (for ([extra-hwnd (in-list extra-hwnds)])
    (set-hwnd-wx! extra-hwnd this))

  (define/public (get-hwnd) hwnd)
  (define/public (get-client-hwnd) hwnd)
  (define/public (get-content-hwnd) (get-client-hwnd))
  (define/public (get-focus-hwnd) hwnd)
  (define/public (get-eventspace) eventspace)

  (define/public (is-hwnd? a-hwnd)
    (ptr-equal? hwnd a-hwnd))
  
  (define/public (wndproc w msg wParam lParam default)
    (if (try-mouse w msg wParam lParam)
        0
        (cond
         [(= msg WM_SETFOCUS)
          (set-top-focus this null w)
          (queue-window-event this (lambda () (on-set-focus)))
          0]
         [(= msg WM_KILLFOCUS)
          (queue-window-event this (lambda () (on-kill-focus)))
          0]
         [(= msg WM_SYSKEYDOWN)
          (let ([result (if (or (= wParam VK_MENU) (= wParam VK_F4)) ;; F4 is close
			    (begin
			      (unhide-cursor)
			      (default w msg wParam lParam))
			    0)])
	    (do-key w msg wParam lParam #f #f void)
	    result)]
         [(= msg WM_KEYDOWN)
          (do-key w msg wParam lParam #f #f default)]
         [(= msg WM_KEYUP)
          (do-key w msg wParam lParam #f #t default)]
         [(= msg WM_SYSCHAR)
	  (let ([result (if (= wParam VK_MENU)
			    (begin
			      (unhide-cursor)
			      (default w msg wParam lParam))
			    0)])
	    (do-key w msg wParam lParam #t #f void)
	    result)]
         [(= msg WM_CHAR)
          (do-key w msg wParam lParam #t #f default)]
	 [(= msg WM_MOUSEWHEEL)
          (gen-wheels w msg lParam (HIWORD wParam) 'wheel-down 'wheel-up)
	  0]
         [(= msg WM_MOUSEHWHEEL) ; Vista and later
          (gen-wheels w msg lParam (HIWORD wParam) 'wheel-left 'wheel-right)
          0]
         [(= msg WM_COMMAND)
          (let* ([control-hwnd (cast lParam _LPARAM _HWND)]
                 [wx (any-hwnd->wx control-hwnd)]
                 [cmd (HIWORD wParam)])
            (if (and wx (send wx is-command? cmd))
                (begin
                  (send wx do-command cmd control-hwnd)
                  0)
                (default w msg wParam lParam)))]
         [(= msg WM_NOTIFY)
          (let* ([nmhdr (cast lParam _LPARAM _NMHDR-pointer)]
                 [control-hwnd (NMHDR-hwndFrom nmhdr)]
                 [wx (any-hwnd->wx control-hwnd)]
                 [cmd (LOWORD (NMHDR-code nmhdr))])
            (if (and wx (send wx is-command? cmd))
                (begin
                  (send wx do-command-ex cmd control-hwnd nmhdr)
                  0)
                (default w msg wParam lParam)))]
         [(or (= msg WM_HSCROLL)
              (= msg WM_VSCROLL))
          (let* ([control-hwnd (cast lParam _LPARAM _HWND)]
                 [wx (any-hwnd->wx control-hwnd)])
            (if wx
                (begin
                  (send wx control-scrolled)
                  0)
                (default w msg wParam lParam)))]
         [(= msg WM_DROPFILES)
          (handle-drop-files wParam)
          0]
         ;; for single-instance applications:
         [(and (= msg WM_IS_GRACKET)
               (positive? WM_IS_GRACKET))
          ;; return 79 to indicate that this is a GRacket window
          79]
         ;; also for single-instance:
         [(= msg WM_COPYDATA)
          (handle-copydata lParam)
          0]
	 [(= msg WM_INPUTLANGCHANGE)
	  (reset-key-mapping)
	  0]
         [else
          (default w msg wParam lParam)])))

  (define/public (is-command? cmd) #f)
  (define/public (control-scrolled) #f)

  (define/public (do-command cmd control-hwnd)
    (void))
  (define/public (do-command-ex cmd control-hwnd nmhdr)
    (do-command cmd control-hwnd))

  (define/public (show on?)
    (when on? (show-children))
    (atomically (direct-show on?)))

  (define shown? #f)
  (define/public (direct-show on? [on-mode SW_SHOW])
    ;; atomic mode
    (set! shown? (and on? #t))
    (register-child-in-parent on?)
    (unless on? (not-focus-child this))
    (ShowWindow hwnd (if on? on-mode SW_HIDE)))
  (unless (memq 'deleted style)
    (show #t))
  
  (define/public (queue-on-size) (void))

  (define/public (on-set-focus) (void))
  (define/public (on-kill-focus) (void))
  (define/public (get-handle) hwnd)
  (define/public (get-client-handle) (get-content-hwnd))

  (define/public (enable on?)
    (unless (eq? enabled? (and on? #t))
      (atomically
       (let ([prev? (and enabled? parent-enabled?)])
         (set! enabled? (and on? #t))
         (let ([now? (and parent-enabled? enabled?)])
           (unless (eq? now? prev?)
             (internal-enable now?)))))))
  (define/public (parent-enable on?)
    (unless (eq? on? parent-enabled?)
      (let ([prev? (and enabled? parent-enabled?)])
        (set! parent-enabled? (and on? #t))
        (let ([now? (and parent-enabled? enabled?)])
          (unless (eq? prev? now?)
            (internal-enable now?))))))
  (define/public (internal-enable on?)
    (void (EnableWindow hwnd on?)))

  (define/public (is-window-enabled?) enabled?)
  (define/public (is-enabled-to-root?)
    (and enabled? parent-enabled?))

  (define/public (is-shown-to-root?)
    (and shown?
	 (send parent is-shown-to-root?)))

  (define/public (is-shown?)
    shown?)

  (define/public (get-x)
    (let ([r (GetWindowRect hwnd)]
          [pr (GetWindowRect (send parent get-content-hwnd))])
      (->normal (- (RECT-left r) (RECT-left pr)))))
  (define/public (get-y)
    (let ([r (GetWindowRect hwnd)]
          [pr (GetWindowRect (send parent get-content-hwnd))])
      (->normal (- (RECT-top r) (RECT-top pr)))))

  (define/public (get-width)
    (let ([r (GetWindowRect hwnd)])
      (->normal (- (RECT-right r) (RECT-left r)))))
  (define/public (get-height)
    (let ([r (GetWindowRect hwnd)])
      (->normal (- (RECT-bottom r) (RECT-top r)))))

  (define/public (notify-child-extent x y)
    (void))

  ;; Converting from normalized to screen coordinates
  ;; with just `->screen` can cause a child's right edge
  ;; to extend beyond the parent's right edge, due to
  ;; rounding via `ceiling`. Allow controls that would
  ;; look bad to round down, instead.
  (define/public (size->screen v) (->screen v))

  (define/public (set-size x y w h)
    (let-values ([(x y w h)
                  (if (or (not x)
                          (not y)
                          (= w -1)
                          (= h -1))
                      (let ([r (GetWindowRect hwnd)])
                        (values (or x (->normal (RECT-left r)))
                                (or y (->normal (RECT-top r)))
                                (if (= w -1) (->normal (- (RECT-right r) (RECT-left r))) w)
                                (if (= h -1) (->normal (- (RECT-bottom r) (RECT-top r))) h)))
                      (values x y w h))])
      (when parent (send parent notify-child-extent (+ x w) (+ y h)))
      (MoveWindow hwnd (->screen x) (->screen y) (size->screen w) (size->screen h) #t))
    (unless (and (= w -1) (= h -1))
      (on-resized))
    (queue-on-size)
    (refresh-one))
  (define/public (move x y)
    (set-size x y -1 -1))

  (define/public (set-control-font font [hwnd hwnd])
    (unless theme-hfont
      (set! theme-hfont (CreateFontIndirectW (get-theme-logfont))))
    (let ([hfont (if font
                     (or (font->hfont font)
			 theme-hfont)
                     theme-hfont)])
      (SendMessageW hwnd WM_SETFONT (cast hfont _HFONT _LPARAM) 0)))

  (define/public (auto-size font label min-w min-h dw dh
                            [resize
                             (lambda (w h) (set-size #f #f w h))]
                            #:combine-width [combine-w max]
                            #:combine-height [combine-h max]
                            #:scale-w [scale-w 1]
                            #:scale-h [scale-h 1])
    (atomically
     (unless measure-dc
       (let* ([bm (make-object win32-bitmap% 1 1 #f)]
              [dc (make-object bitmap-dc% bm)])
         (set! measure-dc dc)))
     (send measure-dc set-font (or font
				   (get-default-control-font)))
     (let-values ([(w h d a) (let loop ([label label])
                               (cond
                                [(null? label) (values 0 0 0 0)]
                                [(label . is-a? . bitmap%)
                                 (values (send label get-width)
                                         (send label get-height)
                                         0
                                         0)]
                                [(pair? label)
                                 (let-values ([(w1 h1 d1 a1)
                                               (loop (car label))]
                                              [(w2 h2 d2 a2)
                                               (loop (cdr label))])
                                   (values (combine-w w1 w2) (combine-h h1 h2)
                                           (combine-h d1 d1) (combine-h a1 a2)))]
                                [else
                                 (define strs (regexp-split #rx"\n" label))
                                 (for/fold ([w 0][h 0][d 0] [a 0]) ([str (in-list strs)])
                                   (define-values (tw th d a) (send measure-dc get-text-extent label #f #t))
                                   (values (max w tw) (+ h th) 0 0))]))]
                  [(->int) (lambda (v) (inexact->exact (ceiling v)))])
       (resize (->int (* scale-h (max (+ w dw) min-w)))
               (->int (* scale-w (max (+ h dh) min-h)))))))

  (define/public (popup-menu m x y)
    (let ([gx (box x)]
          [gy (box y)])
      (client-to-screen gx gy)
      (send m popup (unbox gx) (unbox gy)
            hwnd
            (lambda (thunk) (queue-window-event this thunk)))))

  (define/public (center a b) (void))

  (define/public (get-parent) parent)
  (define/public (set-parent p) 
    ;; in atomic mode
    (set! parent p)
    (SetParent hwnd (send parent get-content-hwnd)))

  (define/public (is-frame?) #f)

  (define/public (refresh-one) (void))
  (define/public (refresh)
    (refresh-one)
    (refresh-all-children))
  (define/public (refresh-all-children) (void))

  (define/public (on-resized) (void))

  (define event-position-wrt-wx #f)
  (define/public (set-event-positions-wrt wx)
    (set! event-position-wrt-wx wx))

  (define/private (adjust-event-position x y)
    (if event-position-wrt-wx
        (let ([xb (box x)]
              [yb (box y)])
          (internal-client-to-screen xb yb)
          (send event-position-wrt-wx internal-screen-to-client xb yb)
          (values (unbox xb) (unbox yb)))
        (values x y)))

  (define/public (screen-to-client x y)
    (internal-screen-to-client x y))
  (define/public (internal-screen-to-client x y)
    (let ([p (make-POINT (->screen (unbox x)) (->screen (unbox y)))])
      (ScreenToClient (get-client-hwnd) p)
      (set-box! x (->normal (POINT-x p)))
      (set-box! y (->normal (POINT-y p)))))
  (define/public (client-to-screen x y)
    (internal-client-to-screen x y))
  (define/public (internal-client-to-screen x y)
    (let ([p (make-POINT (->screen (unbox x)) (->screen (unbox y)))])
      (ClientToScreen (get-client-hwnd) p)
      (set-box! x (->normal (POINT-x p)))
      (set-box! y (->normal (POINT-y p)))))

  (define/public (warp-pointer x y)
    (define xb (box x))
    (define yb (box y))
    (client-to-screen xb yb)
    (void (SetCursorPos (->screen (unbox xb)) (->screen (unbox yb)))))

  (define/public (in-content? p)
    (ScreenToClient (get-client-hwnd) p)
    (let ([r (GetClientRect (get-client-hwnd))])
      (and (< 0 (POINT-x p) (- (RECT-right r)
			       (RECT-left r)))
	   (< 0 (POINT-y p) (- (RECT-bottom r)
			       (RECT-top r))))))

  (define/public (drag-accept-files on?)
    (DragAcceptFiles (get-hwnd) on?))

  (define/private (handle-drop-files wParam)
    (let* ([hdrop (cast wParam _WPARAM _HDROP)]
           [count (DragQueryFileW hdrop #xFFFFFFFF #f 0)])
      (for ([i (in-range count)])
        (let* ([len (DragQueryFileW hdrop i #f 0)]
               [b (malloc (add1 len) _int16)])
          (DragQueryFileW hdrop i b (add1 len))
          (let ([s (cast b _gcpointer _string/utf-16)])
            (queue-window-event this (lambda () (on-drop-file (string->path s)))))))
      (DragFinish hdrop)))

  (define/public (on-drop-file p) (void))

  (define/public (get-client-size w h)
    (define-values (uw uh) (get-scaled-client-size))
    (set-box! w (->normal uw))
    (set-box! h (->normal uh)))

  (define/public (get-scaled-client-size)
    (let ([r (GetClientRect (get-client-hwnd))])
      (values (- (RECT-right r) (RECT-left r))
              (- (RECT-bottom r) (RECT-top r)))))
  
  (define/public (get-gl-client-size)
    (get-scaled-client-size))

  (define/public (get-size w h)
    (let ([r (GetWindowRect (get-client-hwnd))])
      (set-box! w (->normal (- (RECT-right r) (RECT-left r))))
      (set-box! h (->normal (- (RECT-bottom r) (RECT-top r))))))

  (define cursor-handle #f)
  (define/public (set-cursor c)
    (set! cursor-handle (and c (send (send c get-driver) get-handle)))
    (when mouse-in?
      (cursor-updated-here)))

  (define/public (cursor-updated-here)
    (when mouse-in?
      (send (get-top-frame) reset-cursor (get-arrow-cursor))))

  (define/public (reset-cursor-in-child child default)
    (send child reset-cursor (or cursor-handle default)))

  (define effective-cursor-handle #f)
  (define/public (reset-cursor default)
    (let ([c (or cursor-handle default)])
      (set! effective-cursor-handle c)
      (SetCursor c)))

  (define/public (no-cursor-handle-here)
    (send parent cursor-updated-here))

  (define/public (set-focus [child-hwnd hwnd])
    (when (can-accept-focus?)
      (set-top-focus this null child-hwnd)))

  (define/public (can-accept-focus?)
    (child-can-accept-focus?))

  (define/public (child-can-accept-focus?)
    (and shown?
         (send parent child-can-accept-focus?)))

  (define/public (set-top-focus win win-path hwnd)
    (send parent set-top-focus win (cons this win-path) hwnd))
  (define/public (not-focus-child v)
    (send parent not-focus-child v))

  (define/public (gets-focus?) #f)

  (define/public (register-child child on?)
    (void))
  (define/public (register-child-in-parent on?)
    (when parent
      (send parent register-child this on?)))

  (define/public (show-children) (void))
  (define/public (paint-children) (void))

  (define/public (get-top-frame)
    (send parent get-top-frame))

  (define/private (gen-wheels w msg lParam val down up)
    (let ([orig-delta (quotient val WHEEL_DELTA)])
      (let loop ([delta (abs orig-delta)])
        (unless (zero? delta)
          (do-key w msg (if (negative? orig-delta)
                            down
                            up)
                  lParam #f #f void)
          (loop (sub1 delta))))))
  
  (define/private (do-key w msg wParam lParam is-char? is-up? default)
    (let ([e (maybe-make-key-event #f wParam lParam is-char? is-up? hwnd)])
      (if (or (and e
		   (if (definitely-wants-event? w msg wParam e)
		       (begin
			 (queue-window-event this (lambda () (dispatch-on-char/sync e)))
			 #t)
		       (constrained-reply eventspace
					  (lambda () (dispatch-on-char e #t))
					  #t)))
	      (capture-all-key-events?))
          0
          (default w msg wParam lParam))))

  (define/public (try-mouse w msg wParam lParam)
    (cond
     [(= msg WM_RBUTTONDOWN)
      (do-mouse w msg #f 'right-down wParam lParam)]
     [(= msg WM_RBUTTONUP)
      (do-mouse w msg #f 'right-up wParam lParam)]
     [(= msg WM_RBUTTONDBLCLK)
      (do-mouse w msg #f 'right-down wParam lParam)]
     [(= msg WM_MBUTTONDOWN)
      (do-mouse w msg #f 'middle-down wParam lParam)]
     [(= msg WM_MBUTTONUP)
      (do-mouse w msg #f 'middle-up wParam lParam)]
     [(= msg WM_MBUTTONDBLCLK)
      (do-mouse w msg #f 'middle-down wParam lParam)]
     [(= msg WM_LBUTTONDOWN)
      (do-mouse w msg #f 'left-down wParam lParam)]
     [(= msg WM_LBUTTONUP)
      (do-mouse w msg #f 'left-up wParam lParam)]
     [(= msg WM_LBUTTONDBLCLK)
      (do-mouse w msg #f 'left-down wParam lParam)]
     [(= msg WM_MOUSEMOVE)
      (do-mouse w msg #f 'motion wParam lParam)]
     [(= msg WM_MOUSELEAVE)
      (let ([p (make-POINT 0 0)])
        (let ([f (and (GetCursorPos p)
		      (location->window* (POINT-x p) (POINT-y p)))])
          (unless (and (eq? f (get-top-frame))
		       (send f in-content? p))
            (do-mouse w msg #f 'leave wParam lParam))))
      ;; send message on to default handling (e.g., for buttons):
      #f]
     [else (try-nc-mouse w msg wParam lParam)]))

  ;; Breaking out NC mouse operations lets us not handle
  ;; them for frames (where this method is overridden), 
  ;; since handling them intereferes with the cursor and
  ;; resize handling for frames.
  (define/public (try-nc-mouse w msg wParam lParam)
    (cond
     [(= msg WM_NCRBUTTONDOWN)
      (do-mouse w msg #t 'right-down wParam (lp-screen->client w lParam))]
     [(= msg WM_NCRBUTTONUP)
      (do-mouse w msg #t 'right-up wParam (lp-screen->client w lParam))]
     [(= msg WM_NCRBUTTONDBLCLK)
      (do-mouse w msg #t 'right-down wParam (lp-screen->client w lParam))]
     [(= msg WM_NCMBUTTONDOWN)
      (do-mouse w msg #t 'middle-down wParam (lp-screen->client w lParam))]
     [(= msg WM_NCMBUTTONUP)
      (do-mouse w msg #t 'middle-up wParam (lp-screen->client w lParam))]
     [(= msg WM_NCMBUTTONDBLCLK)
      (do-mouse w msg #t 'middle-down wParam (lp-screen->client w lParam))]
     [(= msg WM_NCLBUTTONDOWN)
      (do-mouse w msg #t 'left-down wParam (lp-screen->client w lParam))]
     [(= msg WM_NCLBUTTONUP)
      (do-mouse w msg #t 'left-up wParam (lp-screen->client w lParam))]
     [(= msg WM_NCLBUTTONDBLCLK)
      (do-mouse w msg #t 'left-down wParam (lp-screen->client w lParam))]
     [(and (= msg WM_NCMOUSEMOVE)
           (not (= wParam HTVSCROLL))
           (not (= wParam HTHSCROLL)))
      (do-mouse w msg #t 'motion wParam (lp-screen->client w lParam))]
     [else #f]))

  (define/private (lp-screen->client w lParam)
    (let ([p (make-POINT (LOWORD lParam) (HIWORD lParam))])
      (ScreenToClient w p)
      (MAKELPARAM (POINT-x p) (POINT-y p))))

  (define/private (do-mouse control-hwnd msg nc? type wParam lParam)
    (let ([x (LOWORD lParam)]
          [y (HIWORD lParam)]
          [flags (if nc? 0 wParam)]
          [bit? (lambda (v b) (not (zero? (bitwise-and v b))))])
      (let ([make-e 
             (lambda (type)
               (define-values (mx my) (adjust-event-position (->normal x) (->normal y)))
               (new mouse-event%
                    [event-type type]
                    [left-down (case type
                                 [(left-down) #t]
                                 [(left-up) #f]
                                 [else (bit? flags MK_LBUTTON)])]
                    [middle-down (case type
                                   [(middle-down) #t]
                                   [(middle-up) #f]
                                   [else (bit? flags MK_MBUTTON)])]
                    [right-down (case type
                                  [(right-down) #t]
                                  [(right-up) #f]
                                  [else (bit? flags MK_RBUTTON)])]
                    [x mx]
                    [y my]
                    [shift-down (bit? flags MK_SHIFT)]
                    [control-down (bit? flags MK_CONTROL)]
                    [meta-down #f]
                    [alt-down #f]
                    [time-stamp 0]
                    [caps-down #f]))])
        (if (eq? type 'leave)
            (let ([t (get-top-frame)])
              (send t send-child-leaves make-e)
              (send t send-leaves make-e))
            (begin
              (unless nc?
                (when (wants-mouse-capture? control-hwnd)
                  (when (memq type '(left-down right-down middle-down))
                    (SetCapture control-hwnd))
                  (when (memq type '(left-up right-up middle-up))
                    (ReleaseCapture))))
              (if mouse-in?
                  (if (send-child-leaves make-e)
                      (cursor-updated-here)
                      (if (send (get-top-frame) is-wait-cursor-on?)
                          (void (SetCursor (get-wait-cursor)))
                          (when effective-cursor-handle
                            (void (SetCursor effective-cursor-handle)))))
                  (let ([c (generate-mouse-ins this (lambda (type) (make-e type)))])
                    (TrackMouseEvent (make-TRACKMOUSEEVENT 
                                      (ctype-sizeof _TRACKMOUSEEVENT)
                                      (bitwise-ior TME_LEAVE)
                                      control-hwnd
                                      0))
                    (when c
                      (set! effective-cursor-handle c)
                      (void (SetCursor (if (send (get-top-frame) is-wait-cursor-on?)
                                           (get-wait-cursor)
                                           c))))))
              (when (memq type '(left-down right-down middle-down))
                (set-focus))
              (handle-mouse-event control-hwnd msg wParam (make-e type)))))))

  (define/private (handle-mouse-event w msg wParam e)
    (if (definitely-wants-event? w msg wParam e)
        (begin
          (queue-window-event this (lambda () (dispatch-on-event/sync e)))
          #t)
        (constrained-reply eventspace
                           (lambda () (dispatch-on-event e #t))
                           #t)))

  (define skip-enter-leave? #f)
  (define/public (skip-enter-leave-events skip?)
    (set! skip-enter-leave? skip?))

  (define mouse-in? #f)
  (define/public (generate-mouse-ins in-window mk)
    (if mouse-in?
        effective-cursor-handle
        (begin
          (set! mouse-in? #t)
          (let ([parent-cursor (generate-parent-mouse-ins mk)])
	    (unless skip-enter-leave?
              (handle-mouse-event (get-client-hwnd) 0 0 (mk 'enter)))
            (let ([c (or cursor-handle parent-cursor)])
              (set! effective-cursor-handle c)
              c)))))

  (define/public (generate-parent-mouse-ins mk)
    (send parent generate-mouse-ins this mk))

  (define/public (send-leaves mk)
    (when mouse-in?
      (set! mouse-in? #f)
      (unless skip-enter-leave?
        (when mk
          (let ([e (mk 'leave)])
	    (if (eq? (current-thread) 
		     (eventspace-handler-thread eventspace))
		(handle-mouse-event (get-client-hwnd) 0 0 e)
		(queue-window-event this
				    (lambda () (dispatch-on-event/sync e)))))))))

  (define/public (send-child-leaves mk)
    #f)

  (define/public (wants-mouse-capture? control-hwnd)
    #f)

  (define/public (definitely-wants-event? w msg wParam e)
    #f)
  (define/public (capture-all-key-events?)
    #f)

  (define/public (dispatch-on-char/sync e)
    (pre-event-refresh #t)
    (dispatch-on-char e #f))
  (define/public (dispatch-on-char e just-pre?) 
    (cond
     [(other-modal? this) #t]
     [(call-pre-on-char this e) #t]
     [just-pre? #f]
     [else (when (is-enabled-to-root?) (on-char e)) #t]))
  
  (define/public (dispatch-on-event/sync e)
    (pre-event-refresh #f)
    (dispatch-on-event e #f))
  (define/public (dispatch-on-event e just-pre?) 
    (cond
     [(other-modal? this e) #t]
     [(call-pre-on-event this e) #t]
     [just-pre? #f]
     [else (when (is-enabled-to-root?) (on-event e)) #t]))
  
  (define/public (call-pre-on-event w e)
    (or (send parent call-pre-on-event w e)
        (pre-on-event w e)))
  (define/public (call-pre-on-char w e)
    (or (send parent call-pre-on-char w e)
        (pre-on-char w e)))
  (define/public (pre-on-event w e) #f)
  (define/public (pre-on-char w e) #f)

  (define/public (on-char e) (void))
  (define/public (on-event e) (void))

  (define/private (pre-event-refresh key?)
    ;; Since we break the connection between the
    ;; Win32 queue and event handling, we
    ;; re-sync the display in case a stream of
    ;; events (e.g., key repeat) have a corresponding
    ;; stream of screen updates.
    (flush-display))

  (define/public (get-dialog-level) (send parent get-dialog-level)))

;; ----------------------------------------

(define (handle-copydata lParam)
  (let* ([cd (cast lParam _LPARAM _COPYDATASTRUCT-pointer)]
         [data (COPYDATASTRUCT-lpData cd)]
         [guid-len (bytes-length GRACKET_GUID)]
         [data-len (COPYDATASTRUCT-cbData cd)])
    (when (and (data-len
                . >= . 
                (+ guid-len 4 (ctype-sizeof _DWORD)))
               (bytes=? GRACKET_GUID
                        (scheme_make_sized_byte_string data
                                                       guid-len
                                                       0))
               (bytes=? #"OPEN"
                        (scheme_make_sized_byte_string (ptr-add data guid-len)
                                                       4
                                                       0)))
      ;; The command line's argv (sans argv[0]) is
      ;; expressed as a DWORD for the number of args,
      ;; followed by each arg. Each arg is a DWORD
      ;; for the number of chars and then the chars
      (let ([args
             (let ([count (ptr-ref data _DWORD 'abs (+ guid-len 4))])
               (let loop ([i 0] [delta (+ guid-len 4 (ctype-sizeof _DWORD))])
                 (if (or (= i count)
                         ((+ delta (ctype-sizeof _DWORD)) . > . data-len))
                     null
                     (let ([len (ptr-ref (ptr-add data delta) _DWORD)]
                           [delta (+ delta (ctype-sizeof _DWORD))])
                       (if ((+ delta len) . > . data-len)
                           null
                           (let ([s (scheme_make_sized_byte_string 
                                     (ptr-add data delta)
                                     len
                                     1)])
                             (if (or (bytes=? s #"")
                                     (regexp-match? #rx"\0" s))
                                 null
                                 (cons (bytes->path s)
                                       (loop (add1 i) (+ delta len))))))))))])
        (map queue-file-event args)))))

;; ----------------------------------------

(define default-control-font #f)
(define (get-default-control-font)
  (unless default-control-font
    (set! default-control-font
	  (make-object font%
		       (->normal (get-theme-font-size))
                       (logfont->pango-family
                         (get-theme-logfont))
		       'system
		       'normal 'normal #f 'default
		       #t)))
  default-control-font)

(define (queue-window-event win thunk)
  (queue-event (send win get-eventspace) thunk))

(define (queue-window-refresh-event win thunk)
  (queue-refresh-event (send win get-eventspace) thunk))

;; arguments in screen coordinates
(define (location->window* x y)
  (let ([hwnd (WindowFromPoint (make-POINT x y))])
    (let loop ([hwnd hwnd])
      (and hwnd
           (or (let ([wx (any-hwnd->wx hwnd)])
                 (and wx (send wx get-top-frame)))
               (loop (GetParent hwnd)))))))

(define (location->window x y)
  (location->window* (->screen x) (->screen y)))

(define (flush-display)
  (atomically
   (pre-event-sync #t)))
