haskellgtk2hs

Timer function using gtk2hs


I'm trying to make a Timer in Haskell using gtk2hs. I found an example on this website wiki.haskell Tutorial Threaded GUI which I could successfully implement in my project. The only problem I'm facing is creating a restart button for the timer.

My goal is that when people pres the "New game" button, that a new game starts and that the timer resets.

If a want to just restart a game I can use this line of code

onClicked button1 (startNewGame table window)

, which works. The problem is I can't find a way to bind a the start timer function to a button.

I tried doing this:

onClicked button1 (do (startTimer box) (startNewGame table window))

Which does not work, also this does not work:

onClicked button1 (startTimer box)

How am I suppose to restart a thread correctly? When I run this code:

onClicked button1 (startTimer box)

I get this error:

gui.hs:29:25:
    Couldn't match type `ThreadId' with `()'
    Expected type: IO ()
      Actual type: IO ThreadId
    In the return type of a call of `startTimer'
    In the second argument of `onClicked', namely `(startTimer box)'
    In a stmt of a 'do' block: onClicked button1 (startTimer box)

How can I bind the (startTimer box) function to a button?

Source code:

import Graphics.UI.Gtk
import SetTest
import qualified Data.Set as Set
import qualified Data.Map.Strict as Map
import Control.Monad.Trans(liftIO)
import Debug.Trace
import Control.Concurrent
import Control.Concurrent.MVar
import System.Exit

main :: IO ()
main = do
     initGUI
     window <- windowNew
     set window [windowTitle := "Minesweeper",
                 windowDefaultWidth := 450, windowDefaultHeight := 200]


     box <- vBoxNew False 0
     containerAdd window box

     button1 <- buttonNewWithLabel "New game"                         
     boxPackStart box button1 PackGrow 0

     widgetShowAll window

     table   <- tableNew 5 5 True
     --onClicked button1 (do (startTimer box) (startNewGame table window))
     --onClicked button1 (startTimer box)
     onClicked button1 (startNewGame table window)
     startTimer box
     containerAdd window table
     startNewGame table window
     boxPackStart box table PackNatural 0
     widgetShowAll window
     onDestroy window mainQuit
     mainGUI

startTimer :: BoxClass self => self -> IO ThreadId
startTimer box =  do 
                  timeLabel <- labelNew Nothing
                  boxPackStart box timeLabel PackNatural 0
                  forkIO $ do
                   let
                       printTime t = do{
                               threadDelay 1000000;
                               postGUIAsync $ labelSetText timeLabel (show t);
                               printTime (t+1)}
                   printTime 0

startNewGame:: (WidgetClass self, TableClass self1) => self1 -> self -> IO ()
startNewGame table window = let board = (SetTest.initialize 5 (5,5) (1,1)) :: MyBoard 
                            in checkStatusGame table board window   
:: (WidgetClass self, TableClass self1) =>
 self1 -> MyBoard -> self -> IO ()
checkStatusGame table board window 
                            | won board = do  
                                            cleanAndGenerateTable board table window True
                                            (dialogMessage "hurray hurray hurray" "Gratz, you won!!!")

                            | lost board = do 
                                             (dialogMessage "Baby rage window" "Soz, you lost...")  
                                             cleanAndGenerateTable board table window True
                            | otherwise =  (cleanAndGenerateTable board table window False)
cleanAndGenerateTable :: (WidgetClass self, TableClass self1) =>
 MyBoard -> self1 -> self -> Bool -> IO ()
cleanAndGenerateTable board table window finished = do 
                                            let fieldList = [(x,y) | x <- [0 .. (height board)] , y <- [0 .. (width board)] ]
                                            children <- containerGetChildren table
                                            mapM_ (\child -> containerRemove table child >> widgetDestroy child) children
                                            if finished
                                            then mapM_(generateTableFinished board table window) fieldList
                                            else mapM_ (generateTable board table window) fieldList
                                            widgetShowAll window  

generateTable board table window (x,y) 
                    | Set.member (x,y) (flaggedCells board) = createButton "flag.jpg" (x,y) table board window              
                    | Map.member (x,y) (clickedCells board) = createClickedButton (show (Map.findWithDefault (-1) (x,y) (clickedCells board))) (x,y) table                   
                    | otherwise = createButton "masked.png" (x,y) table board window


generateTableFinished board table window (x,y) 
                    | Set.member (x,y) (bombs board) = createButtonNoAction "bomb.jpg" (x,y) table board window                   
                    | Map.member (x,y) (clickedCells board) = createClickedButton (show (Map.findWithDefault (-1) (x,y) (clickedCells board))) (x,y) table                   
                    | otherwise = createClickedButton (show (Map.findWithDefault (-1) (x,y) (maskedCells board))) (x,y) table

createButtonNoAction pth (x,y) table board window = do 
                                     button <- buttonNew
                                     box   <- hBoxNew False 0
                                     image <- imageNewFromFile pth 
                                     boxPackStart box image PackRepel 0
                                     containerAdd button box
                                     tableAttachDefaults table button x (x+1) y (y+1)  

createClickedButton lbl (x,y) table = do 
                                     button <- buttonNew
                                     box   <- hBoxNew False 0
                                     label <- labelNew (Just lbl)
                                     boxPackStart box label PackRepel 0
                                     containerAdd button box
                                     tableAttachDefaults table button x (x+1) y (y+1)


createButton pth (x,y) table board window = do 
                                     button <- buttonNew
                                     box   <- hBoxNew False 0
                                     image <- imageNewFromFile pth 
                                     boxPackStart box image PackRepel 0
                                     containerAdd button box
                                     tableAttachDefaults table button x (x+1) y (y+1)  
                                     on button buttonReleaseEvent $ do 
                                                                      click <- eventButton
                                                                      liftIO $ case click of { LeftButton -> (checkStatusGame table (SetTest.click (x,y) board) window); RightButton -> (checkStatusGame table (SetTest.flag (x,y) board) window)  }
                                                                      return False
                                     return ()  

dialogMessage title msg =  do dialog <- messageDialogNew Nothing [] MessageOther ButtonsOk msg
                              set dialog [windowTitle := title]
                              widgetShowAll dialog
                              dialogRun dialog
                              widgetDestroy dialog

Solution

  • If you want to communicate with your timer thread, you will need to hand it a communication channel. An MVar seems appropriate here.

    startTimer :: BoxClass self => self -> MVar Integer -> IO ThreadId
    startTimer box timer = do 
      timeLabel <- labelNew Nothing
      boxPackStart box timeLabel PackNatural 0
      forkIO . forever $ do
        threadDelay 1000000
        t <- takeMVar timer
        putMVar timer (t+1)
        postGUIAsync $ labelSetText timeLabel (show t)
    

    At the top of main, you can now create a fresh MVar with timer <- newMVar 0, and pass this to startTimer. In your button callback, you can takeMVar timer >> putMVar timer 0 to reset the timer.