WidgetSetImpl - Tcl specific This module provides basic tcl specific \begin{code} module WidgetSetImpl (module WidgetSetImpl) where import AbstractInterface import qualified TclPrimImpl as T import Identify import StaticTypes import IOExts import Compatibility import Listener import List import Monad \end{code} Instances for tcl-tk widgets All tcl-tk widgets are widget items. Don't allow destruction of the root window, that only happens when we quit. \begin{code} instance WidgetItem (T.Widget c w) where destroy w | T.wpath w == "." = return () destroy w = T.destroy w uniqueId w = identify $ T.wtag w cset w c = T.cset' w c addFinaliserW w act = T.addFinaliserW w act \end{code} Windows, all panel widgets, all canvas widgets, all tag widgets, all embedded text widgets are bindable (ie they can listen to user input). \begin{code} instance Bindable T.Window where bind = bindWidget instance Bindable (T.PWidget w) where bind = bindWidget instance Bindable (T.CWidget w) where bind = bindWidget instance Bindable (T.TWidget w) where bind = bindWidget instance Bindable (T.EWidget w) where bind = bindWidget \end{code} There is a special bindable instance for canvas widgets. This is important when listening to mouse movement on canvases. It is common to move canvas items around. When doing this we usually listen to the mouse movement on the parent canvas to get the movement coordinates in canvas coordinates. This messes up with canvas window items (ie placing panel items in canvases). Mouse input when over the panel item goes to it and not to the canvas. We can fix this y listening to mouse movements on the top level window (which hears everything) and then translate the mouse coordinates into the canvas coordinates. (ok, its a bit of a hack :-) \begin{code} instance Bindable T.Canvas where bind = bindWidget' bindArgs bindArgs :: T.Canvas -> (Bool,Bool,String,String) -> ([String] -> IO ()) -> IO (IO ()) bindArgs c (brk,_,"",xy) a = do p <- T.tclW c $ \c -> ["winfo toplevel",c] w <- T.totcl_wwidget p T.TkTop (T.getTclGUIInfo c) let bind = T.bindArgs w (brk,True,"","XY") case xy of "XY" -> bind a "xy" -> bind (a' p) where a' p [x,y] = do x' <- T.tclW c $ \c -> ["winfo rootx", c] y' <- T.tclW c $ \c -> ["winfo rooty", c] let x'' = T.parseInt x - T.parseInt x' y'' = T.parseInt y - T.parseInt y' a [show x'',show y''] bindArgs c w a = T.bindArgs c w a \end{code} Tcl-tk action listeners. bind to the command from the widget the IO action from the listener. (See Listener.hs for more on fireListener'). \begin{code} actionW :: T.Widget w c -> Listener () -> IO (IO ()) actionW w l = do rm <- fixIO $ \rm -> do op <- fireListener' l rm rm <- T.bindCommand w (op ()) return rm return $ rm instance Has_Action T.Button where action = actionW instance Has_Action T.MButton where action = actionW instance Has_Action T.MCheckbutton where action = actionW instance Has_Action T.MRadiobutton where action = actionW instance Has_Action T.Radiobutton where action = actionW instance Has_Action T.Checkbutton where action = actionW instance Has_Action T.Scale where action = actionW \end{code} Tcl-tk windows are window items \begin{code} instance WindowItem WindowPrim where showWindow = T.showWindow hideWindow = T.hideWindow iconifyWindow = T.iconifyWindow getWinPanel w = return $ Panel w \end{code} Panel items - Panel widgets are panel items, and can be added to boxes and grids, and removed from them. \begin{code} instance PanelItem (T.PWidget w) where gridAdd = gridAdd' boxAdd = boxAdd' raise a b = T.raise a (fmap getWPath' b) lower a b = T.lower a (fmap getWPath' b) \end{code} Tcl-tk frames are panels. \begin{code} instance IsPanel T.Frame where mkSubPanel w = do f <- T.frame w [] return $! Panel f setGridLayout frm = do return $! Grid frm setBoxLayout frm = do ref <- newIORef PackAbove elts <- newIORef [] return $! Box frm ref elts \end{code} It's also useful to make tcl-tk windows panels; this allows us to write getWinPanel without making a child frame, thereby allowing us to have just one winpanel. This is a bit of a dodgy hack as it means we have to make tcl-tk windows into panel items. to do: rethink. \begin{code} instance PanelItem WindowPrim where gridAdd _ _ _ _ = return (return ()) boxAdd _ _ _ _ = return (return ()) raise _ _ = return () lower _ _ = return () instance IsPanel WindowPrim where mkSubPanel w = do f <- T.frame w [] return $! Panel f setGridLayout w = do return $! Grid w setBoxLayout w = do ref <- newIORef PackAbove elts <- newIORef [] return $! Box w ref elts \end{code} How do we actually translate actions and User actions into tcl-tk terminology. We turn the action into a tcl-tk specific data using wrapWC. We then turn the listener into an IO action and bind the widget to fire the IO action. The function fireListener' expects a remove action which is the unregister action for the widget binding. \begin{code} bindWidget :: T.Widget a b -> Action -> Listener UserAction -> IO (IO ()) bindWidget = bindWidget' T.bindArgs bindWidget' :: (T.Widget a b -> (Bool,Bool,String,String) -> ([String] -> IO ()) -> IO (IO ())) -> T.Widget a b -> Action -> Listener UserAction -> IO (IO ()) bindWidget' _ w AskDestroy l = do tellop <- fireListener l addFinaliserW w (tellop Destroy) return (return ()) bindWidget' bindArgs w ask act = do let (ask'@(_,_,s,_),op') = wrapWC ask rm <- fixIO $ \rm -> do tellop <- fireListener' act rm rm <- bindArgs w ask' (runBC tellop op') return $ rm return rm runBC :: (UserAction -> IO ()) -> ([String] -> Maybe UserAction) -> [String] -> IO () runBC tell op xs = do case op xs of Nothing -> return () Just v -> tell v \end{code} We use wrapWC to convert an action into Tcl-tk specific bind data, and to get back a function to turn the widget specific data into a User Action. The Tcl-tk specific data means the following: 1 The first boolean argument will normally be false, indicating that the event should be processed as normal. When the flag is True, after the callback has been invoked to service the event, no further event processing will take place. 2 The second boolean causes the callback to be added to other bindings for the widget when True. 3 The first String argument is the name of the event we're binding to. 4 The second String argument given access to the the Tcl-tk % mechanism. This allows us to access the parameters from the user input, such as the mouse coordinates when listening to mouse motion. \begin{code} wrapWC :: Action -> ((Bool,Bool,String,String),[String] -> Maybe UserAction) wrapWC (AskButton n b mods) = ((False,True, "<" ++ showMods mods ++ "Button" ++ (if b then "Press-" else "Release-" ) ++ show n ++ ">", ""), const $ Just (Button n b mods)) wrapWC (AskMotion mods) = ((False,True,"<" ++ showMods mods ++ "Motion>","xy"), f') where f' [x,y] = Just $ MouseMove (worldToPoint2 (xx,yy)) mods where xx = T.parseInt x yy = T.parseInt y f' _ = Nothing wrapWC AskDestroy = ((False,True,"",""),const $ Just Destroy) wrapWC (AskEnter mods) = ((False,True,"<" ++ showMods mods ++ "Enter>",""),const $ Just $ Enter mods) wrapWC (AskLeave mods) = ((False,True,"<" ++ showMods mods ++ "Leave>",""),const $ Just $ Leave mods) wrapWC (AskKey b mbk mods) = ((False,False, "<" ++ showMods mods ++ "Key" ++ (if b then "Press" else "Release") ++ (case mbk of Nothing -> ">" Just c' -> "-"++showKey c'++">"), "K"), f') where f' [s] = Just $ CharKey b (fromKey s) mods f' _ = Nothing wrapWC (AskAction s nm) = ((False,True,"<" ++ s ++ ">",nm), f) where f [s'] = Just $ ExtAction s s' f _ = Nothing wrapWC AskResize = ((False,True,"","wh"),f') where f' [w,h] = Just $ Resize (worldToVector2' w h) f' _ = Nothing wrapWC _ = ((False,False,"",""),const Nothing) \end{code} How do we actually add tcl-tk panel widgets to grids \begin{code} gridAdd' :: T.PWidget w -> Grid -> (Int,Int) -> [GridBagConstraint] -> IO (IO ()) gridAdd' w (Grid p) (x,y) cs = do T.gridAdd w ([T.Tcl_Any "in" (getWPath p), T.Tcl_Any "row" (show y), T.Tcl_Any "column" (show x)] ++ map fromGridBagConstraint cs) return $ T.gridForget w fromGridBagConstraint :: GridBagConstraint -> T.TclPack fromGridBagConstraint (WidthX x) = T.Tcl_Any "columnspan " (show x) fromGridBagConstraint (WidthY y) = T.Tcl_Any "rowspan " (show y) fromGridBagConstraint (GridPad (PadX x)) = T.Tcl_Any "padx" (show x) fromGridBagConstraint (GridPad (PadY x)) = T.Tcl_Any "pady" (show x) fromGridBagConstraint (GridPadInternal (PadX x)) = T.Tcl_Any "ipadx" (show x) fromGridBagConstraint (GridPadInternal (PadY x)) = T.Tcl_Any "ipady" (show x) fromGridBagConstraint (GridAnchor a) = T.Tcl_Any "sticky" (fromAnchor a) fromGridBagConstraint (GridFill FillX) = T.Tcl_Any "sticky" "we" fromGridBagConstraint (GridFill FillY) = T.Tcl_Any "sticky" "ns" fromGridBagConstraint (GridFill FillXY) = T.Tcl_Any "sticky" "nswe" \end{code} How do we actually add tcl-tk panel widgets to boxes. We work out the location of the element in the box, in particular to calculate this we need to know what is at the head of the list of items in the box. To put an object at the front. We then update the list with this element. \begin{code} boxAdd' :: T.PWidget a -> Box -> PlacePos Ident -> [PackInfo] -> IO (IO ()) boxAdd' w (Box p ref eltsRef) pos pinfo = do let uid = uniqueId w let pth = getWPath' uid let gui = T.getTclGUIInfo w elts <- readIORef eltsRef let hd = case elts of [] -> Nothing (x:xs) -> Just $ getWPath' x writeIORef eltsRef $ insertElt uid pos elts pm <- readIORef ref T.packAdd w (T.Tcl_Any "in" (getWPath p): fromPlacePos hd pm pos ++ map fromPack pinfo) return $ T.packForget w insertElt :: Ident -> PlacePos Ident -> [Ident] -> [Ident] insertElt p PlaceTop xs = p:xs insertElt p PlaceBottom xs = xs ++ [p] insertElt p (PlaceBefore x) xs = before x p xs where before x p [] = [p] before x p (id:as) = if id == x then p:id:as else id:before x p as insertElt p (PlaceAfter x) xs = after x p xs where after x p [] = [p] after x p (id:as) = if id == x then id:p:as else id:after x p as \end{code} Now we'll go on to define a set of functions to translate from the WH data types into tcl-tk land get the widget path from a widget item and more specifically from its unique id. \begin{code} getWPath :: WidgetItem w => w -> String getWPath w = getWPath' $ uniqueId w getWPath' :: Ident -> String getWPath' (IdentS s) = s \end{code} Turn a place position and pack mode into tcl-tk pack arguments. If at the top place before the head of the list; if at the bottom place it (by default it appears at the end); otherwise place before or after the given element. \begin{code} fromPlacePos :: Maybe T.WPath -> PackMode -> PlacePos Ident -> [T.TclPack] fromPlacePos _ pm (PlaceBefore i) = [fromPackMode pm,T.Tcl_Any "before" $ getWPath' i] fromPlacePos _ pm (PlaceAfter i) = [fromPackMode pm,T.Tcl_Any "after" $ getWPath' i] fromPlacePos (Just w) pm PlaceTop = [fromPackMode pm, T.Tcl_Any "before" w] fromPlacePos _ pm _ = [fromPackMode pm] fromPackMode :: PackMode -> T.TclPack fromPackMode PackAbove = T.Tcl_Any "side" "top" fromPackMode PackBeside = T.Tcl_Any "side" "left" \end{code} Translate packinfo into tcl-tk \begin{code} fromPack :: PackInfo -> T.TclPack fromPack (PackFill f) = T.Tcl_Any "fill" (fromFill f) fromPack (PackExpand True) = T.Tcl_Any "expand" "1" fromPack (PackExpand False) = T.Tcl_Any "expand" "0" fromPack (PackFree s s2) = T.Tcl_Any s s2 fromPack (PackAnchor a) = T.Tcl_Any "anchor" (fromAnchor a) fromPack (PackPad (PadX x)) = T.Tcl_Any "padx" (show x) fromPack (PackPad (PadY y)) = T.Tcl_Any "pady" (show y) fromPack (PackPadInternal (PadX x)) = T.Tcl_Any "ipadx" (show x) fromPack (PackPadInternal (PadY y)) = T.Tcl_Any "ipady" (show y) \end{code} likewise fill data \begin{code} fromFill :: Fill -> String fromFill FillX = "x" fromFill FillY = "y" fromFill FillXY = "both" \end{code} likewise anchors \begin{code} fromAnchor :: Anchor -> String fromAnchor N = "n" fromAnchor S = "s" fromAnchor E = "e" fromAnchor W = "w" fromAnchor NE = "ne" fromAnchor NW = "nw" fromAnchor SE = "se" fromAnchor SW = "sw" fromAnchor C = "c" \end{code} Turn a tcl-tk string into and back from the Key data type. \begin{code} fromKey :: String -> Key fromKey "Return" = Return fromKey "Escape" = Escape fromKey "Tab" = Tab fromKey "Caps_Lock" = Caps fromKey "Shift_L" = Shift fromKey "Control_L" = Control fromKey "Alt_L" = Alt fromKey "space" = Space fromKey "App" = App fromKey "Left" = CursorLeft fromKey "Right" = CursorRight fromKey "Down" = CursorDown fromKey "Up" = CursorUp fromKey "Next" = Next fromKey "Prior" = Prior fromKey "Delete" = Delete fromKey "Insert" = Insert fromKey "Home" = Home fromKey "End" = End fromKey "BackSpace" = BackSpace fromKey [c] = KeyChar c fromKey ('F':xs) = F $ read xs fromKey "equal" = KeyChar '=' fromKey "minus" = KeyChar '-' fromKey "parenright" = KeyChar ')' fromKey "parenleft" = KeyChar '(' fromKey "asterisk" = KeyChar '*' fromKey "ampersand" = KeyChar '&' fromKey "asciicircum" = KeyChar '^' fromKey "percent" = KeyChar '%' fromKey "dollar" = KeyChar '$' fromKey "quotedlbl" = KeyChar '"' fromKey "braceleft" = KeyChar '[' fromKey "braceright" = KeyChar ']' fromKey "colon" = KeyChar ':' fromKey "at" = KeyChar '@' fromKey "asciitilde" = KeyChar '~' fromKey "question" = KeyChar '?' fromKey "greater" = KeyChar '>' fromKey "less" = KeyChar '<' fromKey "slash" = KeyChar '/' fromKey "period" = KeyChar '.' fromKey "comma" = KeyChar ',' fromKey "semicolon" = KeyChar ';' fromKey "quoteright" = KeyChar '\'' fromKey "numbersign" = KeyChar '#' fromKey "bracketright" = KeyChar ']' fromKey "bracketleft" = KeyChar '[' fromKey "quoteleft" = KeyChar '`' fromKey "apostrophe" = KeyChar '\'' fromKey "bar" = KeyChar '|' fromKey "plus" = KeyChar '+' fromKey "underscore" = KeyChar '_' fromKey "backslash" = KeyChar '\\' fromKey "exclam" = KeyChar '!' fromKey "quotedbl" = KeyChar '"' fromKey "sterling" = KeyChar '\156' fromKey "grave" = KeyChar '`' fromKey "notsign" = KeyChar '\170' fromKey s = Key s showKey :: Key -> String showKey Return = "Return" showKey Escape = "Escape" showKey Tab = "Tab" showKey Caps = "Caps_Lock" showKey Shift = "Shift_L" showKey Control = "Control_L" showKey Alt = "Alt_L" showKey Space = "space" showKey App = "App" showKey CursorLeft = "Left" showKey CursorDown = "Down" showKey CursorRight = "Right" showKey CursorUp = "Up" showKey Next = "Next" showKey Prior = "Prior" showKey Delete = "Delete" showKey Insert = "Insert" showKey Home = "Home" showKey End = "End" showKey BackSpace = "BackSpace" showKey (F x) = "F" ++ show x showKey (KeyChar c) = show c showKey (Key s) = s \end{code} Handle the Modifier type in tcl-tk \begin{code} showMod :: Modifier -> String showMod (AMod s) = s showMod DoubleM = "Double" showMod ShiftM = "Shift" showMod TripleM = "Triple" showMod AltM = "Alt" showMod ControlM = "Control" showMod (ButtonPressedM n) = "Button" ++ show n showMod m = show m showMods :: [Modifier] -> String showMods [] = "" showMods (x:xs) = concat $ fmap (\x -> x ++ "-") $ map showMod (x:xs) \end{code} helper function to compose one list of configs over another \begin{code} overideCfgs :: [Config] -> [Config] -> [Config] overideCfgs cs1 cs2 = unionBy T.sameConfig cs1 cs2 \end{code} translate colors into tcl-tk rgb. Fran uses values in the range 0-1, tcl-tk in the range 0 - 255 so convert. \begin{code} fromColor :: Color -> String fromColor (ColorRGB r g b) = T.tcl_string $ T.rgb (f r) (f g) (f b) where f v = round (v * 255) \end{code} bitmaps, currently just named bitmaps are valid \begin{code} data Bitmap = Bitmap String deriving Eq fromBitmap :: Bitmap -> String fromBitmap (Bitmap s) = T.tcl_string s namedBitmap = Bitmap \end{code} fonts, currently named fonts with a point size and some style attributes \begin{code} data Font = Font String deriving Eq fromFont :: Font -> String fromFont (Font s) = T.tcl_string s namedFont :: String -> Int -> [FontStyle] -> Font namedFont a b c = Font $ a ++ " " ++ show b ++ " " ++ (unwords $ map showFont c) data FontStyle = Bold | Italic | Underline | Overstrike | Roman deriving Eq showFont Bold = "bold" showFont Italic = "italic" showFont Underline = "underline" showFont Overstrike = "overstrike" showFont Roman = "roman" \end{code} cursors - only named cursors so far \begin{code} data Cursor = Cursor String deriving Eq fromCursor :: Cursor -> String fromCursor (Cursor s) = T.tcl_string s namedCursor = Cursor \end{code} only named images so far \begin{code} data Image = Image String deriving Eq fromImage :: Image -> String fromImage (Image s) = s namedImage = Image \end{code} turn select mode into tcl-tk \begin{code} fromSelectMode :: SelectMode -> String fromSelectMode SingleMode = "single" fromSelectMode BrowseMode = "browse" fromSelectMode MultipleMode = "multiple" fromSelectMode ExtendedMode = "extended" \end{code} turn justify into tcl-tk \begin{code} fromJustify :: Justify -> String fromJustify LeftJ = "left" fromJustify RightJ = "right" fromJustify CenterJ = "center" \end{code} turn relief into tcl-tk \begin{code} fromRelief :: Relief -> String fromRelief Raised = "raised" fromRelief Sunken = "sunken" fromRelief Flat = "flat" fromRelief Ridge = "ridge" fromRelief Solid = "solid" fromRelief Groove = "groove" \end{code} translate active state \begin{code} fromActiveState Active = "active" fromActiveState Disabled = "disabled" fromActiveState Normal = "normal" \end{code} translate rectangular regions \begin{code} fromRegion :: Rect -> String fromRegion (RectLLUR (Point2XY llx lly) (Point2XY urx ury)) = show llx ++ " " ++ show lly ++ " " ++ show urx ++ " " ++ show ury ++ " " \end{code} translate scroll units into tcl \begin{code} fromScroll :: ScrollUnits -> T.ScrollUnit fromScroll (FullScreens x) = T.ScrollPages x fromScroll (Units x) = T.ScrollUnits x \end{code} finally translate wrap arguments \begin{code} fromWrap :: Wrap -> String fromWrap NoWrap = "none" fromWrap CharWrap = "char" fromWrap WordWrap = "word" \end{code} Some coordinate helper functions to translate to and from points and vectors to tcl-tk land \begin{code} pointToWorld :: Point2 -> (Int,Int) pointToWorld (Point2XY x y) = (round x,round y) vectorToWorld :: Vector2 -> (Int,Int) vectorToWorld (Vector2XY v1 v2) = (round v1,round v2) pointToWorld' :: Point2 -> String pointToWorld' (Point2XY x y) = (round x `joinInts` round y) vectorToWorld' :: Vector2 -> String vectorToWorld' (Vector2XY v1 v2) = (round v1 `joinInts` round v2) worldToPoint2 :: (Int,Int) -> Point2 worldToPoint2 (x,y) = point2XY (fromInt x) (fromInt y) worldToVector2' :: String -> String -> Vector2 worldToVector2' x y = Vector2XY (fromInt $ T.parseInt x) (fromInt $ T.parseInt y) worldToPoint2' :: String -> String -> Point2 worldToPoint2' x y = Point2XY (fromInt $ T.parseInt x) (fromInt $ T.parseInt y) joinInts :: Int -> Int -> String joinInts x y = show x ++ " " ++ show y \end{code}