module TclPrimCore where -- This module defines the Widget type and the support definitions for the -- five classes of widgets: window, panel, menu, canvas and tag. import Char import List import TclGUI import qualified TclTrie as T import TclTrie (TrieObjV) import IOExts import Remover import Utils import Monad import Maybe import PlacePos -- RGB Colors ------------------------------------------ rgb :: Int -> Int -> Int -> String rgb r g b = "#" ++ hex r ++ hex g ++ hex b where hex n = hex1 (if n < 0 then 0 else if n > 255 then 255 else n) hex1 n = [hex2 (n `div` 16), hex2 (n `mod` 16)] hex2 n = if n<10 then chr(ord '0'+n) else chr(ord 'A'+n-10) -- Timer Support --------------------------------------- after :: Int -> IO () -> TclGUI (IO ()) after n p gui = do x <- tcl_newName gui remref <- newIORef undefined (tcl_cb,rm) <- tcl_callback gui "" (\_-> do p callRemoverRef remref) writeIORef remref rm nm <- tcl gui ["after", show n, tcl_cb] cancel <- mkRemover $ tcl_ gui ["after cancel",nm] return (cancel >> callRemoverRef remref) -- Widget ---------------------------------------------------- data Widget c w = TkWgt TkWOps c w instance Eq (Widget a b) where w1 == w2 = wtag w1 == wtag w2 data TkWOps = TkWOps { wpath_ :: ! WPath, wtag_ :: ! WTag, cset_ :: ! ([Config] -> IO ()), cget_ :: ! (Config -> IO String), bindArgs_ :: ! ((Bool,Bool,TkEvent,String) -> ([String]-> IO ()) -> IO Remover), destroy_ :: ! (IO ()), trieObj_ :: ! TrieObjV, guiState_ :: ! GUIInfo } type WPath = String type WTag = String type TkEvent = String (%%) :: String -> String -> Config s %% s2 = Tk_Generic s s2 (%#) :: String -> TclGUI () -> Config s %# act = Tk_Action s act parentWPath :: Widget c w -> WPath parentWPath w = parentWPath' $ wpath w parentWPath' :: WPath -> WPath parentWPath' w = case dropWhile (/= '.') $ reverse w of "." -> "." ('.':xs) -> reverse xs wpath :: Widget c w -> WPath wpath (TkWgt (TkWOps {wpath_ = nm}) _ _) = nm wtag :: Widget c w -> WTag wtag (TkWgt (TkWOps {wtag_ = nm}) _ _) = nm cset' :: Widget c w -> [Config] -> IO () cset' (TkWgt (TkWOps {cset_ = cset}) _ _) cfgs = cset cfgs cget' :: Widget c w -> Config -> IO String cget' (TkWgt (TkWOps {cget_ = cget}) _ _) c = cget c destroy :: Widget c w -> IO () destroy (TkWgt (TkWOps {destroy_ = destroy}) _ _) = destroy bindArgs :: Widget c w -> (Bool,Bool,TkEvent,String) -> ([String] -> IO ()) -> IO (IO ()) bindArgs (TkWgt (TkWOps {bindArgs_ = bind}) _ _) = bind trieObj :: Widget c w -> TrieObjV trieObj (TkWgt (TkWOps {trieObj_ = obj}) _ _) = obj getTclGUIInfo :: Widget c w -> GUIInfo getTclGUIInfo (TkWgt (TkWOps {guiState_ = st}) _ _) = st bindCommand :: Widget c w -> IO () -> IO (IO ()) bindCommand (TkWgt (TkWOps {trieObj_ = obj, cset_ = cset, wpath_ = wpath,guiState_ = gui}) _ _) act = bindAnAction "command" "" (const $ act) obj (\s -> (cset ["command" %% s],cset ["command" %% "{}"])) True gui tclW :: Widget c w -> (WPath -> [String]) -> IO String tclW w f = tcl (getTclGUIInfo w) $ f (wpath w) tclW_ :: Widget c w -> (WPath -> [String]) -> IO () tclW_ w f = tcl_ (getTclGUIInfo w) $ f (wpath w) tcl_append :: String -> String -> String tcl_append "." child = child tcl_append parent child = parent++child checkAlive :: Widget c w -> IO Bool checkAlive (TkWgt (TkWOps {trieObj_ = obj}) _ _) = T.checkAlive obj addFinaliserW :: Widget c w -> Remover -> IO () addFinaliserW (TkWgt (TkWOps {trieObj_ = obj}) _ _) = T.addFinaliserW obj mkChildOf :: Widget c w -> IO WPath mkChildOf w = do nm <- tcl_newWgtName (getTclGUIInfo w) return $ tcl_append (wpath w) nm mkSibling :: Widget c w -> IO WPath mkSibling w = do nm <- tcl_newWgtName (getTclGUIInfo w) return $ tcl_append (parentWPath w) nm mkChildOf' :: WPath -> TclGUI WPath mkChildOf' w gui = do nm <- tcl_newWgtName gui return $ tcl_append w nm mkSibling' :: Widget c w -> TclGUI WPath mkSibling' w gui = do nm <- tcl_newWgtName gui return $ tcl_append (parentWPath w) nm {- when we bind an action to an event we create a callback that Tcl will refer to it by. We then bind the callback name to the event and TclGUI.hs (tcl_callback) deals with connecting the haskell callback to the Tcl name. (Note that the remove action returned by tcl_callback is already idempotent (one shot action)). We want to be able to remove callbacks too. We add a finaliser to the widget so that the haskell callback is deleted when the widget is destroyed. We return an action that both deletes the haskell callback and unbinds the event. We only want this to happen once. So we use a remover ref, that is a idempotent (onceshot) remove action. We also update the binding. When an event binding is overridden we clear all the unbind remover refs so they can't interfere again, as they are no longer valid. When and event binding is added we just add the invalidation action to the TrieObj object. -} bindAnAction :: String -> String -> ([String] -> IO ()) -> TrieObjV -> (String -> (IO (),IO ())) -> Bool -> TclGUI Remover bindAnAction event args op obj mkBinds clearall gui = do (s,rm) <- tcl_callback gui args op case mkBinds s of (bind,bindCleanUp) -> do bind rmref <- newIORef $ bindCleanUp rmold <- T.updBinding obj event s clearall (Just $ rm >> writeIORef rmref (return ())) maybe (return ()) id rmold return $ callRemoverRef rmref >> rm removeBind :: (String -> IO String) -> String -> IO () removeBind tke op = do ops <- tke "" let ops' = map toCommand $ lines ops case filter (/= op) ops' of [] -> do tke "{}";return () (x:xs) -> do tke $ unlines $ x:xs;return () where toCommand s = "{" ++ s ++ "}" -- WWidget ------------------------------------------------- type WWidget a = Widget WClass a data WClass = TkW String tcl_wwidget :: WPath -> [Config] -> String -> w -> TclGUI (WWidget w) tcl_wwidget nm cfgs k w gui = do tov <- register gui nm let wgt = TkWgt (tcl_wops nm gui tov) (TkW nm) w let (cfgs',act) = tcl_configure cfgs tcl_ gui [k, nm, cfgs'] act (cset' wgt) tov gui return wgt totcl_wwidget :: WPath -> w -> TclGUI (WWidget w) totcl_wwidget nm w gui = do mb <- register gui nm return $ TkWgt (tcl_wops nm gui mb) (TkW nm) w tcl_wops :: String -> GUIInfo -> TrieObjV -> TkWOps tcl_wops nm gui tov = TkWOps nm nm csetW cgetW bindArgsW destroy tov gui where csetW [] = return () csetW cfgs = do let (cfgs',act) = tcl_configure cfgs tcl_ gui [nm, "configure", cfgs'] act csetW tov gui cgetW cfg = tcl gui [nm,"cget",'-':cstr] where (cstr,_) = configText cfg destroy = do b <- T.checkAlive tov when b $ do b <- runDeletes gui nm when b $ tcl_ gui ["destroy",nm] bindArgsW (brk,add,ev,args) a = bindAnAction ev args a tov mkBinds (not add) gui where mkBinds cb = let op = bind_break_munge brk cb in (tcl_ gui [ "bind", nm, ev,addOrOverwrite add op ], removeBind (\op -> tcl gui ["bind",nm,ev,op]) op) -- WWidget ------------------------------------------------- type PWidget a = Widget PClass a data PClass = TkP String tcl_pwidget :: WPath -> [Config] -> String -> w -> TclGUI (PWidget w) tcl_pwidget nm cfgs k w gui = do tov <- register gui nm let wgt = TkWgt (tcl_wops nm gui tov) (TkP nm) w let (cfgs',act) = tcl_configure cfgs tcl_ gui [k, nm,cfgs'] act (cset' wgt) tov gui return wgt totcl_pwidget :: WPath -> w -> TclGUI (PWidget w) totcl_pwidget nm w gui = do mb <- register gui nm return $ TkWgt (tcl_wops nm gui mb) (TkP nm) w tcl_pops :: String -> GUIInfo -> TrieObjV -> TkWOps tcl_pops nm gui tov = TkWOps nm nm csetW cgetW bindArgsW destroy tov gui where csetW [] = return () csetW cfgs = do let (cfgs',act) = tcl_configure cfgs tcl_ gui [nm, "configure", cfgs'] act csetW tov gui cgetW cfg = tcl gui [nm,"cget",'-':cstr] where (cstr,_) = configText cfg destroy = do b <- T.checkAlive tov when b $ do b <- runDeletes gui nm when b $ tcl_ gui ["destroy",nm] bindArgsW (brk,add,ev,args) a = bindAnAction ev args a tov mkBinds (not add) gui where mkBinds cb = let op = bind_break_munge brk cb in (tcl_ gui [ "bind", nm, ev,addOrOverwrite add op ], removeBind (\op -> tcl gui ["bind",nm,ev,op]) op) raise :: PWidget a -> Maybe WPath -> IO () raise w sMb = tclW_ w $ \w -> ["raise " ++ w ++ " " ++ maybe "" id sMb] lower :: PWidget a -> Maybe WPath -> IO () lower w sMb = tclW_ w $ \w -> ["lower " ++ w ++ " " ++ maybe "" id sMb] -- MWidget ------------------------------------------------- type MWidget a = Widget MClass a data MClass = TkM String (IO (Maybe Int)) type Menu = WWidget Men data Men = TkMen -- move a menu item by deleting it and replacing it in tcl land. -- but we don't need to zap all the haskell state associated with it. -- remove it from menu item list, calculate its new location and then -- replace. moveMItem :: Menu -> WTag -> PlacePos WTag -> IO () moveMItem m tg pos = moveMItem' m tg pos $ \i _ -> mkconfigs (wpath m) (getTclGUIInfo m) i -- basic move function, note that mkconfigs is not very efficient -- frantk has something better, that relies on all options being -- set through frantk rather than directly moveMItem' :: Menu -> WTag -> PlacePos WTag -> (Int -> String -> IO (String,String)) -- get the item name and config info -> IO () moveMItem' m tg pos mkconfigs = do mb <- T.removeItemObj (trieObj m) tg case mb of Nothing -> return () Just ptg' -> do p <- getItemPos m pos T.insertItemObj (trieObj m) tg p ptg <- checkTearOff m ptg' p <- checkTearOff m p let nm = wpath m gui = getTclGUIInfo m removeAndReplace'' nm gui ptg p $ mkconfigs ptg tg -- there is a bug in tcl8.0, at least under nt. If you delete a -- menu item, then the item after it becomes disabled. Removing -- and replacing it seems to fix this problem. Odd but true. when (p > ptg) $ do tg2 <- T.atIndexItemObj (trieObj m) ptg' maybe (return ()) (\tg -> removeAndReplace'' nm gui ptg ptg $ mkconfigs ptg tg) tg2 removeAndReplace :: Menu -> Int -> Int -> IO () removeAndReplace m ptg nptg = removeAndReplace' (wpath m) (getTclGUIInfo m) ptg nptg -- when replacing an item, if it at the end of the menu, don't need -- and we're placing it in it's new position, we're doing it for the -- bug mentioned above. This ain't necessary with the last item. removeAndReplace' :: WPath -> GUIInfo -> Int -> Int -> IO () removeAndReplace' nm gui ptg nptg = removeAndReplace'' nm gui ptg nptg $ mkconfigs nm gui ptg -- see moveMItem for commens on mkconfigs removeAndReplace'' :: WPath -> GUIInfo -> Int -> Int -> IO (String,String) -> IO () removeAndReplace'' nm gui ptg nptg mkconfigs = if ptg /= nptg then rem else do sz <- lastMItem nm gui unless (ptg >= sz) rem where -- calculate all the current configuration options for the widget -- before deleting it. Then when redisplayed do so with its current -- configuration list. rem = do (typ,cfgs) <- mkconfigs tcl_ gui [nm,"delete",show ptg] tcl_ gui [nm,"insert",show nptg,typ,cfgs] -- get all the configuration options using entrycget, then -- make a new list of actions. Note that different types of -- menu item have different configuration options. -- not very efficient, frantk does this better, but relies -- on all configs being set in frantk and not directly mkconfigs :: WPath -> GUIInfo -> Int -> IO (String,String) mkconfigs nm gui ptg = do t <- tcl gui [nm,"type",show ptg] let cs = convert t vs <- mapM (\s -> tcl gui [nm,"entrycget",show ptg,'-':s]) cs let mkc _ "" = "" -- note that haskell events need to be surrounded by brackets, -- as there is more than one word in the event, -- but they are not in form returned by entrycget, mkc a@("command") b = "-" ++ a ++ " {" ++ b ++ "}" mkc a b = "-" ++ a ++ " " ++ b return (t,unwords $ zipWith mkc cs vs) where convert "command" = button convert "separator" = all convert "radiobutton" = rnc ++ ["value"] convert "checkbutton" = rnc ++ ["offvalue","onvalue"] convert "cascade" = button ++ ["menu"] all = ["columnbreak","hidemargin"] button = ["activebackground","activeforeground", "accelerator", "background","bitmap","command","font", "foreground","hidemargin","image","label", "state","underline"] ++ all rnc = button ++ ["variable","indicatoron","selectcolor","selectimage"] getMItemLoc :: Menu -> WTag -> IO (Maybe Int) getMItemLoc menu tag = do p <- T.indexItemObj (trieObj menu) tag maybe (return Nothing) (fmap Just . checkTearOff menu) p getItemPos :: Menu -> PlacePos WTag -> IO Int getItemPos m PlaceTop = return 0 getItemPos m PlaceBottom = menuSize m getItemPos m (PlaceBefore tg) = do p <- T.indexItemObj (trieObj m) tg case p of Nothing -> return 0 Just p -> return p getItemPos m (PlaceAfter tg) = do p <- T.indexItemObj (trieObj m) tg case p of Nothing -> menuSize m Just p -> return $ p + 1 menuSize :: Menu -> IO Int menuSize m = menuSize' (trieObj m) menuSize' :: TrieObjV -> IO Int menuSize' t = T.sizeItems t lastMItem :: WPath -> GUIInfo -> IO Int lastMItem w g = do n <- tcl g [w,"index end"] case n of "none" -> return 0 x -> return $ read x checkTearOff' :: Menu -> IO Bool checkTearOff' m = do t <- cget' m ("tearoff" %% "") return $ if t == "0" then False else True checkTearOff :: Menu -> Int -> IO Int checkTearOff m p = do b <- checkTearOff' m if b then return $ p + 1 else return $ p -- create the haskell data for the mwidget totcl_mwidget :: Menu -> WTag -> Int -> w -> IO (MWidget w) totcl_mwidget men tag n w = do obj <- register gui tag mb <- T.indexItemObj menobj tag unless (isJust mb) (T.insertItemObj menobj tag n) let getItemLoc :: IO (Maybe Int) getItemLoc = getMItemLoc men tag let remItem :: IO (Maybe Int) remItem = T.removeItemObj menobj tag >>= maybe (return Nothing) (fmap Just . checkTearOff men) let wgt = TkWgt (tcl_mops getItemLoc remItem nm tag gui obj) (TkM nm getItemLoc) w return $! wgt where nm = wpath men menobj = trieObj men gui = getTclGUIInfo men -- create the tclhaskell mwidget tcl_mwidget :: Menu -> [Config] -> String -> w -> Int -> IO (MWidget w) tcl_mwidget m = tcl_mwidget' mk m where mk i _ = mkconfigs (wpath m) (getTclGUIInfo m) i -- create the tclhaskell mwidget, but given a function to get config and -- name info (for destroy) tcl_mwidget' :: (Int -> String -> IO (String,String)) -> Menu -> [Config] -> String -> w -> Int -> IO (MWidget w) tcl_mwidget' mkconfigs men cfgs k w n = do un <- tcl_newName gui let tag = nm ++ ".menuItem" ++ show un obj <- register gui tag T.insertItemObj menobj tag n let getItemLoc :: IO (Maybe Int) getItemLoc = getMItemLoc men tag let remItem :: IO (Maybe Int) remItem = T.removeItemObj menobj tag >>= maybe (return Nothing) (fmap Just . checkTearOff men) let wgt = TkWgt (tcl_mops' getItemLoc remItem nm tag gui obj mkconfigs) (TkM nm getItemLoc) w let (cfgs',act) = tcl_configure cfgs mb <- getItemLoc tcl_ gui [ nm, "insert ", maybe "end" show mb, k, cfgs'] act (cset' wgt) obj gui return wgt where nm = wpath men menobj = trieObj men gui = getTclGUIInfo men tcl_mops :: IO (Maybe Int) -> IO (Maybe Int) -> String -> String -> GUIInfo -> TrieObjV -> TkWOps tcl_mops getLoc remLoc nm tag gui obj = tcl_mops' getLoc remLoc nm tag gui obj $ \i _ -> mkconfigs nm gui i tcl_mops' :: IO (Maybe Int) -> IO (Maybe Int) -> String -> String -> GUIInfo -> TrieObjV -> (Int -> String -> IO (String,String)) -> TkWOps tcl_mops' getLoc remLoc nm tag gui obj mkconfigs = TkWOps nm tag csetM cgetM bindArgsM destroy obj gui where csetM [] = return () csetM cs = do i <- getLoc maybeDo i $ \i -> do let (cs',act) = tcl_configure cs tcl_ gui [nm, "entryconfigure", show i, cs'] act csetM obj gui cgetM cfg = do i <- getLoc maybe (return "") (\i -> tcl gui [nm, "entrycget", show i, '-':cstr]) i where (cstr,_) = configText cfg destroy = do b <- T.checkAlive obj when b $ do i <- remLoc maybeDo i $ \i -> do b <- runDeletes gui tag when b $ tcl_ gui [nm,"delete",show i] -- there is a bug in tcl8.0, at least under nt. If you delete a -- menu item, then the item after it becomes disabled. Removing -- and replacing it seems to fix this problem. Odd but true. removeAndReplace'' nm gui i i $ mkconfigs i "" bindArgsM (brk,add,ev,args) w = fail "cannot bind menu widgets" -- CWidget ------------------------------------------------- type CWidget a = Widget CClass a data CClass = TkC String type CCoord = String ccoord :: (Int,Int) -> String ccoord (x,y) = show x ++ " " ++ show y -- create cwidget haskell data, assuming we're creating the actual tcl -- wigdet separtely totcl_cwidget :: PWidget w' -> w -> IO (CWidget w) totcl_cwidget ww w = do i <- tcl_newName gui let tag = nm ++ ".childOfCanvas" ++ show i tov <- register gui tag return $ TkWgt (tcl_cops tag nm tov gui) (TkC nm) w where nm = wpath ww gui = getTclGUIInfo ww -- draw a cwidget given the tclhaskell object, current coords, config info, -- and the name of the type of widget we wish to create. tcl_mapcwidget :: CWidget w -> [CCoord] -> [Config] -> String -> IO () tcl_mapcwidget cw@(TkWgt _ (TkC nm) _) ps cfgs k = do let (cfgs',act) = tcl_configure cfgs i <- tcl gui $ [nm,"create",k] ++ ps ++ [cfgs'] tcl_ gui [nm,"itemconfigure",i,"-tags","\"",wtag cw, "[",nm,"itemcget",i,"-tags]\""] act (cset' cw) (trieObj cw) gui return () where gui = getTclGUIInfo cw -- create a cwidget tcl_cwidget :: PWidget w' -> [CCoord] -> [Config] -> String -> w -> IO (CWidget w) tcl_cwidget c ps cfgs k w = do w <- totcl_cwidget c w tcl_mapcwidget w ps cfgs k return w tcl_cops :: String -> String -> TrieObjV -> GUIInfo -> TkWOps tcl_cops tag nm tov gui = TkWOps nm tag csetC cgetC bindArgsC destroy tov gui where csetC [] = return () csetC cfgs = do let (cfgs',act) = tcl_configure cfgs tcl_ gui [nm, "itemconfigure", tag, cfgs'] act csetC tov gui cgetC cfg = tcl gui [nm, "itemcget", tag, '-':cstr] where (cstr,_) = configText cfg destroy = do b <- T.checkAlive tov when b $ do b <- runDeletes gui tag when b $ tcl_ gui [nm, "delete", tag] bindArgsC (brk,add,ev,args) a = bindAnAction ev args a tov mkBinds (not add) gui where mkBinds cb = let op = bind_break_munge brk cb in (tcl_ gui [nm, "bind", tag, ev,addOrOverwrite add op ], removeBind (\op -> tcl gui [nm,"bind",tag,ev,op]) op) -- TWidget ------------------------------------------------- type TWidget a = Widget TClass a data TClass = TkT String TagId type TagId = String tcl_tgwidget :: PWidget w' -> TagId -> String -> [Config] -> w -> IO (TWidget w) tcl_tgwidget e ti indx cs w = do let nm = wpath e let wtag = nm ++ ".tagof" ++ ti tov <- register gui wtag let tg = TkWgt (tcl_tgops wtag nm ti tov gui) (TkT nm ti) w unless (null indx) $ tcl_ gui [nm,"tag add",ti,indx] cset' tg cs return tg where gui = getTclGUIInfo e totcl_tgwidget :: PWidget w' -> TagId -> w -> IO (TWidget w) totcl_tgwidget ww ti w = do let wtag = nm ++ ".tagof" ++ ti tov <- register gui wtag let tg = TkWgt (tcl_tgops wtag nm ti tov gui) (TkT nm ti) w return tg where gui = getTclGUIInfo ww nm = wpath ww tcl_tgops :: WTag -> WPath -> TagId -> TrieObjV -> GUIInfo -> TkWOps tcl_tgops wtag nm ti tov gui = TkWOps nm wtag csetTg cgetTg onArgsTg destroy tov gui where csetTg [] = return () csetTg cfgs = do let (cfgs',act) = tcl_configure cfgs tcl_ gui [nm, "tag configure", ti, cfgs'] act csetTg tov gui cgetTg cfg = tcl gui [nm, "tag cget", ti, '-':cstr] where (cstr,_) = configText cfg destroy = do b <- T.checkAlive tov when b $ do b <- runDeletes gui wtag when b $ tcl_ gui [nm,"tag delete",ti] onArgsTg (brk,add,ev,args) a = bindAnAction ev args a tov mkBinds (not add) gui where mkBinds cb = let op = bind_break_munge brk cb in (tcl_ gui [nm, "tag bind", show ti, ev,addOrOverwrite add op ], removeBind (\op -> tcl gui [nm,"tag bind",show ti,ev,op]) op) -- EWidget ------------------------------------------------- type EWidget a = Widget EClass a data EClass = TkE WPath WTag tcl_ewidget :: PWidget w' -> WPath -> String -> [Config] -> w -> IO (EWidget w) tcl_ewidget e wtag indx cs w = do let nm = wpath e tov <- register gui wtag let tg = TkWgt (tcl_eops nm wtag tov gui) (TkE nm wtag) w let (cs',act) = tcl_configure cs tcl_ gui [nm,"window create",indx,cs'] act (cset' tg) tov gui return tg where gui = getTclGUIInfo e totcl_ewidget :: PWidget w' -> WPath -> w -> IO (EWidget w) totcl_ewidget ww wtag w = do tov <- register gui wtag let tg = TkWgt (tcl_eops nm wtag tov gui) (TkE nm wtag) w return tg where gui = getTclGUIInfo ww nm = wpath ww tcl_eops :: WPath -> WTag -> TrieObjV -> GUIInfo -> TkWOps tcl_eops wpath wtag tov gui = TkWOps wpath wtag csetTg cgetTg onArgsTg destroy tov gui where csetTg [] = return () csetTg cs = do let (cs',act) = tcl_configure cs tcl_ gui [wpath, "window configure", wtag, cs'] act csetTg tov gui cgetTg cfg = tcl gui [wpath, "window cget", wtag, '-':cstr] where (cstr,_) = configText cfg destroy = do b <- T.checkAlive tov when b $ do b <- runDeletes gui wtag when b $ tcl_ gui ["destroy",wtag] onArgsTg (brk,add,ev,args) a = bindAnAction ev args a tov mkBinds (not add) gui where mkBinds cb = let op = bind_break_munge brk cb in (tcl_ gui ["bind",wtag, ev,addOrOverwrite add op ], removeBind (\op -> tcl gui ["bind",wtag,ev,op]) op) -- Generic Canvas Operations ------------------------------- moveObject :: CWidget w -> (Int,Int) -> IO () moveObject w@(TkWgt _ (TkC nm) _) c = tcl_ (getTclGUIInfo w) [nm, "move",wtag w,ccoord c] removeObject :: CWidget w -> IO () removeObject w@(TkWgt _ (TkC nm) _) = tcl_ (getTclGUIInfo w) [nm, "delete", wtag w] scaleObject :: CWidget a -> (Int,Int) -> Double -> Double -> IO () scaleObject w@(TkWgt _ (TkC nm) _) (x,y) xs ys = tcl_ (getTclGUIInfo w) [nm,"scale",wtag w,show x ++ " " ++ show y,show xs,show ys] lowerObject :: CWidget w -> Maybe String -> IO () lowerObject w@(TkWgt _ (TkC nm) _) s = tcl_ (getTclGUIInfo w) $ [nm, "lower", wtag w] ++ maybe [] (\x -> [x]) s raiseObject :: CWidget w -> Maybe String -> IO () raiseObject w@(TkWgt _ (TkC nm) _) s = tcl_ (getTclGUIInfo w) $ [nm,"raise",wtag w] ++ maybe [] (\x -> [x]) s bboxObjects :: [CWidget a] -> IO (Int,Int,Int,Int) bboxObjects [] = return (0,0,0,0) bboxObjects (w@(TkWgt _ (TkC nm) _):cws') = do let cws = filter chk cws' s <- tcl (getTclGUIInfo w) ([nm,"bbox", wtag w]++[wtag cw | cw<-cws]) let ps = map parseInt (words s) case ps of [a,b,c,d] -> return (a,b,c,d) xs -> return (0,0,0,0) where chk (TkWgt _ (TkC nm') _) = nm == nm' getCoords :: CWidget w -> IO [(Int,Int)] getCoords w@(TkWgt _ (TkC nm) _) = do s <- tcl (getTclGUIInfo w) [nm, "coords",wtag w] let ps = map (parseInt . takeWhile (/='.')) (words s) return (makeCoords ps) where makeCoords [] = [] makeCoords [_] = error "getCoords:makeCoords" makeCoords (x:y:ss) = (x,y):makeCoords ss setCoords :: CWidget w -> [(Int,Int)] -> IO () setCoords w@(TkWgt _ (TkC nm) _) ll = tcl_ (getTclGUIInfo w) ([nm, "coords",wtag w] ++ map ccoord ll) -- Preparing for Widget Configurartion ---------------- tcl_configure :: [Config] -> (String, ([Config] -> IO ()) -> TrieObjV -> TclGUI ()) tcl_configure cs = foldr config ("",\_ _ _ -> return ()) cs where config :: Config -> (String,([Config] -> IO ()) -> TrieObjV -> TclGUI ()) -> (String,([Config] -> IO ()) -> TrieObjV -> TclGUI ()) config (Tk_Action nm f) (s,a) = (s,op) where op :: ([Config] -> IO ()) -> TrieObjV -> TclGUI () op cset obj gui = do bindAnAction nm "" (\_-> f gui) obj (\cb -> (cset [nm %% cb],cset [nm %% "{}"])) True gui a cset obj gui config c (s,a) = (" -" ++ tcl_config c ++ s ,a) -- Config ---------------------------------------------------- data Config = Tk_Generic String String | Tk_Action String (GUIInfo -> IO ()) identifyConfig :: Config -> String identifyConfig (Tk_Generic nm _) = nm identifyConfig (Tk_Action nm _) = nm sameConfig :: Config -> Config -> Bool sameConfig (Tk_Generic s1 _) (Tk_Generic s2 _) = s1 == s2 sameConfig (Tk_Action s1 _) (Tk_Generic s2 _) = s1 == s2 sameConfig _ _ = False tcl_config :: Config -> String tcl_config c = case configText c of (act,arg) -> act ++ " " ++ arg configText :: Config -> (String,String) configText cfg = case cfg of Tk_Generic nm s -> (nm,s) Tk_Action nm s -> (nm,"") -- Munging Event Scripts ------------------------------- bind_break_munge :: Bool -> String -> String bind_break_munge True script = "{eval " ++ script ++ "; break}" bind_break_munge False script = script addOrOverwrite :: Bool -> String -> String addOrOverwrite True ('{':xs) = "{+" ++ xs addOrOverwrite False xs = xs -- Protecting Strings -- only for static texts --------- tcl_string :: String -> String tcl_string s = "\"" ++ protect s ++ "\"" where protect [] = [] protect (x:xs) = x' ++ protect xs where x' = case x of '\n' -> "\\n" '\t' -> "\\t" '[' -> "\\[" ']' -> "\\]" '{' -> "\\{" '}' -> "\\}" '\\' -> "\\\\" '\"' -> "\\\"" '$' -> "\\$" otherwise -> [x]