chiark / gitweb /
Bug fix
[clg] / glib / gcallback.lisp
CommitLineData
55212af1 1;; Common Lisp bindings for GTK+ v2.x
2;; Copyright 2000 Espen S. Johnsen <espen@users.sf.net>
c9819f3e 3;;
55212af1 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:
c9819f3e 11;;
55212af1 12;; The above copyright notice and this permission notice shall be
13;; included in all copies or substantial portions of the Software.
c9819f3e 14;;
55212af1 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
2e8019d5 23;; $Id: gcallback.lisp,v 1.35 2006/06/07 13:16:11 espen Exp $
c9819f3e 24
25(in-package "GLIB")
26
27(use-prefix "g")
28
29
3b8e5eb0 30;;;; Callback invokation
c9819f3e 31
f7573853 32(deftype gclosure () 'pointer)
33(register-type 'gclosure '|g_closure_get_type|)
34
60cfb912 35(defun register-callback-function (function)
36 (check-type function (or null symbol function))
37 (register-user-data function))
c9819f3e 38
3b8e5eb0 39;; Callback marshal for regular signal handlers
a92553bd 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))
08d14e5e 44 (declare (ignore gclosure invocation-hint))
3b8e5eb0 45 (callback-trampoline callback-id n-params param-values return-value))
c9819f3e 46
3b8e5eb0 47;; Callback function for emission hooks
a92553bd 48(define-callback signal-emission-hook nil
49 ((invocation-hint pointer) (n-params unsigned-int) (param-values pointer)
50 (callback-id unsigned-int))
f84e7a8e 51 (declare (ignore invocation-hint))
3b8e5eb0 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)))
c9819f3e 56 (let* ((return-type (unless (null-pointer-p return-value)
60cfb912 57 (gvalue-type return-value)))
831668e8 58 (args (loop
59 for n from 0 below n-params
ad112f20 60 for offset from 0 by +gvalue-size+
10ede675 61 collect (gvalue-peek (pointer+ param-values offset)))))
ad112f20 62 (unwind-protect
63 (let ((result (apply #'invoke-callback callback-id return-type args)))
64 (when return-type
65 (gvalue-set return-value result)))
10ede675 66 ;; TODO: this should be made more general, by adding a type
67 ;; method to return invalidate functions.
ad112f20 68 (loop
69 for arg in args
10ede675 70 when (typep arg 'struct)
ad112f20 71 do (invalidate-instance arg)))))
72
831668e8 73
7bde5a67 74(defun invoke-callback (callback-id return-type &rest args)
831668e8 75 (restart-case
76 (apply (find-user-data callback-id) args)
77 (continue nil :report "Return from callback function"
7bde5a67 78 (when return-type
79 (format *query-io* "Enter return value of type ~S: " return-type)
831668e8 80 (force-output *query-io*)
81 (eval (read *query-io*))))
82 (re-invoke nil :report "Re-invoke callback function"
7bde5a67 83 (apply #'invoke-callback callback-id return-type args))))
c9819f3e 84
c9819f3e 85
60cfb912 86;;;; Timeouts and idle functions
87
0f2fb864 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
a92553bd 97(define-callback source-callback-marshal nil ((callback-id unsigned-int))
3b8e5eb0 98 (callback-trampoline callback-id 0 nil))
60cfb912 99
100(defbinding (timeout-add "g_timeout_add_full")
0f2fb864 101 (interval function &optional (priority +priority-default+)) unsigned-int
60cfb912 102 (priority int)
103 (interval unsigned-int)
a92553bd 104 (source-callback-marshal callback)
60cfb912 105 ((register-callback-function function) unsigned-long)
a92553bd 106 (user-data-destroy-callback callback))
60cfb912 107
0f2fb864 108(defun timeout-remove (timeout)
109 (source-remove timeout))
110
60cfb912 111(defbinding (idle-add "g_idle_add_full")
0f2fb864 112 (function &optional (priority +priority-default-idle+)) unsigned-int
60cfb912 113 (priority int)
a92553bd 114 (source-callback-marshal callback)
60cfb912 115 ((register-callback-function function) unsigned-long)
a92553bd 116 (user-data-destroy-callback callback))
60cfb912 117
0f2fb864 118(defun idle-remove (idle)
119 (source-remove idle))
60cfb912 120
c9819f3e 121
3b8e5eb0 122;;;; Signal information querying
c9819f3e 123
3b8e5eb0 124(defbinding signal-lookup (name type) unsigned-int
c9819f3e 125 ((signal-name-to-string name) string)
3b8e5eb0 126 ((find-type-number type t) type-number))
c9819f3e 127
3b8e5eb0 128(defbinding signal-name () (copy-of string)
c9819f3e 129 (signal-id unsigned-int))
130
3b8e5eb0 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)
c9819f3e 139 (etypecase signal-id
3b8e5eb0 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)))
c9819f3e 151
3b8e5eb0 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)
10ede675 170 (signal-query signal-query :in/return))
3b8e5eb0 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
2d3de529 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
e9151788 223
224(defun %call-next-handler (n-params types args return-type)
2d3de529 225 (let ((params (allocate-memory (* n-params +gvalue-size+))))
226 (loop
e9151788 227 for arg in args
2d3de529 228 for type in types
229 for offset from 0 by +gvalue-size+
10ede675 230 do (gvalue-init (pointer+ params offset) type arg))
2d3de529 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+
10ede675 241 do (gvalue-unset (pointer+ params offset)))
2d3de529 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)))
e9151788 255 (next (make-symbol "ARGS"))
256 (default (make-symbol "DEFAULT")))
2d3de529 257
258 `(progn
259 (signal-override-class-closure ',name ',class
260 #'(lambda (,object ,@args)
e9151788 261 (let ((,default (list* ,object ,@vars ,rest)))
262 (flet ((call-next-handler (&rest ,next)
2d3de529 263 (%call-next-handler
2e8019d5 264 ,n-params ',types (or ,next ,default) ',return-type)))
265 ,@body))))
2d3de529 266 ',name)))
267
268
3b8e5eb0 269(defbinding %signal-stop-emission () nil
c9819f3e 270 (instance ginstance)
3b8e5eb0 271 (signal-id unsigned-int)
272 (detail quark))
273
274(defvar *signal-stop-emission* nil)
275(declaim (special *signal-stop-emission*))
c9819f3e 276
3b8e5eb0 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)
a92553bd 287 (signal-emission-hook callback)
3b8e5eb0 288 ((register-callback-function function) unsigned-int)
a92553bd 289 (user-data-destroy-callback callback))
3b8e5eb0 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))
c9819f3e 294
c9819f3e 295
3f4249c7 296(defbinding (signal-has-handler-pending-p "g_signal_has_handler_pending")
c9819f3e 297 (instance signal-id &key detail blocked) boolean
298 (instance ginstance)
7eec806d 299 ((ensure-signal-id signal-id instance) unsigned-int)
c9819f3e 300 ((or detail 0) quark)
3d36c5d6 301 (blocked boolean))
c9819f3e 302
3b8e5eb0 303(defbinding %signal-connect-closure-by-id () unsigned-int
c9819f3e 304 (instance ginstance)
3b8e5eb0 305 (signal-id unsigned-int)
306 (detail quark)
307 (closure pointer)
c9819f3e 308 (after boolean))
309
3f4249c7 310(defbinding signal-handler-block () nil
c9819f3e 311 (instance ginstance)
3b8e5eb0 312 (handler-id unsigned-int))
c9819f3e 313
3f4249c7 314(defbinding signal-handler-unblock () nil
c9819f3e 315 (instance ginstance)
3b8e5eb0 316 (handler-id unsigned-int))
c9819f3e 317
3f4249c7 318(defbinding signal-handler-disconnect () nil
c9819f3e 319 (instance ginstance)
3b8e5eb0 320 (handler-id unsigned-int))
321
322(defbinding signal-handler-is-connected-p () boolean
323 (instance ginstance)
324 (handler-id unsigned-int))
c9819f3e 325
bde4e068 326(defbinding (callback-closure-new "clg_callback_closure_new") () gclosure
3b8e5eb0 327 (callback-id unsigned-int)
a92553bd 328 (callback callback)
329 (destroy-notify callback))
c9819f3e 330
3b8e5eb0 331(defun make-callback-closure (function)
332 (let ((callback-id (register-callback-function function)))
333 (values
a92553bd 334 (callback-closure-new callback-id closure-marshal user-data-destroy-callback)
3b8e5eb0 335 callback-id)))
336
54ea42fe 337(defgeneric compute-signal-function (gobject signal function object))
a6e13fb0 338
54ea42fe 339(defmethod compute-signal-function ((gobject gobject) signal function object)
340 (declare (ignore signal))
3b8e5eb0 341 (cond
54ea42fe 342 ((or (eq object t) (eq object gobject)) function)
343 ((not object)
3b8e5eb0 344 #'(lambda (&rest args) (apply function (rest args))))
345 (t
54ea42fe 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)))
3b8e5eb0 361
a6e13fb0 362
3b8e5eb0 363(defmethod signal-connect ((gobject gobject) signal function
54ea42fe 364 &key detail after object remove)
3b8e5eb0 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."
54ea42fe 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)))))
3b8e5eb0 381 (multiple-value-bind (closure-id callback-id)
382 (make-callback-closure wrapper)
383 (let ((handler-id (%signal-connect-closure-by-id
54ea42fe 384 gobject signal-id detail-quark closure-id after)))
3b8e5eb0 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)))))
54ea42fe 392 handler-id))))
3b8e5eb0 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
10ede675 419 as tmp = params then (pointer+ tmp +gvalue-size+)
3b8e5eb0 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
10ede675 429 as tmp = params then (pointer+ tmp +gvalue-size+)
3b8e5eb0 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
dd181a20 445
11e1e57c 446;;;; Convenient macros
447
a92553bd 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))
ad3e0b2b 475 (invoke-callback callback-id ',return-type ,@(nreverse params)))))
11e1e57c 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))))