Hi ltkers,
Maybe I'm just not using it quite right, but I'm having trouble cleanly dealing with the *wish* global which, if i understand correctly, is let'd in several places to ensure that functions use the correct wish object.
I'm using ltk along with some networking code and so I ltk's :server-event set to nil. The problem is I then have to bind *wish* properly whenever I update the gui from the network code. It's sort of clunky and frustrating.
My suggestion: have some or all tk widgets contain a wish slot that stores the connection to wish. That way objects just do the right thing when you use them. For commands that don't require a widget, such as wish-exit, you explicitely pass an ltk-connection object that was given to you when you opened the connection.
This seems closer to how clx works.
-Shawn
Hmm, I must have been doing something wrong, but in all the time I used Ltk, I never fiddled around with the *wish* variable :). So could you please post a bit of your code so we can have a look what your issue is?
Peter
Hi Peter,
Sorry for the delay. I'll try to illustrate what I'm doing and my problem without quoting a bunch of irrelevant source code:
(defun do-rad-things () (ltk:do-msg "activity from network!"))
(defun start-network () (let ((socket-stream (....)) (wish ltk:*wish*)) (sb-ext:add-fd-handler socket-stream (lambda () (let ((ltk:*wish* wish)) 'do-rad-things)))))
(defun start () (ltk:with-ltk (:serve-event t) (build-our-gui) (start-network)))
When tk sends output to ltk, the ltk handler is called and ltk:*wish* is bound to the poper wish structure. But when do-rad-things is called, ltk:*wish* is no longer bound to the proper wish structure so I have to do a little let dance to keep it in order.
I propose that instead of a with-ltk macro, an ltk:root class is created which holds the connection to the subprocess and represents the default root window wish produces when you start it. It would probably be most convenient if all other widgets also carried a pointer to the wish subprocess or the root class.
So then the above might look like this:
(defun do-rad-things (wish) (ltk:do-msg wish "activity from network!"))
(defun start-network (wish) (let ((socket-stream (....)) (sb-ext:add-fd-handler socket-stream (lambda () (do-rad-things wish)))))
(defun start () (let ((wish (make-instance 'ltk:root))) (build-our-gui wish) (start-network wish)))
What do you think?
-Shawn
Hi Shawn,
Thanks for the clear example code; it's a lot easier to support users who are clear about what they're trying to do :-)
On 6/24/07, Shawn Betts sabetts@vcn.bc.ca wrote:
Hi Peter,
Sorry for the delay. I'll try to illustrate what I'm doing and my problem without quoting a bunch of irrelevant source code:
(defun do-rad-things () (ltk:do-msg "activity from network!"))
(defun start-network () (let ((socket-stream (....)) (wish ltk:*wish*)) (sb-ext:add-fd-handler socket-stream (lambda () (let ((ltk:*wish* wish)) 'do-rad-things)))))
(defun start () (ltk:with-ltk (:serve-event t) (build-our-gui) (start-network)))
When tk sends output to ltk, the ltk handler is called and ltk:*wish* is bound to the poper wish structure. But when do-rad-things is called, ltk:*wish* is no longer bound to the proper wish structure so I have to do a little let dance to keep it in order.
Okay, I see what your problem is. The reason that *wish* is handled the way it is in the serve-event backend is that you might have more than one active wish process, so there is no "the" tk connection to make global. If you're perfectly happy with having just one gui running, or just one primary gui at least, you can skip with-ltk and use the lower-level functions: start-wish and mainloop. So your start function would become:
(defun start () (start-wish) (mainloop :serve-event t) (build-our-gui) (start-network))
This will start a serve-event wish connection on the global binding of *wish*. This is what I do for playing around with ltk at the sbcl repl.
However, a word of warning: serve-event ltk is not fully safe wrt reentrant serve-event. It's not likely to be a problem for you, but here's the situation where it could bite you: if you're trying to send the wish process more data than there is room for in its buffer, instead of blocking your sbcl calls back into serve-event, gets some network activity, wish processes the previous input and clears its buffer, your network event handler tries to send more data to wish and it ends out in the middle of the data for the previous command.
This is highly unlikely, and if you're just doing widget stuff (as opposed to, say, sending images across the pipe) I don't think it can come up. But if you see Ltk tripping over itself under high network load, this would be the reason.
We could, can, and probably will fix this. Up to now we haven't had any known serve-event users besides ourselves, so it's been a low priority (higher priority is merging the little bug fixes and enhancements developed at work). So holler if this bothers you.
Again, we have a work-around. Rather than push changes out to wish, you can have it pull them. The ltk:after function lets you schedule idle events. So you could have your network code push events onto a global queue and have the GUI check for updates every 1 sec.
Putting all that together, here's how I'd rewrite your example:
(defvar *gui-updates* ())
(defun process-updates () (let ((updates (nreverse *gui-updates*))) (setf *gui-updates* nil) (mapc #'funcall *gui-updates*) (after 1000 #'process-updates)))
(defun do-rad-things () (labels ((aux () (ltk:do-msg "activity from network!"))) (push #'aux *gui-updates*)))
(defun start-network () (let ((socket-stream (....))) (sb-ext:add-fd-handler socket-stream (lambda () (do-rad-things)))))
(defun start () (start-wish) (mainloop :serve-event t) (build-our-gui) (start-network) (process-updates))
I propose that instead of a with-ltk macro, an ltk:root class is created which holds the connection to the subprocess and represents the default root window wish produces when you start it. It would probably be most convenient if all other widgets also carried a pointer to the wish subprocess or the root class.
So then the above might look like this:
(defun do-rad-things (wish) (ltk:do-msg wish "activity from network!"))
(defun start-network (wish) (let ((socket-stream (....)) (sb-ext:add-fd-handler socket-stream (lambda () (do-rad-things wish)))))
(defun start () (let ((wish (make-instance 'ltk:root))) (build-our-gui wish) (start-network wish)))
What do you think?
This actually looks more cumbersome to me than what we have right now. But I like the idea of having widgets know about their wish connection; then *wish* is only relevant to the few truly global things you can do: make a new toplevel, do-msg, etc.