X-Git-Url: https://git.distorted.org.uk/~mdw/clg/blobdiff_plain/8ab0db90ff4867ccaa730c9c089d9cc2a10e92bf..eba2d996724fcedbb6fc4b0e642d3a88b49bfb72:/gtk/gtk.lisp diff --git a/gtk/gtk.lisp b/gtk/gtk.lisp index 4eb3604..c70e830 100644 --- a/gtk/gtk.lisp +++ b/gtk/gtk.lisp @@ -20,7 +20,7 @@ ;; TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE ;; SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -;; $Id: gtk.lisp,v 1.62 2006-04-26 12:33:52 espen Exp $ +;; $Id: gtk.lisp,v 1.78 2007-07-10 08:45:06 espen Exp $ (in-package "GTK") @@ -45,80 +45,174 @@ (format nil "Gtk+ v~A.~A.~A" major minor micro)))) (defun clg-version () - "clg 0.93pre") + "clg 0.93") -;;;; Initalization +;;;; Initalization and display handling + +(defparameter *event-poll-interval* 10000) ; in microseconds + (defbinding (gtk-init "gtk_parse_args") () boolean "Initializes the library without opening the display." (nil null) (nil null)) -(defparameter *event-poll-interval* 10000) +(defun clg-init (&optional display multi-threading-p) + "Initializes the system and starts event handling." + (unless (gdk:display-get-default) + #?(pkg-exists-p "gtk+-2.0" :atleast-version "2.8.0") + (progn + #+sbcl(sb-int:set-floating-point-modes :traps nil) + #+cmu(ext:set-floating-point-modes :traps nil)) -(defun clg-init (&optional display) - "Initializes the system and starts the event handling" - #+sbcl(when (and - (find-package "SWANK") - (eq (symbol-value (find-symbol "*COMMUNICATION-STYLE*" "SWANK")) :spawn)) - (error "When running clg in Slime the communication style :spawn can not be used. See the README file and for more information.")) + (gdk:gdk-init) + (unless (gtk-init) + (error "Initialization of GTK+ failed.")) + + (if (not multi-threading-p) + (%init-async-event-handling display) + #+sb-thread(%init-multi-threaded-event-handling display) + #-sb-thread(error "Multi threading not supported on this platform"))) + (gdk:ensure-display display t)) - (unless (gdk:display-get-default) - (gdk:gdk-init) - (unless (gtk-init) - (error "Initialization of GTK+ failed.")) - (prog1 - (gdk:display-open display) - #+(or cmu sbcl) - (progn - (add-fd-handler (gdk:display-connection-number) :input #'main-iterate-all) - (setq *periodic-polling-function* #'main-iterate-all) - (setq *max-event-to-sec* 0) - (setq *max-event-to-usec* *event-poll-interval*)) - #+(and clisp readline) - ;; Readline will call the event hook at most ten times per second - (setf readline:event-hook #'main-iterate-all) - #+clisp - ;; When running in Slime we need to hook into the Swank server - ;; to handle events asynchronously - (if (find-symbol "WAIT-UNTIL-READABLE" "SWANK") - (setf (symbol-function 'swank::wait-until-readable) - #'(lambda (stream) - (loop - (case (socket:socket-status (cons stream :input) 0 *event-poll-interval*) - (:input (return t)) - (:eof (read-char stream)) - (otherwise (main-iterate-all)))))) - #-readline(warn "Not running in Slime and Readline support is missing, so the Gtk main loop has to be invoked explicit."))))) - -#+sbcl -(defun clg-init-with-threading (&optional display) - "Initializes the system and starts the event handling" - (unless (gdk:display-get-default) - (gdk:gdk-init) - (gdk:threads-set-lock-functions) - (unless (gtk-init) - (error "Initialization of GTK+ failed.")) - (sb-thread:make-thread - #'(lambda () - (gdk:display-open display) - (gdk:with-global-lock (main))) - :name "gtk event loop"))) - -#+sbcl (defun clg-init-with-threading (&optional display) - "Initializes the system and starts the event handling" - (unless (gdk:display-get-default) - (gdk:gdk-init) - (gdk:threads-set-lock-functions) - (unless (gtk-init) - (error "Initialization of GTK+ failed.")) - (sb-thread:make-thread - #'(lambda () - (gdk:display-open display) - (gdk:with-global-lock (main))) - :name "gtk event loop"))) + (clg-init display t)) + + +#?(sbcl>= 1 0 6) +;; A very minimal implementation of CLISP's socket-status +(defun socket-status (socket seconds microseconds) + (sb-alien:with-alien ((read-fds (sb-alien:struct sb-unix:fd-set))) + (let ((fd (sb-sys:fd-stream-fd (car socket)))) + (sb-unix:fd-zero read-fds) + (sb-unix:fd-set fd read-fds) + + (let ((num-fds-changed + (sb-unix:unix-fast-select + (1+ fd) (sb-alien:addr read-fds) nil nil + seconds microseconds))) + (unless (or (not num-fds-changed) (zerop num-fds-changed)) + (if (peek-char nil (car socket) nil) + :input + :eof)))))) + +(defun %init-async-event-handling (display) + (let ((style #?(or (featurep :cmu) (sbcl< 1 0 6)) :fd-handler + #?-(or (featurep :cmu) (sbcl< 1 0 6)) nil)) + (when (and + (find-package "SWANK") + (not (eq (symbol-value (find-symbol "*COMMUNICATION-STYLE*" "SWANK")) style))) + (error "When running clg in Slime, the communication style ~A must be used in combination with asynchronous event handling on this platform. See the README file and for more information." style))) + + #?(or (featurep :cmu) (sbcl< 1 0 6)) + (progn + (signal-connect (gdk:display-manager) 'display-opened + #'(lambda (display) + (let ((fd (gdk:display-connection-number display))) + (unless (< fd 0) + (let ((handler (add-fd-handler + (gdk:display-connection-number display) + :input #'main-iterate-all))) + (signal-connect display 'closed + #'(lambda (is-error-p) + (declare (ignore is-error-p)) + (remove-fd-handler handler)))))))) + (setq *periodic-polling-function* #'main-iterate-all) + (setq *max-event-to-sec* 0) + (setq *max-event-to-usec* *event-poll-interval*)) + + #+(and clisp readline) + ;; Readline will call the event hook at most ten times per second + (setf readline:event-hook #'main-iterate-all) + + #?-(or (featurep :cmu) (sbcl< 1 0 6)) + ;; When running in Slime we need to hook into the Swank server + ;; to handle events asynchronously. + (if (find-package "SWANK") + (let ((read-from-emacs (symbol-function (find-symbol "READ-FROM-EMACS" "SWANK"))) + (stream (funcall (find-symbol "CONNECTION.SOCKET-IO" "SWANK") (symbol-value (find-symbol "*EMACS-CONNECTION*" "SWANK"))))) + (setf (symbol-function (find-symbol "READ-FROM-EMACS" "SWANK")) + #'(lambda () + (loop + (case (socket-status (cons stream :input) 0 *event-poll-interval*) + ((:input :eof) (return (funcall read-from-emacs))) + (otherwise (main-iterate-all))))))) + #-(and clisp readline) + (warn "Asynchronous event handling not supported on this platform. An explicit main loop has to be started.")) + + (gdk:display-open display)) + +#+sb-thread +(progn + (defvar *main-thread* nil) + + ;; Hopefully, when threading support is added to the Win32 port of + ;; SBCL in the future, this will work just out of the box. + #+win32 + (let ((done (sb-thread:make-waitqueue)) + (functions ()) + (results ())) + + ;; In Win32 all GDK calls have to be made from the main loop + ;; thread, so we add a timeout function which will poll for code and + ;; execute it. + + (defun funcall-in-main (function) + (if (or + (not *main-thread*) + (eq sb-thread:*current-thread* *main-thread*)) + (funcall function) + (gdk:with-global-lock + (push function functions) + (sb-thread:condition-wait done gdk:*global-lock*) + (pop results)))) + + ;; Will lock REPL on error, need to be fixed! + (defun %funcall-in-main-poll () + (when functions + (loop + for n from 0 + while functions + do (push (funcall (pop functions)) results) + finally (sb-thread:condition-notify done n))) + t)) + + (defmacro within-main-loop (&body body) + #-win32 `(gdk:with-global-lock ,@body) + #+win32 `(funcall-in-main #'(lambda () ,@body))) + + (defun %init-multi-threaded-event-handling (display) + (when (and + (find-package "SWANK") + (not (eq (symbol-value (find-symbol "*COMMUNICATION-STYLE*" "SWANK")) :spawn))) + (error "When running clg in Slime, the communication style :spawn must be used in combination with multi threaded event handling. See the README file and for more information.")) + (let ((main-running (sb-thread:make-waitqueue))) + (gdk:with-global-lock + (setf *main-thread* + (sb-thread:make-thread + #'(lambda () + (gdk:threads-init) + (gdk:with-global-lock + (gdk:display-open display) + #+win32(gdk:timeout-add-with-lock (/ *event-poll-interval* 1000) + #'%funcall-in-main-poll) + (sb-thread:condition-notify main-running) + (main))) + :name "gtk event loop")) + (sb-thread:condition-wait main-running gdk:*global-lock*))) + + ;; We need to hook into the Swank server to protect calls to GDK properly. + ;; This will *only* protect code entered directly in the REPL. + (when (find-package "SWANK") + (push #'(lambda (form) + (within-main-loop (eval form))) + swank::*slime-repl-eval-hooks*)))) + +#-sb-thread +(defmacro within-main-loop (&body body) + `(progn ,@body)) + ;;; Generic functions @@ -235,7 +329,7 @@ (multiple-value-bind (key modifiers) (parse-accelerator accelerator) (%accel-groups-activate object key modifiers))) -(defbinding accel-groups-from-object () (gslist accel-groups) +(defbinding accel-groups-from-object () (gslist accel-group) (object gobject)) (defbinding accelerator-valid-p (key &optional modifiers) boolean @@ -427,6 +521,56 @@ (right unsigned-int)) +;;; Assistant + +#?(pkg-exists-p "gtk+-2.0" :atleast-version "2.10.0") +(progn + (defbinding assistant-get-nth-page () widget + (assistant assistant) + (page-num int)) + + (defbinding %assistant-insert-page () int + (assistant assistant) + (page widget) + (pos int)) + + (defun assistant-insert-page (assistant page position &rest child-args) + (let ((pos (case position + (:first 0) + (:last -1) + (t position)))) + (prog1 + (%assistant-insert-page assistant page pos) + (init-child-slots assistant page child-args)))) + + (defun assistant-append-page (assistant page &rest child-args) + (apply #'assistant-insert-page assistant page :last child-args)) + + (defun assistant-prepend-page (assistant page &rest child-args) + (apply #'assistant-insert-page assistant page :first child-args)) + + (define-callback-marshal %assistant-page-func-callback int + ((current-page int))) + + (defbinding assistant-set-forward-func (assistant function) nil + (assistant assistant) + (%assistant-page-func-callback callback) + ((register-callback-function function) pointer-data) + (user-data-destroy-callback callback)) + + (defbinding assistant-add-action-widget () nil + (assistant assistant) + (child widget)) + + (defbinding assistant-remove-action-widget () nil + (assistant assistant) + (child widget)) + + (defbinding assistant-update-buttons-state () nil + (assistant assistant))) + + + ;;; Aspect frame @@ -438,11 +582,11 @@ (container-add bin child) child) -(defmethod compute-signal-function ((bin bin) signal function object) +(defmethod compute-signal-function ((bin bin) signal function object args) (declare (ignore signal)) (if (eq object :child) - #'(lambda (&rest args) - (apply function (bin-child bin) (rest args))) + #'(lambda (&rest emission-args) + (apply function (bin-child bin) (nconc (rest emission-args) args))) (call-next-method))) @@ -672,8 +816,8 @@ (ensure-signal-id 'response dialog) (call-next-method))) -(defmethod compute-signal-function ((dialog dialog) signal function object) - (declare (ignore function object)) +(defmethod compute-signal-function ((dialog dialog) signal function object args) + (declare (ignore function object args)) (let ((callback (call-next-method)) (id (dialog-response-id dialog signal))) (if id @@ -783,6 +927,12 @@ (setf (container-children (dialog-vbox dialog)) children)) +;;; Drawing Area + +(defun drawing-area-scroll (drawing-area dx dy) + (gdk:window-scroll (widget-window drawing-area) dx dy)) + + ;;; Entry (defbinding entry-get-layout-offsets () nil @@ -1070,6 +1220,8 @@ (defmethod activate-radio-widget ((button radio-button)) (signal-emit button 'clicked)) +(defgeneric add-activate-callback (action function &key object after)) + (defmethod add-activate-callback ((button radio-button) function &key object after) (%add-activate-callback button 'clicked function object after)) @@ -1253,10 +1405,13 @@ ;;; Window (defmethod initialize-instance ((window window) &rest initargs - &key accel-group accel-groups) + &key display accel-group accel-groups) (declare (ignore accel-group accel-groups)) (prog1 - (call-next-method) + (if display + (apply #'call-next-method + window :screen (gdk:display-get-default-screen (gdk:ensure-display display)) initargs) + (call-next-method)) (initial-add window #'window-add-accel-group initargs :accel-group :accel-groups))) @@ -1297,13 +1452,14 @@ (defbinding %window-set-geometry-hints () nil (window window) + (widget (or widget null)) (geometry gdk:geometry) (geometry-mask gdk:window-hints)) -(defun window-set-geometry-hints (window &key min-width min-height +(defun window-set-geometry-hints (window &key widget min-width min-height max-width max-height base-width base-height - width-inc height-inc min-aspect max-aspect - (gravity nil gravity-p) min-size max-size) + width-inc height-inc gravity + aspect (min-aspect aspect) (max-aspect aspect)) (let ((geometry (make-instance 'gdk:geometry :min-width (or min-width -1) :min-height (or min-height -1) @@ -1314,12 +1470,11 @@ :width-inc (or width-inc 0) :height-inc (or height-inc 0) :min-aspect (or min-aspect 0) - :max-aspect (or max-aspect 0) - :gravity gravity)) + :max-aspect (or max-aspect 0))) (mask ())) - (when (or min-size min-width min-height) + (when (or min-width min-height) (push :min-size mask)) - (when (or max-size max-width max-height) + (when (or max-width max-height) (push :max-size mask)) (when (or base-width base-height) (push :base-size mask)) @@ -1327,9 +1482,10 @@ (push :resize-inc mask)) (when (or min-aspect max-aspect) (push :aspect mask)) - (when gravity-p - (push :win-gravity mask)) - (%window-set-geometry-hints window geometry mask))) + (when gravity + (push :win-gravity mask) + (setf (gdk:geometry-gravity geometry) gravity)) + (%window-set-geometry-hints window widget geometry mask))) (defbinding window-list-toplevels () (glist (copy-of window)) "Returns a list of all existing toplevel windows.") @@ -1484,10 +1640,14 @@ (defbinding %window-set-default-icon () nil (icons (glist gdk:pixbuf))) +(defgeneric (setf window-default-icon) (icon)) + (defmethod (setf window-default-icon) ((icon gdk:pixbuf)) (%window-set-default-icon icon) icon) +(defgeneric (setf window-group) (group window)) + (defmethod (setf window-group) ((group window-group) (window window)) (window-group-add-window group window) group) @@ -1973,12 +2133,16 @@ (table table) (spacing unsigned-int)) -(defun (setf table-col-spacing) (spacing table &optional col) - (if col - (%table-set-col-spacing table col spacing) +(defun (setf table-column-spacing) (spacing table &optional column) + (if column + (%table-set-col-spacing table column spacing) (%table-set-col-spacings table spacing)) spacing) +(defun (setf table-col-spacing) (spacing table &optional col) + (warn "TABLE-COL-SPACING is deprecatet, use TABLE-COLUMN-SPACING instead") + (setf (table-column-spacing table col) spacing)) + (defbinding %table-get-col-spacing () unsigned-int (table table) (col unsigned-int)) @@ -1986,11 +2150,15 @@ (defbinding %table-get-default-col-spacing () unsigned-int (table table)) -(defun table-col-spacing (table &optional col) - (if col - (%table-get-col-spacing table col) +(defun table-column-spacing (table &optional column) + (if column + (%table-get-col-spacing table column) (%table-get-default-col-spacing table))) +(defun table-col-spacing (table &optional col) + (warn "TABLE-COL-SPACING is deprecatet, use TABLE-COLUMN-SPACING instead") + (table-column-spacing table col)) + ;;; Toolbar @@ -2408,3 +2576,17 @@ (defmethod allocate-foreign ((plug plug) &key id) (%plug-new (or id 0))) + + +;;;; New stuff in Gtk+ 2.10 + +;;; Link button + +#?(pkg-exists-p "gtk+-2.0" :atleast-version "2.10.0") +(progn + (define-callback-marshal %link-button-uri-callback nil (link-button (link string))) + + (defbinding link-button-set-uri-hook (function) pointer + (%link-button-uri-callback callback) + ((register-callback-function function) unsigned-int) + (user-data-destroy-callback callback)))