chiark / gitweb /
Added more bindings to GtkWindow
[clg] / glib / gcallback.lisp
CommitLineData
112ac1d3 1;; Common Lisp bindings for GTK+ v2.x
2;; Copyright 2000 Espen S. Johnsen <espen@users.sf.net>
c8c48a4c 3;;
112ac1d3 4;; Permission is hereby granted, free of charge, to any person obtaining
5;; a copy of this software and associated documentation files (the
6;; "Software"), to deal in the Software without restriction, including
7;; without limitation the rights to use, copy, modify, merge, publish,
8;; distribute, sublicense, and/or sell copies of the Software, and to
9;; permit persons to whom the Software is furnished to do so, subject to
10;; the following conditions:
c8c48a4c 11;;
112ac1d3 12;; The above copyright notice and this permission notice shall be
13;; included in all copies or substantial portions of the Software.
c8c48a4c 14;;
112ac1d3 15;; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16;; EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
17;; MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18;; IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
19;; CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
20;; TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
21;; SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
22
e1fcda22 23;; $Id: gcallback.lisp,v 1.35 2006-06-07 13:16:11 espen Exp $
c8c48a4c 24
25(in-package "GLIB")
26
27(use-prefix "g")
28
29
e0d2987b 30;;;; Callback invokation
c8c48a4c 31
6851535e 32(deftype gclosure () 'pointer)
33(register-type 'gclosure '|g_closure_get_type|)
34
e378b861 35(defun register-callback-function (function)
36 (check-type function (or null symbol function))
37 (register-user-data function))
c8c48a4c 38
e0d2987b 39;; Callback marshal for regular signal handlers
56ccd5b7 40(define-callback closure-marshal nil
41 ((gclosure gclosure) (return-value gvalue) (n-params unsigned-int)
42 (param-values pointer) (invocation-hint pointer)
43 (callback-id unsigned-int))
266ca870 44 (declare (ignore gclosure invocation-hint))
e0d2987b 45 (callback-trampoline callback-id n-params param-values return-value))
c8c48a4c 46
e0d2987b 47;; Callback function for emission hooks
56ccd5b7 48(define-callback signal-emission-hook nil
49 ((invocation-hint pointer) (n-params unsigned-int) (param-values pointer)
50 (callback-id unsigned-int))
5de2b5f6 51 (declare (ignore invocation-hint))
e0d2987b 52 (callback-trampoline callback-id n-params param-values))
53
54(defun callback-trampoline (callback-id n-params param-values &optional
55 (return-value (make-pointer 0)))
c8c48a4c 56 (let* ((return-type (unless (null-pointer-p return-value)
e378b861 57 (gvalue-type return-value)))
34f9e1d4 58 (args (loop
59 for n from 0 below n-params
832df308 60 for offset from 0 by +gvalue-size+
77a843ca 61 collect (gvalue-peek (pointer+ param-values offset)))))
832df308 62 (unwind-protect
63 (let ((result (apply #'invoke-callback callback-id return-type args)))
64 (when return-type
65 (gvalue-set return-value result)))
77a843ca 66 ;; TODO: this should be made more general, by adding a type
67 ;; method to return invalidate functions.
832df308 68 (loop
69 for arg in args
77a843ca 70 when (typep arg 'struct)
832df308 71 do (invalidate-instance arg)))))
72
34f9e1d4 73
8755b1a5 74(defun invoke-callback (callback-id return-type &rest args)
34f9e1d4 75 (restart-case
76 (apply (find-user-data callback-id) args)
77 (continue nil :report "Return from callback function"
8755b1a5 78 (when return-type
79 (format *query-io* "Enter return value of type ~S: " return-type)
34f9e1d4 80 (force-output *query-io*)
81 (eval (read *query-io*))))
82 (re-invoke nil :report "Re-invoke callback function"
8755b1a5 83 (apply #'invoke-callback callback-id return-type args))))
c8c48a4c 84
c8c48a4c 85
e378b861 86;;;; Timeouts and idle functions
87
acd28982 88(defconstant +priority-high+ -100)
89(defconstant +priority-default+ 0)
90(defconstant +priority-high-idle+ 100)
91(defconstant +priority-default-idle+ 200)
92(defconstant +priority-low+ 300)
93
94(defbinding source-remove () boolean
95 (tag unsigned-int))
96
56ccd5b7 97(define-callback source-callback-marshal nil ((callback-id unsigned-int))
e0d2987b 98 (callback-trampoline callback-id 0 nil))
e378b861 99
100(defbinding (timeout-add "g_timeout_add_full")
acd28982 101 (interval function &optional (priority +priority-default+)) unsigned-int
e378b861 102 (priority int)
103 (interval unsigned-int)
56ccd5b7 104 (source-callback-marshal callback)
e378b861 105 ((register-callback-function function) unsigned-long)
56ccd5b7 106 (user-data-destroy-callback callback))
e378b861 107
acd28982 108(defun timeout-remove (timeout)
109 (source-remove timeout))
110
e378b861 111(defbinding (idle-add "g_idle_add_full")
acd28982 112 (function &optional (priority +priority-default-idle+)) unsigned-int
e378b861 113 (priority int)
56ccd5b7 114 (source-callback-marshal callback)
e378b861 115 ((register-callback-function function) unsigned-long)
56ccd5b7 116 (user-data-destroy-callback callback))
e378b861 117
acd28982 118(defun idle-remove (idle)
119 (source-remove idle))
e378b861 120
c8c48a4c 121
e0d2987b 122;;;; Signal information querying
c8c48a4c 123
e0d2987b 124(defbinding signal-lookup (name type) unsigned-int
c8c48a4c 125 ((signal-name-to-string name) string)
e0d2987b 126 ((find-type-number type t) type-number))
c8c48a4c 127
e0d2987b 128(defbinding signal-name () (copy-of string)
c8c48a4c 129 (signal-id unsigned-int))
130
e0d2987b 131(defbinding signal-list-ids (type) (vector unsigned-int n-ids)
132 ((find-type-number type t) type-number)
133 (n-ids unsigned-int :out))
134
135(defun signal-list-names (type)
136 (map 'list #'signal-name (signal-list-ids type)))
137
138(defun ensure-signal-id-from-type (signal-id type)
c8c48a4c 139 (etypecase signal-id
e0d2987b 140 (integer (if (signal-name signal-id)
141 signal-id
142 (error "Invalid signal id: ~D" signal-id)))
143 ((or symbol string)
144 (let ((numeric-id (signal-lookup signal-id type)))
145 (if (zerop numeric-id)
146 (error "Invalid signal name for ~S: ~D" type signal-id)
147 numeric-id)))))
148
149(defun ensure-signal-id (signal-id instance)
150 (ensure-signal-id-from-type signal-id (type-of instance)))
c8c48a4c 151
e0d2987b 152(eval-when (:compile-toplevel :load-toplevel :execute)
153 (deftype signal-flags ()
154 '(flags :run-first :run-last :run-cleanup :no-recurse
155 :detailed :action :no-hooks))
156
157 (defclass signal-query (struct)
158 ((id :allocation :alien :type unsigned-int)
159 (name :allocation :alien :type (copy-of string))
160 (type :allocation :alien :type type-number)
161 (flags :allocation :alien :type signal-flags)
162 (return-type :allocation :alien :type type-number)
163 (n-params :allocation :alien :type unsigned-int)
164 (param-types :allocation :alien :type pointer))
165 (:metaclass struct-class)))
166
167(defbinding signal-query
168 (signal-id &optional (signal-query (make-instance 'signal-query))) nil
169 (signal-id unsigned-int)
77a843ca 170 (signal-query signal-query :in/return))
e0d2987b 171
172(defun signal-param-types (info)
173 (with-slots (n-params param-types) info
174 (map-c-vector 'list
175 #'(lambda (type-number)
176 (type-from-number type-number))
177 param-types 'type-number n-params)))
178
179
180(defun describe-signal (signal-id &optional type)
181 (let ((info (signal-query (ensure-signal-id-from-type signal-id type))))
182 (with-slots (id name type flags return-type n-params) info
183 (format t "The signal with id ~D is named '~A' and may be emitted on instances of type ~S~%~%" id name (type-from-number type t))
184 (format t "Signal handlers should return ~A and take ~A~%"
185 (cond
186 ((= return-type (find-type-number "void")) "no values")
187 ((not (type-from-number return-type)) "values of unknown type")
188 ((format nil "values of type ~S" (type-from-number return-type))))
189 (if (zerop n-params)
190 "no arguments"
191 (format nil "arguments with the following types: ~A"
192 (signal-param-types info)))))))
193
194
195;;;; Signal connecting and controlling
196
9944c385 197(defvar *overridden-signals* (make-hash-table :test 'equalp))
198
199(defbinding %signal-override-class-closure () nil
200 (signal-id unsigned-int)
201 (type-number type-number)
202 (callback-closure pointer))
203
204
205(defun signal-override-class-closure (name type function)
206 (let* ((signal-id (ensure-signal-id-from-type name type))
207 (type-number (find-type-number type t))
208 (callback-id (gethash (cons type-number signal-id) *overridden-signals*)))
209 (if callback-id
210 (update-user-data callback-id function)
211 (multiple-value-bind (callback-closure callback-id)
212 (make-callback-closure function)
213 (%signal-override-class-closure signal-id type-number callback-closure)
214 (setf
215 (gethash (cons type-number signal-id) *overridden-signals*)
216 callback-id)))))
217
218
219(defbinding %signal-chain-from-overridden () nil
220 (args pointer)
221 (return-value (or null gvalue)))
222
38049b1a 223
224(defun %call-next-handler (n-params types args return-type)
9944c385 225 (let ((params (allocate-memory (* n-params +gvalue-size+))))
226 (loop
38049b1a 227 for arg in args
9944c385 228 for type in types
229 for offset from 0 by +gvalue-size+
77a843ca 230 do (gvalue-init (pointer+ params offset) type arg))
9944c385 231
232 (unwind-protect
233 (if return-type
234 (with-gvalue (return-value return-type)
235 (%signal-chain-from-overridden params return-value))
236 (%signal-chain-from-overridden params nil))
237 (progn
238 (loop
239 repeat n-params
240 for offset from 0 by +gvalue-size+
77a843ca 241 do (gvalue-unset (pointer+ params offset)))
9944c385 242 (deallocate-memory params)))))
243
244
245(defmacro define-signal-handler (name ((object class) &rest args) &body body)
246 (let* ((info (signal-query (ensure-signal-id-from-type name class)))
247 (types (cons class (signal-param-types info)))
248 (n-params (1+ (slot-value info 'n-params)))
249 (return-type (type-from-number (slot-value info 'return-type)))
250 (vars (loop
251 for arg in args
252 until (eq arg '&rest)
253 collect arg))
254 (rest (cadr (member '&rest args)))
38049b1a 255 (next (make-symbol "ARGS"))
256 (default (make-symbol "DEFAULT")))
9944c385 257
258 `(progn
259 (signal-override-class-closure ',name ',class
260 #'(lambda (,object ,@args)
38049b1a 261 (let ((,default (list* ,object ,@vars ,rest)))
262 (flet ((call-next-handler (&rest ,next)
9944c385 263 (%call-next-handler
e1fcda22 264 ,n-params ',types (or ,next ,default) ',return-type)))
265 ,@body))))
9944c385 266 ',name)))
267
268
e0d2987b 269(defbinding %signal-stop-emission () nil
c8c48a4c 270 (instance ginstance)
e0d2987b 271 (signal-id unsigned-int)
272 (detail quark))
273
274(defvar *signal-stop-emission* nil)
275(declaim (special *signal-stop-emission*))
c8c48a4c 276
e0d2987b 277(defun signal-stop-emission ()
278 (if *signal-stop-emission*
279 (funcall *signal-stop-emission*)
280 (error "Not inside a signal handler")))
281
282
283(defbinding signal-add-emission-hook (type signal function &key (detail 0))
284 unsigned-int
285 ((ensure-signal-id-from-type signal type) unsigned-int)
286 (detail quark)
56ccd5b7 287 (signal-emission-hook callback)
e0d2987b 288 ((register-callback-function function) unsigned-int)
56ccd5b7 289 (user-data-destroy-callback callback))
e0d2987b 290
291(defbinding signal-remove-emission-hook (type signal hook-id) nil
292 ((ensure-signal-id-from-type signal type) unsigned-int)
293 (hook-id unsigned-int))
c8c48a4c 294
c8c48a4c 295
0383dd48 296(defbinding (signal-has-handler-pending-p "g_signal_has_handler_pending")
c8c48a4c 297 (instance signal-id &key detail blocked) boolean
298 (instance ginstance)
e49e135a 299 ((ensure-signal-id signal-id instance) unsigned-int)
c8c48a4c 300 ((or detail 0) quark)
73572c12 301 (blocked boolean))
c8c48a4c 302
e0d2987b 303(defbinding %signal-connect-closure-by-id () unsigned-int
c8c48a4c 304 (instance ginstance)
e0d2987b 305 (signal-id unsigned-int)
306 (detail quark)
307 (closure pointer)
c8c48a4c 308 (after boolean))
309
0383dd48 310(defbinding signal-handler-block () nil
c8c48a4c 311 (instance ginstance)
e0d2987b 312 (handler-id unsigned-int))
c8c48a4c 313
0383dd48 314(defbinding signal-handler-unblock () nil
c8c48a4c 315 (instance ginstance)
e0d2987b 316 (handler-id unsigned-int))
c8c48a4c 317
0383dd48 318(defbinding signal-handler-disconnect () nil
c8c48a4c 319 (instance ginstance)
e0d2987b 320 (handler-id unsigned-int))
321
322(defbinding signal-handler-is-connected-p () boolean
323 (instance ginstance)
324 (handler-id unsigned-int))
c8c48a4c 325
d75a77ff 326(defbinding (callback-closure-new "clg_callback_closure_new") () gclosure
e0d2987b 327 (callback-id unsigned-int)
56ccd5b7 328 (callback callback)
329 (destroy-notify callback))
c8c48a4c 330
e0d2987b 331(defun make-callback-closure (function)
332 (let ((callback-id (register-callback-function function)))
333 (values
56ccd5b7 334 (callback-closure-new callback-id closure-marshal user-data-destroy-callback)
e0d2987b 335 callback-id)))
336
cd9b9e8b 337(defgeneric compute-signal-function (gobject signal function object))
65670fe5 338
cd9b9e8b 339(defmethod compute-signal-function ((gobject gobject) signal function object)
340 (declare (ignore signal))
e0d2987b 341 (cond
cd9b9e8b 342 ((or (eq object t) (eq object gobject)) function)
343 ((not object)
e0d2987b 344 #'(lambda (&rest args) (apply function (rest args))))
345 (t
cd9b9e8b 346 #'(lambda (&rest args) (apply function object (rest args))))))
347
348
349(defgeneric compute-signal-id (gobject signal))
350
351(defmethod compute-signal-id ((gobject gobject) signal)
352 (ensure-signal-id signal gobject))
353
354
355(defgeneric signal-connect (gobject signal function &key detail after object remove))
356
357(defmethod signal-connect :around ((gobject gobject) signal function &rest args)
358 (declare (ignore gobject signal args))
359 (when function
360 (call-next-method)))
e0d2987b 361
65670fe5 362
e0d2987b 363(defmethod signal-connect ((gobject gobject) signal function
cd9b9e8b 364 &key detail after object remove)
e0d2987b 365"Connects a callback function to a signal for a particular object. If
366:OBJECT is T, the object connected to is passed as the first argument
367to the callback function, or if :OBJECT is any other non NIL value, it
368is passed as the first argument instead. If :AFTER is non NIL, the
369handler will be called after the default handler for the signal. If
370:REMOVE is non NIL, the handler will be removed after beeing invoked
371once."
cd9b9e8b 372(let* ((signal-id (compute-signal-id gobject signal))
373 (detail-quark (if detail (quark-intern detail) 0))
374 (signal-stop-emission
375 #'(lambda ()
376 (%signal-stop-emission gobject signal-id detail-quark)))
377 (callback (compute-signal-function gobject signal function object))
378 (wrapper #'(lambda (&rest args)
379 (let ((*signal-stop-emission* signal-stop-emission))
380 (apply callback args)))))
e0d2987b 381 (multiple-value-bind (closure-id callback-id)
382 (make-callback-closure wrapper)
383 (let ((handler-id (%signal-connect-closure-by-id
cd9b9e8b 384 gobject signal-id detail-quark closure-id after)))
e0d2987b 385 (when remove
386 (update-user-data callback-id
387 #'(lambda (&rest args)
388 (unwind-protect
389 (let ((*signal-stop-emission* signal-stop-emission))
390 (apply callback args))
391 (signal-handler-disconnect gobject handler-id)))))
cd9b9e8b 392 handler-id))))
e0d2987b 393
394
395;;;; Signal emission
396
397(defbinding %signal-emitv () nil
398 (gvalues pointer)
399 (signal-id unsigned-int)
400 (detail quark)
401 (return-value gvalue))
402
403(defvar *signal-emit-functions* (make-hash-table))
404
405(defun create-signal-emit-function (signal-id)
406 (let ((info (signal-query signal-id)))
407 (let* ((type (type-from-number (slot-value info 'type)))
408 (param-types (cons type (signal-param-types info)))
409 (return-type (type-from-number (slot-value info 'return-type)))
410 (n-params (1+ (slot-value info 'n-params)))
411 (params (allocate-memory (* n-params +gvalue-size+))))
412 #'(lambda (detail object &rest args)
413 (unless (= (length args) (1- n-params))
414 (error "Invalid number of arguments: ~A" (+ 2 (length args))))
415 (unwind-protect
416 (loop
417 for arg in (cons object args)
418 for type in param-types
77a843ca 419 as tmp = params then (pointer+ tmp +gvalue-size+)
e0d2987b 420 do (gvalue-init tmp type arg)
421 finally
422 (if return-type
423 (return
424 (with-gvalue (return-value)
425 (%signal-emitv params signal-id detail return-value)))
426 (%signal-emitv params signal-id detail (make-pointer 0))))
427 (loop
428 repeat n-params
77a843ca 429 as tmp = params then (pointer+ tmp +gvalue-size+)
e0d2987b 430 while (gvalue-p tmp)
431 do (gvalue-unset tmp)))))))
432
433(defun signal-emit-with-detail (object signal detail &rest args)
434 (let* ((signal-id (ensure-signal-id signal object))
435 (function (or
436 (gethash signal-id *signal-emit-functions*)
437 (setf
438 (gethash signal-id *signal-emit-functions*)
439 (create-signal-emit-function signal-id)))))
440 (apply function detail object args)))
441
442(defun signal-emit (object signal &rest args)
443 (apply #'signal-emit-with-detail object signal 0 args))
444
c0f178d0 445
fd1e4a39 446;;;; Convenient macros
447
56ccd5b7 448(defmacro define-callback-marshal (name return-type args &key (callback-id :last))
449 (let* ((ignore ())
450 (params ())
451 (names (loop
452 for arg in args
453 collect (if (or
454 (eq arg :ignore)
455 (and (consp arg) (eq (first arg) :ignore)))
456 (let ((name (gensym "IGNORE")))
457 (push name ignore)
458 name)
459 (let ((name (if (atom arg)
460 (gensym (string arg))
461 (first arg))))
462 (push name params)
463 name))))
464 (types (loop
465 for arg in args
466 collect (cond
467 ((eq arg :ignore) 'pointer)
468 ((atom arg) arg)
469 (t (second arg))))))
470 `(define-callback ,name ,return-type
471 ,(ecase callback-id
472 (:first `((callback-id unsigned-int) ,@(mapcar #'list names types)))
473 (:last `(,@(mapcar #'list names types) (callback-id unsigned-int))))
474 (declare (ignore ,@ignore))
248f4dd7 475 (invoke-callback callback-id ',return-type ,@(nreverse params)))))
fd1e4a39 476
477(defmacro with-callback-function ((id function) &body body)
478 `(let ((,id (register-callback-function ,function)))
479 (unwind-protect
480 (progn ,@body)
481 (destroy-user-data ,id))))