module TclGUI (module TclGUI,Remover,parseInt)where import Remover import Char import IO import System import TclCompatibility import Monad import TclTrie import IOExts import ExtArray import Int import TclTime type TclGUI a = GUIInfo -> IO a -- Lifted Communication Primitives --------------------- tcl_debug :: Bool -> TclGUI () tcl_debug flg _ = primTclDebug flg tcl_initTcl :: GUIInfo -> IO () tcl_initTcl gui = do ok <- primInitTcl if ok then tcl_ gui [init_script] else error "initialization error" tcl :: GUIInfo -> [String] -> IO String tcl _ ss = primExecuteTcl (unwords ss) tcl_ :: GUIInfo -> [String] -> IO () tcl_ _ ss = primExecuteTcl_ (unwords ss) runTcl :: GUIInfo -> IO Bool runTcl _ = primRunTcl getEvent :: GUIInfo -> IO String getEvent _ = primGetEvent -- the GUI State ---------------------------------------- data GUIInfo = GUIInfo { finaliseGUI :: IORef (IO ()), namesfromGUI :: IORef Int, callbacksGUI :: TkCB, tclTrie :: TclTrie, tclTime :: IORef Int } type TkCB = ExtArray TkCBEntry newtype TkCBEntry = TkCBEntry ([String]-> IO ()) -- Making a GUI State ---------------------------------- tcl_initState :: IO GUIInfo tcl_initState = do qtp <- newIORef (return ()) ctr <- tcl_initPathNames cb <- tcl_initCallbacks trie <- mkTclTrie tm <- newIORef 0 return $! GUIInfo qtp ctr cb trie tm -- Using the TclTrie ------------------------------------ register :: GUIInfo -> String -> IO TrieObjV register (GUIInfo {tclTrie = trie}) = register_ trie lookupTrieObj :: GUIInfo -> String -> IO (Maybe TrieObjV) lookupTrieObj (GUIInfo {tclTrie = trie}) = lookupTrieObj' trie widgetAlive' :: GUIInfo -> String -> IO Bool widgetAlive' (GUIInfo {tclTrie = trie}) = checkAlive_ trie addFinaliserW' :: GUIInfo -> String -> Remover -> IO () addFinaliserW' (GUIInfo {tclTrie = trie}) = addFinaliserW_ trie runDeletes :: GUIInfo -> String -> IO Bool runDeletes (GUIInfo {tclTrie = trie}) = doDelete trie -- Inventing New Names ---------------------------------- tcl_initPathNames :: IO (IORef Int) tcl_initPathNames = newIORef 0 tcl_newWgtName :: GUIInfo -> IO String tcl_newWgtName info = do n <- tcl_newName info return ('.':'@':(show n)) tcl_newName :: GUIInfo -> IO Int tcl_newName (GUIInfo {namesfromGUI = ctr}) = do n <- readIORef ctr writeIORef ctr (n+1) return n -- The Callback Structure ------------------------------- tcl_initCallbacks :: IO TkCB tcl_initCallbacks = newExtArray 1000 tcl_addCallback :: GUIInfo -> TkCBEntry -> IO Int tcl_addCallback (GUIInfo {callbacksGUI = cb}) p = do nm <- nextExtEntry cb writeExtArray cb nm p return nm tcl_removeCallback :: GUIInfo -> Int -> IO () tcl_removeCallback (GUIInfo {callbacksGUI = cb}) n = do clearExtArray cb n tcl_getAct :: GUIInfo -> [String] -> IO (IO ()) tcl_getAct _ [] = return (return ()) tcl_getAct (GUIInfo {callbacksGUI = cb}) (num:args) = do mbp <- readExtArray cb (parseInt num) case mbp of Nothing -> return (return ()) Just (TkCBEntry act) -> return (act args) -- Callbacks ------------------------------------------- tcl_callback :: GUIInfo -> String -> ([String]-> IO ()) -> IO (String,Remover) tcl_callback gui args a = do nr <- tcl_addCallback gui (TkCBEntry a ) rm <- mkRemover $ tcl_removeCallback gui nr return (tcl_cb nr,rm) where tcl_cb nr = unwords (["{haskellEvent \"", show nr] ++ [['%',c] | c<-args] ++ ["\"}"]) -- Quitting -------------------------------------------- quit :: GUIInfo -> IO () quit gui@(GUIInfo {finaliseGUI = rf,tclTrie = trie}) = do alv <- checkAlive_ trie "." when alv $ do p <- readIORef rf p doDelete trie "." tcl_ gui ["destroy ."] tcl_tidy :: GUIInfo -> IO () -> IO () tcl_tidy gui@(GUIInfo {finaliseGUI = rf}) p = do writeIORef rf p -- The Event Loop -------------------------------------- tcl_init :: GUIInfo -> IO () -> IO () tcl_init gui initact = do tcl_initTcl gui -- init tcl/tk initact -- perform first action tcl_cycle gui tcl_cycle :: GUIInfo -> IO () tcl_cycle gui = do fin <- runTcl gui -- run Tcl's event loop in case fin of -- one-shot mode True -> tcl_event gui -- True => process events False -> return () -- False => `.' destroyed tcl_event :: GUIInfo -> IO () tcl_event gui = do s <- getEvent gui -- read event buffer case s of "" -> tcl_cycle gui -- all events processed: -- cycle to next event _ -> do a <- tcl_getAct gui (words s) -- get action catch a tidy -- execute action tcl_event gui -- process next event where tidy err = do quit gui; ioError err tcl_eventUntil :: GUIInfo -> IO Bool -> IO () tcl_eventUntil gui st = do fin <- primRunTcl when fin event v <- st if v || not fin then return () else tcl_eventUntil gui st where event = do s <- primGetEvent a <- tcl_getAct gui (words s) catch a tidy where tidy err = do quit gui; ioError err -- Clearing the Event Queue ---------------------------- tcl_clearEventQueue :: GUIInfo -> IO () tcl_clearEventQueue gui = do s <- primGetEvent case s of "" -> return () _ -> tcl_clearEventQueue gui runLoop :: (GUIInfo -> IO ()) -> IO () runLoop initact = do st <- tcl_initState tcl_init st $ do (tcl_cb,_) <- tcl_callback st "" (\_->quit st) tcl_ st ["wm protocol . WM_DELETE_WINDOW", tcl_cb] ms <- getTclTime writeIORef (tclTime st) ms initact st getTime :: GUIInfo -> IO Double getTime gui@(GUIInfo {tclTime = var}) = do ms <- getTclTime startMS <- readIORef var return $ abs (fromIntegral (ms - startMS) / 1000) -- init_script ------------------------------------------- init_script :: String init_script = unlines ---------------------------------------------------- -- The Tk Popup menus seem to be banjaxed in some -- versions... ---------------------------------------------------- ["proc haskell_popup {menu x y {entry {}}} {" ," global tkPriv" ," global tcl_platform" ," if {($tkPriv(popup) != \"\") || ($tkPriv(postedMb) != \"\")} {" ," tkmenuunpost {}" ," }" ," tkPostOverPoint $menu $x $y $entry" ," if {$tcl_platform(platform) == \"unix\"} {" ," tkSaveGrabInfo $menu" ," grab -global $menu" ," set tkPriv(popup) $menu" ," tk_menuSetFocus $menu" ," }" ,"}" ---------------------------------------------------- -- haskellLoadFile : load file into text widget, -- 1000 bytes at a time ---------------------------------------------------- ,"proc haskellInsertFile {file text posn tags} {" ," set f [open $file r]" ," $text mark set haskellInsertFile $posn" ," while {![eof $f]} {" ," $text insert haskellInsertFile [read $f 1000] $tags" ," }" ," $text mark unset haskellInsertFile" ," close $f" ,"}" ---------------------------------------------------- -- haskellSaveFile : save file from text widget, -- 20 lines at a time ---------------------------------------------------- ,"proc haskellSaveFile {file text} {" ," set f [open $file w]" ," scan [$text index end] \"%d.\" x" ," for {set i 1} {$i < $x-20} {incr i 20} {" ," puts $f [$text get $i.0 [expr $i+19].end]" ," }" ," puts -nonewline $f [$text get $i.0 end]" ," close $f" ,"}" ]