My tools involved here are GTK and Haskell. My questions are probably pretty trivial for anyone who has done significant GUI work, but I've been off in the equivalent of CGI applications for my whole career.
I'm building an application that displays tabular data, displays the same data in a graph form, and has an edit field for both entering new data and for editing existing data. After asking about sharing resources, I decided that all of the data involved will be stored in an MVar so that every component can just read the current state from the MVar.
All of that works, but now it is time for me to rearrange the application so that it can be interactive.
With that in mind, I have three widgets: a TextView (for editing), a TreeView (for displaying the data), and a DrawingArea (for displaying the data as a graph).
I THINK I need to do two things, and the core of my question is, are these the right things, or is there a better way.
Thing the first: All event handlers, those functions that will be called any time a redisplay is needed, need to be written at a high level and then passed into the function that actually constructs the widget to begin with. For instance:
drawStatData :: DrawingArea -> MVar Core.ST -> (Core.ST -> SetRepWorkout.WorkoutStore) -> IO ()
createStatView :: (DrawingArea -> IO ()) -> IO VBox
createUI :: MVar Core.ST -> (Core.ST -> SetRepWorkout.WorkoutStore) -> IO HBox
createUI storeMVar field = do
graphs <- createStatView (\area -> drawStatData area storeMVar field)
hbox <- hBoxNew False 10
boxPackStart hbox graphs PackNatural 0
return hbox
In this case, createStatView builds up a VBox that contains a DrawingArea to graph the data and potentially other widgets. It attaches drawStatData to the realize and exposeEvent events for the DrawingArea. I would do something similar for the TreeView, but I am not completely sure what since I have not yet done it and what I am thinking of would involve replacing the TreeModel every time the TreeView needs to be updated.
My alternative to the above would be...
drawStatData :: DrawingArea -> MVar Core.ST -> (Core.ST -> SetRepWorkout.WorkoutStore) -> IO ()
createStatView :: IO (VBox, DrawingArea)
... but in this case, I would arrange createUI like so:
createUI :: MVar Core.ST -> (Core.ST -> SetRepWorkout.WorkoutStore) -> IO HBox
createUI storeMVar field = do
(graphbox, graph) <- createStatView (\area -> drawStatData area storeMVar field)
hbox <- hBoxNew False 10
boxPackStart hbox graphs PackNatural 0
on graph realize (drawStatData graph storeMVar field)
on graph exposeEvent (do liftIO $ drawStatData graph storeMVar field
return ())
return hbox
I'm not sure which is better, but that does lead me to...
Thing the second: it will be necessary for me to rig up an event system so that various events can send signals all the way to my widgets. I'm going to need a mediator of some kind to pass events around and to translate application-semantic events to the actual events that my widgets respond to. Is it better for me to pass my addressable widgets up the call stack to the level where the mediator lives, or to pass the mediator down the call stack and have the widgets register directly with it?
So, in summary, my two questions:
1) pass widgets up the call stack to a global mediator, or pass the global mediator down and have the widgets register themselves to it?
2) pass my redraw functions to the builders and have the builders attach the redraw functions to the constructed widgets, or pass the constructed widgets back and have a higher level attach the redraw functions (and potentially link some widgets together)?
Okay, and...
3) Books or wikis about GUI application architecture, preferably coherent architectures where people aren't arguing about minute details?
The application in its current form (displays data but does not write data or allow for much interaction) is available at https://bitbucket.org/savannidgerinel/fitness . You can run the application by going to the root directory and typing
runhaskell -isrc src/Main.hs data/
or...
cabal build
dist/build/fitness/fitness data/
You may need to install libraries, but cabal should tell you which ones.