Event handling in GTK2hs: managing events and global state

Previous Topic Next Topic
 
classic Classic list List threaded Threaded
3 messages Options
Reply | Threaded
Open this post in threaded view
|

Event handling in GTK2hs: managing events and global state

Aleksandar Dimitrov
Hello community,

I'm just diving into my first bigger Haskell problems, and got stuck at a
certain point with gtk2hs. I couldn't find documentation or even small examples
for it, no matter where I looked, so I turn to this list.

My basic problem is that I want a certain action to happen based on the effects
of a side effect; in my case, loading a file into a widget. Together with the
file, I load a data structure, and define an event that will translate clicks on
the widget into some magic on the data structure.

So when I load a file, I do the following:

> loadFile :: GladeXML -> FilePath -> IO ()
> loadFile xml fp = do widget <- xmlGetWidget -- ?
>     -- do stuff with the widget, yield some 'data'
>     connectId <- widget `on` buttonPressEvent $ handler data
>                    ?

(this is a minimal trivial example, to show the crucial point.) So, the handler
step depends on the particular contents of the file. This means, that for every
file I load, I first have to disable the old action, and only then install a new
one, since I generate a new handler every time a file is loaded. Every addition
of a handler gives me a connectId, which is its true name. And, as the gtk2hs
docs inform me, I have to call it by its true name to dispel it.

The gtk2hs docs suggest I 'keep around' the connectId, but herein lies my
problem. The loadFile function can be called from several spots in the
application (and I'd like to keep it that way.) Even if I wrapped loadFile in a
State monad, those entry points to load file (command line, menu, button,
possibly more) have no way of knowing the state of connectId, lest I make it
global (*shudders*) using unsafePerformIO and a Maybe IORef.

Forgive me if I'm missing something obvious, but I have thus far only used
monads for serialized code flows, and not for asynchronous, event-driven
computation.

So the other approach would be (and thanks to mmorrow at freenode for the
idea) to wrap my widget component (if I understood correctly) in a new type that
also keeps track of its connectedId (namely, the event it's been connected to)
via an IORef.  This way the state gets 'hidden' in the IO monad, via IORef. I'd
end up with something like

data WW = WW { widget :: SomeWidget
             , connectId :: IORef (Maybe (ConnectId SomeWidget))
             }

and of course no monads to indicate there's something state-changing going on
(Except the IO monad, which is the underlying monad for the whole brouhaha
anyway.) The above call would look more like:

> main = do ?
>           widget <- xmlGetWidget ?
>           nothingRef <- newIORef Nothing
>           openButton `onClicked` $ loadFile xml fp (WW widget nothingRef)
>           ?
>
>
> loadFile :: GladeXML -> FilePath -> WW -> IO ()
> loadFile xml fp (WW widget cid) = -- do stuff, yield some 'data'
>                    ref <- readIORef cid
>                    case ref of
>                         (Just i) -> signalDisconnect i
>                         Nothing   -> return ()
>     connectId <- widget `on` buttonPressEvent $
> handler data widget
>                    writeIORef cid $ Just connectId
>                    ?


This doesn't strike me as a very elegant way of doing it. I'm sure there is some
nice Haskellery that would make it more beautiful, so I'd like to ask this list
what that might be?

Also, a small plea: the gtk2hs documentation is sorely lacking an example of the
usage of "signalDisconnect". I can provide a working minimal example of this solution,
if anyone is interested, so maybe that could serve as a documentation. Though I
still hope that this isn't the best possible way of doing it.

Thanks in advance,
Aleks
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 197 bytes
Desc: not available
Url : http://www.haskell.org/pipermail/beginners/attachments/20090325/fad74f60/attachment.bin
Reply | Threaded
Open this post in threaded view
|

Event handling in GTK2hs: managing events and global state

Brandon S Allbery KF8NH
On 2009 Mar 25, at 0:24, ?????????? ?. ????????  
wrote:

> The gtk2hs docs suggest I 'keep around' the connectId, but herein  
> lies my
> problem. The loadFile function can be called from several spots in the
> application (and I'd like to keep it that way.) Even if I wrapped  
> loadFile in a
> State monad, those entry points to load file (command line, menu,  
> button,
> possibly more) have no way of knowing the state of connectId, lest I  
> make it
> global (*shudders*) using unsafePerformIO and a Maybe IORef.

You can make it "global" without using unsafePerformIO:  Set up a  
ReaderT MyState IO and run the program inside it, after initializing  
your MyState with appropriate IORefs.  You need to lift gtk2hs  
functions, and callbacks have to be wrapped to push them back up into  
the ReaderT:

     curState <- ask
     lift $ widget `on` buttonPressEvent $ runReaderT curState .  
myCallback

The reason you want a ReaderT instead of a StateT is that any state  
not accessed via an IORef can't be propagated between the mainline  
code and callbacks, so you want something that is forced to be read-
only after initialization except via an IORef stored within it.

Aside:  I've suggested at times that gtk2hs use class MonadIO instead  
of type IO, which would make explicit lifting (and I think "dropping")  
unnecessary.  I never made a formal enhancement suggestion though; I  
should do so.

--
brandon s. allbery [solaris,freebsd,perl,pugs,haskell] [hidden email]
system administrator [openafs,heimdal,too many hats] [hidden email]
electrical and computer engineering, carnegie mellon university    KF8NH


-------------- next part --------------
A non-text attachment was scrubbed...
Name: PGP.sig
Type: application/pgp-signature
Size: 195 bytes
Desc: This is a digitally signed message part
Url : http://www.haskell.org/pipermail/beginners/attachments/20090325/b7421f10/PGP-0001.bin
Reply | Threaded
Open this post in threaded view
|

Event handling in GTK2hs: managing events and global state

Aleksandar Dimitrov
Also Sprach Brandon S. Allbery KF8NH:

> You can make it "global" without using unsafePerformIO:  Set up a  
> ReaderT MyState IO and run the program inside it, after initializing  
> your MyState with appropriate IORefs.  You need to lift gtk2hs  
> functions, and callbacks have to be wrapped to push them back up into  
> the ReaderT:
>
>     curState <- ask
>     lift $ widget `on` buttonPressEvent $ runReaderT curState .  
> myCallback
>
> The reason you want a ReaderT instead of a StateT is that any state not
> accessed via an IORef can't be propagated between the mainline code and
> callbacks, so you want something that is forced to be read-only after
> initialization except via an IORef stored within it.
>
> Aside:  I've suggested at times that gtk2hs use class MonadIO instead of
> type IO, which would make explicit lifting (and I think "dropping")  
> unnecessary.  I never made a formal enhancement suggestion though; I  
> should do so.

Thanks for your very helpful answer :-) I ended up passing a state object around
in functions and then porting it back to use the Monad (damn deadlines, always
in the way of sound programming.)

If the GTK2hs people made this a bit easier, it would surely help. I'll look
into the possibilities myself, too.

Thanks again,
Aleks
-------------- next part --------------
A non-text attachment was scrubbed...
Name: not available
Type: application/pgp-signature
Size: 197 bytes
Desc: not available
Url : http://www.haskell.org/pipermail/beginners/attachments/20090404/0352cd1b/attachment.bin