;;;-*-Mode: LISP; Package: GUI -*-
;;;
;;; Copyright 2004 Clozure Associates
;;;
;;; Licensed under the Apache License, Version 2.0 (the "License");
;;; you may not use this file except in compliance with the License.
;;; You may obtain a copy of the License at
;;;
;;;     http://www.apache.org/licenses/LICENSE-2.0
;;;
;;; Unless required by applicable law or agreed to in writing, software
;;; distributed under the License is distributed on an "AS IS" BASIS,
;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
;;; See the License for the specific language governing permissions and
;;; limitations under the License.

(in-package "GUI")

(defstruct cocoa-default
  symbol                                ; a lisp special variable
  string                                ; an NSConstantString
  type                                  ; a keyword
  value                                 ; the "standard" initial value
  doc                                   ; a doc string
  change-hook                           ; an optional hook function
  )

(let* ((cocoa-defaults ()))
  (defun %get-cocoa-default (name)
    (find name cocoa-defaults :key #'cocoa-default-symbol))
  (defun %put-cocoa-default (default)
    (push default cocoa-defaults))
  (defun cocoa-defaults () cocoa-defaults)
  (defun %remove-cocoa-default (name)
    (setq cocoa-defaults
          (delete name cocoa-defaults :key #'cocoa-default-symbol)))
  (defun %clear-cocoa-defaults () (setq cocoa-defaults nil)))

(defun set-cocoa-default (name string type value doc &optional change-hook)
  (check-type name symbol)
  (check-type string ccl::objc-constant-string)
  (check-type type keyword)
  (check-type doc (or null string))
  (%remove-cocoa-default name)
  (%put-cocoa-default (make-cocoa-default :symbol name
                                          :string string
                                          :type type
                                          :value value
                                          :doc doc
                                          :change-hook change-hook))
  (if (eq type :color)
    (apply #'color-values-to-nscolor value)
    value))

;;; Names which contain #\* confuse Cocoa Bindings.
(defun objc-default-key (name)
  (ccl::ns-constant-string (ccl::lisp-to-objc-message (list (make-symbol (remove #\* (string name)))))))
  

(defun %define-cocoa-default (name type value doc &optional change-hook)
  (proclaim `(special ,name))
  ;; Make the variable "GLOBAL": its value can be changed, but it can't
  ;; have a per-thread binding.
  (ccl::%symbol-bits name (logior (ash 1 ccl::$sym_vbit_global)
				  (the fixnum (ccl::%symbol-bits name))))
  (record-source-file name 'variable)
  (setf (documentation name 'variable) doc)
  (set name (set-cocoa-default name (objc-default-key name) type value doc change-hook))
  name)
  
  

(defmacro def-cocoa-default (name type value  doc &optional change-hook &environment env)
  `(progn
     (eval-when (:compile-toplevel)
       (ccl::note-variable-info ',name :global ,env))
    (declaim (special ,name))
    (defloadvar ,name nil)
    (%define-cocoa-default ',name  ',type ,value ',doc ,change-hook)))

    
(defun update-cocoa-defaults ()
  (update-cocoa-defaults-list
   (#/standardUserDefaults ns:ns-user-defaults)
   (cocoa-defaults)))

(defun update-cocoa-defaults-list (domain defaults)
  (dolist (d defaults)
    (let* ((name (cocoa-default-symbol d))
           (type (cocoa-default-type d)) 
           (key (ccl::objc-constant-string-nsstringptr (cocoa-default-string d))))
      (let* ((hook (cocoa-default-change-hook d))
             (old-value (symbol-value name)))
        (case type
          (:int
           (set name (#/integerForKey: domain key)))
          (:float
           (set name (#/floatForKey: domain key)))
          (:bool
           (set name (#/boolForKey: domain key)))
          (:string
           (let* ((nsstring (#/stringForKey: domain key)))
             (unless (%null-ptr-p nsstring)
               (set name (lisp-string-from-nsstring nsstring)))))
          ((:color :font)
           (let* ((data (#/dataForKey: domain key)))
             (unless (%null-ptr-p data)
               (set name (#/retain (#/unarchiveObjectWithData: ns:ns-keyed-unarchiver data)))))))
        (when hook (funcall hook old-value (symbol-value name)))))))



;;; Return an NSDictionary describing the "default" values of the defaults.
(defun cocoa-defaults-initial-values ()
  (let* ((defaults (cocoa-defaults))
         (dict (make-instance 'ns:ns-mutable-dictionary
                              :with-capacity (length defaults))))
    (dolist (d defaults dict)
      (let* ((value (cocoa-default-value d)))
        (#/setObject:forKey: dict
                             (case (cocoa-default-type d)                               
                               (:color (#/archivedDataWithRootObject:
                                        ns:ns-keyed-archiver
                                        (apply #'color-values-to-nscolor value)))
			       (:font (#/archivedDataWithRootObject:
                                       ns:ns-keyed-archiver
                                       (funcall value)))
                               (:bool (if value #@"YES" #@"NO"))
                               (t
                                (%make-nsstring (format nil "~a" (cocoa-default-value d)))))
                             (ccl::objc-constant-string-nsstringptr (cocoa-default-string d)))))))
