;;;
;;; hwnd.lisp
;;;
;;; Windows and window handles.
;;;

(cl:in-package :lh-winapi)


;;;; Storage representation.

;;; We represent a window as a wrapper class and an underlying integer
;;; window handle.  User-defined window classes are expected to
;;; subclass this wrapper to customize their behavior with respect to
;;; window message handling and so on.

(defclass window ()
  ((hwnd :accessor window-hwnd :initarg hwnd)))


;;;; "Interning".

;;; To provide EQ-level object identity for window wrappers over the
;;; same window handle we "intern" the wrappers in one of two hash
;;; tables keyed by the window handle.  One of the hash tables is a
;;; weak hash table used for "outside" window handles not created or
;;; explicitly registered by Lisp code.  The other is a normal hash
;;; table for windows created by Lisp.  This split is to allow the
;;; externally-managed window handles to be collected as needed while
;;; maintaining references to those windows which were created from
;;; Lisp and thus presumably have some specific functionality
;;; associated with the wrapper class.

;;; There is one known issue with the default behavior here, and that
;;; is window creation, and there are two parts to it.  First, we get
;;; (believed to be synchronous) window messages when a new window is
;;; created before we learn its HWND through any other channel.
;;; Second, when we create controls such as buttons we have to use a
;;; function (CreateWindow) that returns a new HWND.  In order to
;;; acommodate these two scenarios, we use a special variable
;;; *window-being-created* that holds the lisp-side window instance
;;; that is being set up.  When this occurs, we store the new HWND
;;; value in the wrapper and return the wrapper.

(defvar *known-lisp-windows* (make-hash-table))
(defvar *known-alien-windows* (make-hash-table :weakness :value))

;;; During creation, a window will be sent messages before we can set
;;; the hwnd in the window object itself or add the reverse mapping.
;;; Or if it's a control, we still have the HWND return value from
;;; CreateWindow.  In order to cover for these cases, when we try to
;;; look up an HWND not in the mapping table, we check to see if
;;; *window-being-created* is bound, and if so we set up the forward
;;; and reverse HWND mappings for the window and register it as a lisp
;;; window.
(defvar *window-being-created*)

(defun register-window (window hwnd)
  (setf (window-hwnd window) hwnd)
  (setf (gethash hwnd *known-lisp-windows*) window))

(defun unregister-window (window)
  (remhash (window-hwnd window) *known-lisp-windows*)
  (slot-makunbound window 'hwnd))

(defun registered-window (hwnd)
  (gethash hwnd *known-lisp-windows*))

(defun find-known-window (hwnd)
  (or (gethash hwnd *known-lisp-windows*)
      (gethash hwnd *known-alien-windows*)))

(defun find-window (hwnd)
  (or (find-known-window hwnd)
      (and (boundp '*window-being-created*)
	   (let ((window *window-being-created*))
	     (makunbound 's*window-being-created*)
	     (register-window window hwnd)
	     window))
      (setf (gethash hwnd *known-alien-windows*)
	    (make-instance 'window 'hwnd hwnd))))


;;;; Alien type definition.

;;; This is based on the original work done for translated alien type
;;; definition in lh-guid.lisp.  An initial difference is that as we
;;; have no need for implicit pass-by-reference semantics we do not
;;; need to inherit from system-area-pointer.  This difference and the
;;; smaller size of the underlying representation has an affect on the
;;; naturalize, deport, extract and deposit methods.  A possible
;;; future change would be parameterizing the type to include the
;;; actual window class name as known to windows in order to allow for
;;; smarter wrapper / proxy object creation.

;;; First, the basics.  Class definition, translator definition and
;;; supporting methods for type tranlsation.  We still include a type
;;; so that we don't have to worry about arg-tn or result-tn methods.

(define-alien-type-class (hwnd :include integer :include-args (signed)))

(define-alien-type-translator hwnd ()
  (make-alien-hwnd-type :bits 32 :signed nil))

(define-alien-type-method (hwnd :unparse) (type)
  (declare (ignore type))
  'hwnd)

(define-alien-type-method (hwnd :lisp-rep) (type)
  (declare (ignore type))
  '(or window null))

;;; The same logic applies for type= and subtypep methods as did for
;;; the original guid example.

;;; As for the rest of the methods, the versions inherited from the
;;; integer type work well for us except for naturalize and deport.
;;; For those we want to do our hwnd <-> wrapper lookup.

(define-alien-type-method (hwnd :naturalize-gen) (type alien)
  (declare (ignore type))
  `(if (not (zerop ,alien))
    (find-window ,alien)
    nil))

(define-alien-type-method (hwnd :deport-gen) (type object)
  (declare (ignore type))
  `(if ,object
    (the (unsigned-byte 32) (window-hwnd ,object))
    0))


;;; EOF
