Reputation: 97
I'm starting a GUI with haskell and gtk2hs. I've got a notebook widget and I want to switch pages with "F1, F2 ... F11" keys.
My working code is:
import Control.Monad.Trans (liftIO)
import Graphics.UI.Gtk
main = do
initGUI
builder <- builderNew
builderAddFromFile builder "M62.glade"
window <- builderGetObject builder castToWindow "window1"
notebook <- builderGetObject builder castToNotebook "notebook1"
window `on` keyPressEvent $ tryEvent $ do "F1" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 0
window `on` keyPressEvent $ tryEvent $ do "F2" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 1
window `on` keyPressEvent $ tryEvent $ do "F3" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 2
window `on` keyPressEvent $ tryEvent $ do "F4" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 3
window `on` keyPressEvent $ tryEvent $ do "F5" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 4
window `on` keyPressEvent $ tryEvent $ do "F6" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 5
window `on` keyPressEvent $ tryEvent $ do "F7" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 6
window `on` keyPressEvent $ tryEvent $ do "F8" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 7
window `on` keyPressEvent $ tryEvent $ do "F9" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 8
window `on` keyPressEvent $ tryEvent $ do "F10" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 9
window `on` keyPressEvent $ tryEvent $ do "F11" <- eventKeyName
liftIO $ notebookSetCurrentPage notebook 10
onDestroy window mainQuit
widgetShowAll window
mainGUI
Is there betters and/or concises ways to do it? I've tried to handle it out of the 'main' but then only "F1" works. I don't see how to manage this boilerplate.
Upvotes: 0
Views: 172
Reputation: 5241
Try splitting the repeated part into a function, like this:
import Control.Monad
import Graphics.UI.Gtk
main = do
initGUI
builder <- builderNew
builderAddFromFile builder "M62.glade"
window <- builderGetObject builder castToWindow "window1"
notebook <- builderGetObject builder castToNotebook "notebook1"
-- Split the repeated code into a reusable function, like this
let registerKeyPressEvent n =
window `on` keyPressEvent $ tryEvent $ do
pressed <- eventKeyName
guard (pressed == ("F" ++ show (n + 1)))
liftIO $ notebookSetCurrentPage notebook n
-- Thanks to Tarmil for catching a bug in the code that used to be above.
-- Tarmil got it right, so I'm borrowing his/her version.
-- Then you can call it more than once
registerKeyPressEvent 0
registerKeyPressEvent 1
registerKeyPressEvent 2
registerKeyPressEvent 3
registerKeyPressEvent 4
registerKeyPressEvent 5
registerKeyPressEvent 6
registerKeyPressEvent 7
registerKeyPressEvent 8
registerKeyPressEvent 9
registerKeyPressEvent 10
-- But even that is too verbose.
-- You can shorten it even further like this:
mapM_ registerKeyPressEvent [0..10]
mapM
is like map
, except for monads. The type of map
is:
map :: (a -> b) -> [a] -> [b]
meaning that it takes a function and applies it to every element of a list, returning the result. The type of mapM
is:
mapM :: Monad m => (a -> m b) -> [a] -> m [b]
meaning that it takes a monadic function (such as registerKeyPressEvent
, function in the IO
monad that creates the side effect of registering a key press event). mapM
then executes this function once for every element in the list, and not only collects the results into a list, but collects the monadic actions into the resulting monad, meaning that the side effects from running registerKeyPressEvent
11 times are performed in order.
The final piece of the puzzle is that you might get a type error if you use mapM
, because it assumes you care about the resulting list, and therefore returns m [b]
. However, in this case, the type of main is IO ()
, and ()
is not going to match up to [b]
. You therefore want a slight variation on mapM
that throws away the resulting list, only collecting the monadic actions:
mapM_ :: Monad m => (a -> m b) -> [a] -> m ()
This has the return type you're looking for.
Upvotes: 2
Reputation: 152697
How about this:
window `on` keyPressEvent $ tryEvent $ do
'F':n_ <- eventKeyName
let (n, ""):_ = reads n_
liftIO . notebookSetCurrentPage notebook $ n - 1
This is hopelessly partial: there are two partial pattern matches that can throw an exception. But that's okay, because that's what tryEvent
is for. At time of writing, all other answers involve registering many event handlers, whereas this one registers only one. This should have a (slight) performance advantage.
Upvotes: 3
Reputation: 11362
I don't know much about gtk2hs, but using forM_
to loop over the key indices should go a long way. Also, it seems events are a MonadPlus
, so pattern match failure can be favorably replaced with guard
.
forM_ [0..10] $ \i -> do
let key = "F" ++ show (i + 1)
window `on` keyPressEvent $ tryEvent $ do
pressed <- eventKeyName
guard (pressed == key)
liftIO $ notebookSetCurrentPage notebook i
Upvotes: 7