module TclPrimWidgets where

-- This module defines each of the individual TclHaskell widgets that
-- comes with TclHaskell. This is the primitive stuff without type 
-- classes.

import Char
import List
import IO
import TclGUI
import TclPrimCore
import Monad
import Maybe
import IOExts
import Remover
import Utils

-- packing widgets

isIn :: [TclPack] -> Maybe TclPack
isIn ps = find eq ps
 where
   eq (Tcl_Any "in" _) = True
   eq _ = False

handleIn :: [TclPack] -> WPath -> [String]
handleIn ps me = 
 case isIn ps of
  Just (Tcl_Any "in" x) | parentWPath' me /= x -> [";raise",me,x]
  _ -> []

gridAdd :: PWidget a -> [TclPack] -> IO ()
gridAdd w pi = 
 tclW_ w $ \w -> ["grid",w] ++ fmap fromTclPack pi ++
		 handleIn pi w
  
gridForget :: PWidget a -> IO ()
gridForget w = tclW_ w $ \w -> ["grid forget",w] 

packAdd :: PWidget a -> [TclPack] -> IO ()
packAdd w pi = tclW_ w $ \w -> ["pack",w] ++ fmap fromTclPack pi ++
			       handleIn pi w

packForget :: PWidget a -> IO ()
packForget w = tclW_ w $ \w -> ["pack forget ",w]

fromTclPack :: TclPack -> String
fromTclPack (Tcl_Any "" "") = ""
fromTclPack (Tcl_Any s1 s2) = "-" ++ s1 ++ " " ++ s2

data TclPack = Tcl_Any String String
 deriving Eq

-- Events ----------------------------------------------------

bind ::  (w -> (Bool,Bool,TkEvent,String) -> ([String] -> m ()) 
          -> m a)
     -> w -> TkEvent -> m () -> m a
bind bindArgs wgt e a = bindArgs wgt (False,False,e,"") (\_ -> a)

bindxy :: (w -> (Bool,Bool,TkEvent,String) -> ([String] -> m ()) 
           -> m a)
       -> w -> TkEvent -> ((Int,Int)-> m ()) -> m a
bindxy bindArgs wgt ev f = bindCoords bindArgs "xy" wgt ev f

bindXY ::  (w -> (Bool,Bool,TkEvent,String) -> ([String] -> m ()) 
            -> m a)
       -> w -> TkEvent -> ((Int,Int)-> m ()) -> m a
bindXY bindArgs wgt ev f = bindCoords bindArgs "XY" wgt ev f

bindCoords :: (w -> (Bool,Bool,TkEvent,String) -> ([String] -> m ()) 
               -> m a)
           -> String -> w -> TkEvent -> ((Int,Int)-> m ()) 
	   -> m a
bindCoords bindArgs prm_str wgt ev f = 
  bindArgs wgt (False,False,ev,prm_str)  (f . parseCoords)

parseCoords :: [String] -> Coord
parseCoords [x,y] = (parseInt x,parseInt y)
parseCoords _ = error "parse coords"

-- dialog boxes

getOpenFileName :: TclGUI (Maybe FilePath)
getOpenFileName gui =
     do fn <- tcl gui ["tk_getOpenFile"]
        if null fn
           then return Nothing
           else return (Just fn)

getSaveFileName :: TclGUI (Maybe FilePath)
getSaveFileName gui = do
        fn <- tcl gui ["tk_getSaveFile"]
        if null fn
           then return Nothing
           else return (Just fn)

-- Window --------------------------------------------

type Window = WWidget Win
data Win    = TkTop

data Geometry =
	WinSz   (Int,Int)	    | -- size
	WinPn   (Int,Int)	    | -- position
	WinSzPn (Int,Int) (Int,Int)   -- size position
 deriving Eq
rootWin :: TclGUI Window
rootWin = totcl_wwidget "." TkTop

window' :: WPath -> [Config] -> TclGUI Window
window' nm cfgs = tcl_wwidget nm cfgs "toplevel" TkTop

window :: [Config] -> TclGUI Window
window cfgs gui = do
     do nm <- tcl_newWgtName gui
	window' nm cfgs gui

title :: Window -> String -> IO ()
title w s = tclW_ w $ \w -> ["wm title",w,tcl_string s]

geometry :: Window -> Geometry -> IO ()
geometry w g = tclW_ w $ \w -> ["wm geometry",w,geometry_string g]

geometry_string :: Geometry -> String
geometry_string (WinSz (x,y)) =        show x ++ "x" ++ show y
geometry_string (WinPn (x,y)) = "+" ++ show x ++ "+" ++ show y
geometry_string (WinSzPn sz pn) =
		geometry_string (WinSz sz) ++ geometry_string (WinPn pn)


showWindow,hideWindow,iconifyWindow :: Window -> IO ()
showWindow t = tclW_ t $ \t -> ["wm deiconify",t]
hideWindow t = tclW_ t $ \t -> ["wm withdraw",t]
iconifyWindow t = tclW_ t $ \t -> ["wm iconify",t]

getPointerLoc :: Window -> IO Coord
getPointerLoc w = fmap (parseCoords . words) $ tclW w $ \w -> ["winfo pointerxy",w]

getRootPointerLoc :: TclGUI Coord
getRootPointerLoc g = fmap (parseCoords . words) $ tcl g ["winfo pointerxy ."]

-- Trapping Window Deletions ---------------------------

trapDeleteWindow :: Window -> IO () -> IO ()
trapDeleteWindow top p =
     do (tcl_cb,rm) <- tcl_callback gui "" (\_->p)
	addFinaliserW top rm
	tcl_ gui ["wm protocol", wpath top, "WM_DELETE_WINDOW", tcl_cb]
  where
    gui = getTclGUIInfo top


mkPWidget :: (WPath -> [Config] -> TclGUI w) 
	  -> Widget a b 
	  -> [Config]
	  -> IO w
mkPWidget f t cs = do
 let gui = getTclGUIInfo t
 nm <- mkChildOf t
 f nm cs gui
 
-- Message ---------------------------------------------

type Message = PWidget Messg
data Messg = TkMessg

message' :: WPath -> [Config] -> TclGUI Message
message' nm cs = tcl_pwidget nm cs "message" TkMessg

message :: Window -> [Config] -> IO Message
message = mkPWidget message'
 

-- Menu ------------------------------------------------
 
-- type Menu = WWidget Men	-- see TclCore
-- data Men  = TkMen
 
menu' :: WPath -> [Config] -> TclGUI Menu
menu' nm cfgs = tcl_wwidget nm cfgs' "menu" TkMen
 where
 cfgs' = case find (sameConfig c) cfgs of
          Nothing -> (c:cfgs)
          Just _ -> cfgs 
 c = "tearoff" %% "False"

menu :: Widget a b -> [Config] -> IO Menu
menu w cfgs = do
 nm <- mkChildOf w
 menu' nm cfgs gui
  where 
   gui = getTclGUIInfo w

popup :: Menu -> (Int,Int) -> IO ()
popup w (x,y) = tclW_ w $ \w -> ["haskell_popup",w,show x,show y]

popupM :: Menu -> IO ()
popupM m = tclW_ m $ \m -> [m,"post [winfo pointerx .] [winfo pointery .]"]

popdown :: Menu -> IO ()
popdown w = tclW_ w $ \w -> [w,"unpost"]

-- Menubutton ------------------------------------------------

type Menubutton  = PWidget MB
data MB = TkMB
 
menubutton' :: WPath -> Maybe WPath -> [Config] -> TclGUI Menubutton
menubutton' nm ext cs = tcl_pwidget nm (mc ++ cs) "menubutton" TkMB
      where
	mc = maybe [] (\x -> ["menu" %% x]) ext

menubutton :: Window -> [Config] -> IO Menubutton
menubutton win cs = 
 mkPWidget (\path cs -> menubutton' path Nothing cs) win cs

-- Making menu item -----------------------------------------

mkMWidget :: String -> w
          -> Menu -> [Config] -> Int -> IO (MWidget w)
mkMWidget s w m cs i = tcl_mwidget m cs s w i 
 
-- Cascade ---------------------------------------------------

type Cascade     = MWidget CB
data CB = TkCB
  
cascade :: Menu -> Menu -> [Config] -> Int -> IO Cascade
cascade w w' cs = mkMWidget "cascade" TkCB w (mc:cs) 
      where
	mc = "menu" %% (wpath w')

-- MButton ------------------------------------

type MButton = MWidget MBut
data MBut    = TkMBut
 
mbutton :: Menu -> [Config] -> Int -> IO MButton
mbutton = mkMWidget "command" TkMBut


-- MCheckbutton --------------------------------
 
type MCheckbutton = MWidget MChe
data MChe         = TkMChe
 
mcheckbutton :: Menu -> [Config] -> Int -> IO MCheckbutton
mcheckbutton w cs x =
     do c <- mkMWidget "checkbutton" TkMChe w cs x
	cset' c ["offvalue" %% "False",
		 "onvalue"  %% "True",
		 "variable" %% wtag c]
     	return c

getMCheck :: MCheckbutton -> IO Bool
getMCheck w =
     do s <- tclW w $ const [ "set", wtag w ]
	case s of
	  "True" -> return True
	  _      -> return False
 
setMCheck :: MCheckbutton -> Bool -> IO ()
setMCheck w b = tclW_ w $ const [ "set", wtag w, show b ]

varMCheck :: MCheckbutton -> String
varMCheck = wtag

-- Separator -------------------------------------------------
 
type Separator  = MWidget Sep
data Sep = TkSep
  
separator :: Menu -> Int -> IO Separator
separator w n = mkMWidget "separator" TkSep w [] n

 
-- MRadiobutton ---------------------------------

type MRadiobutton = MWidget MRB
data MRB          = TkMRB
 
mradiobutton :: Menu -> [Config] -> Int -> IO MRadiobutton
mradiobutton = mkMWidget "radiobutton" TkMRB


-- Radio -----------------------------------------------

data Radio = TkRad String GUIInfo (IORef [String])

radio :: [Radiobutton] -> TclGUI Radio
radio = tcl_radio

mradio :: [MRadiobutton] -> TclGUI Radio
mradio = tcl_radio

tcl_radio :: [Widget c w] -> TclGUI Radio
tcl_radio ws gui = do
     do	n <- tcl_newName gui 
	ref <- newIORef (map wtag ws)
	let nm = "haskell_radio-"++show n
	    rd = TkRad nm gui ref
	mapM_ (su nm) ws
	return rd
      where
	su nm w = cset' w ["variable" %% nm,"value" %% wtag w]

removeRadio :: Radio -> Widget a b -> IO ()
removeRadio (TkRad nm gui ref) r = do
 cset' r ["variable" %% ""]
 updIORef ref (\xs -> delete (wtag r) xs)

appendRadio :: Radio -> Widget a b -> IO ()
appendRadio (TkRad nm gui ref) r = do
 cset' r ["variable" %% nm,"value" %% wtag r]
 updIORef ref (\xs -> xs ++ [wtag r])

setRadio :: Radio -> Int -> IO ()
setRadio (TkRad nm gui ref) n = do
 xs <- readIORef ref
 if n + 1 > length xs then
   return ()
  else do
    tcl_ gui [ "set", nm, xs !! n]
    
getRadio :: Radio -> IO Int
getRadio (TkRad v gui ref) = do
 s <- tcl gui ["set",v]
 xs <- readIORef ref
 case findIndex (== s) xs of
  Nothing -> fail $ "Invalid radio element: " ++ s
  Just n -> return n

setRadio' :: Radio -> WTag -> IO ()
setRadio' (TkRad nm gui ref) t = tcl_ gui [ "set", nm, t]

getRadio' :: Radio -> IO WTag
getRadio' (TkRad v gui ref) = tcl gui ["set", v]

varRadio :: Radio -> String
varRadio (TkRad v _ ref) = v


-- Radiobutton ---------------------------------
 
type Radiobutton  = PWidget RB
data RB           = TkRB

radiobutton' :: WPath -> [Config] -> TclGUI Radiobutton
radiobutton' nm cs = tcl_pwidget nm cs "radiobutton" TkRB

radiobutton :: Window -> [Config] -> IO Radiobutton
radiobutton = mkPWidget radiobutton' 


-- Checkbutton --------------------------------
 
type Checkbutton = PWidget Che
data Che         = TkChe

checkbutton' :: WPath -> [Config] -> TclGUI Checkbutton
checkbutton' nm cs gui =
     do c <- tcl_pwidget nm cs "checkbutton" TkChe gui
	cset' c ["offvalue" %% "False",
		 "onvalue"  %% "True",
		 "variable" %% nm]
     	return c

checkbutton :: Window -> [Config] -> IO Checkbutton
checkbutton = mkPWidget checkbutton'

getCheck :: Checkbutton -> IO Bool
getCheck w =
     do s <- tclW w $ \w -> ["set", w ]
	case s of
	  "True" -> return True
	  _      -> return False
 
setCheck :: Checkbutton -> Bool -> IO ()
setCheck w b = tclW_ w $ \w -> [ "set", w, show b ]
 
varCheck :: Checkbutton -> String
varCheck w = wpath w

-- Button  ------------------------------------
 
type Button  = PWidget But
data But     = TkBut

button' :: WPath -> [Config] -> TclGUI Button
button' nm cs = tcl_pwidget nm (cs) "button" TkBut

button :: Window -> [Config] -> IO Button
button = mkPWidget button' 

-- Frame -----------------------------------------------
 
type Frame = PWidget Fra
data Fra   = TkFra
  
frame' :: WPath -> [Config] -> TclGUI Frame
frame' nm cs = tcl_pwidget nm (cs) "frame" TkFra

frame :: Widget a b -> [Config] -> IO Frame
frame = mkPWidget frame'

-- Label -----------------------------------------------
 
type Label = PWidget Lab
data Lab   = TkLab
 
label' :: WPath -> [Config] -> TclGUI Label
label' nm cs = tcl_pwidget nm (cs) "label" TkLab

label :: Window -> [Config] -> IO Label
label = mkPWidget label'

-- Scale -----------------------------------------------

type Scale = PWidget Sca
data Sca = TkSca

vscale', hscale' :: WPath -> [Config] -> TclGUI Scale
vscale' nm cs = tcl_pwidget nm cs' "scale" TkSca
      where
	cs'  = cs
hscale' nm cs = tcl_pwidget nm cs' "scale" TkSca
      where
	cs'  = ("orient" %% "horizontal"):cs

vscale,hscale :: Window -> [Config] -> IO Scale
vscale = mkPWidget vscale'
hscale = mkPWidget hscale'

getScale :: Scale -> IO Int
getScale w =
    do s <- tclW w $ \w -> [w,"get"]
       return (parseInt s)

setScale :: Scale -> Int -> IO ()
setScale w i = tclW_ w $ \w -> [w,"set", show i]

-- Scanable --------------------------------------------

scanMark :: Widget a b -> Int -> Int -> IO ()
scanMark w x y = tclW_ w $ \w -> [w,"scan mark",show x,show y]

scanDrag :: Widget a b -> Int -> Int -> IO ()
scanDrag w x y = tclW_ w $ \w -> [w,"scan dragto ",show x,show y]

-- Listbox ---------------------------------------------

type Listbox = PWidget Lis
data Lis     = TkLis 

listbox' :: WPath -> [Config] -> TclGUI Listbox
listbox' nm cs = tcl_pwidget nm cs "listbox" TkLis 

listbox :: Window -> [Config] -> IO Listbox
listbox = mkPWidget listbox'

data LIndex = LIndex Int | LIndexActive | LIndexAnchor | LIndexEnd 
            | LIndexAt ! Int ! Int | LFree String

showIndex :: LIndex -> String
showIndex (LIndex n) = show n
showIndex LIndexActive = "active"
showIndex LIndexAnchor = "anchor"
showIndex LIndexEnd = "end"
showIndex (LIndexAt x y) = "@" ++ show x ++ "," ++ show y

getListboxSelection :: Listbox -> IO [Int]
getListboxSelection w =
     do sel_str <- tclW w $ \w -> [w,"curselection"]
	return (map parseInt (words sel_str))

getListboxEntries :: Listbox -> LIndex -> LIndex -> IO [String]
getListboxEntries l x y = 
 fmap words $ tclW l $ \w -> [w,"get",showIndex x,showIndex y]

getListboxSize :: Listbox -> IO Int
getListboxSize l = fmap parseInt $ tclW l $ \w -> [w,"size"]

listboxScanMark :: Listbox -> Int -> Int -> IO ()
listboxScanMark w x y = tclW_ w $ \w -> [w,"scan mark",show x,show y]

listboxScanDrag :: Listbox -> Int -> Int -> IO ()
listboxScanDrag w x y = tclW_ w $ \w -> [w,"scan dragto ",show x,show y]

listboxMoveToSee :: Listbox -> LIndex -> IO ()
listboxMoveToSee l n = tclW_ l $ \w -> [w,"see",showIndex n]

insertListbox :: Listbox -> LIndex -> [String] -> IO ()
insertListbox l x xs = tclW_ l $ \w -> [w,"insert",showIndex x] ++ xs

deleteListbox :: Listbox -> LIndex -> LIndex -> IO ()
deleteListbox l x1 x2 = tclW_ l $ \w -> [w,"delete",showIndex x1,showIndex x2]

resetListbox :: Listbox -> [String] -> IO ()
resetListbox w ls =
     do tclW_ w $ \w -> [w,"delete","0","end;",w,"insert","end"] ++ ls

addListboxSelection :: Listbox -> LIndex -> LIndex -> IO ()
addListboxSelection l x1 x2 = 
 tclW_ l $ \w -> [w,"selection set",showIndex x1,showIndex x2]

clearListboxSelection :: Listbox -> LIndex -> LIndex -> IO ()
clearListboxSelection l x1 x2 = 
  tclW_ l $ \w -> [w,"selection clear",showIndex x1,showIndex x2]

setListboxSelectionAnchor :: Listbox -> LIndex -> IO ()
setListboxSelectionAnchor l x = 
  tclW_ l $ \w -> [w,"selection anchor",showIndex x]

-- Scrollable in X and Y

-- Scrollable Widget Class -----------------------------
{-
class (ScrollableX w,ScrollableY w) => Scrollable w 

class ScrollableX w where
 xview :: w -> IO (Double,Double)
 xviewSet :: w -> Int -> IO ()
 xMoveTo :: w -> Double -> IO ()
 xScroll :: w -> ScrollUnit -> IO ()
-}
data ScrollUnit = ScrollPages ! Int | ScrollUnits ! Int
 
fromScrollUnit :: ScrollUnit -> (String,Int)
fromScrollUnit (ScrollPages x) = ("pages",x)
fromScrollUnit (ScrollUnits x) = ("units",x)

showScrollUnit :: ScrollUnit -> String
showScrollUnit x =
 case fromScrollUnit x of
  (s,x) -> show x ++ " " ++ s
{-
class ScrollableY w where
 yview :: w -> IO (Double,Double)
 yviewSet :: w -> Int -> IO ()
 yMoveTo :: w -> Double -> IO ()
 yScroll :: w -> ScrollUnit -> IO ()
-}
xview_ :: Widget a b -> IO (Double,Double)
xviewSet_ :: Widget a b -> Int -> IO ()
xMoveTo_ :: Widget a b -> Double -> IO ()
xScroll_ :: Widget a b -> ScrollUnit -> IO ()

xview_ w = do
 s <- tclW w $ \w -> [w,"xview"]
 case words s of
  [a,b] -> return (read a,read b)
  _ -> fail $ "invalid return value for xview: " ++ s 

xviewSet_ w x = tclW_ w $ \w -> [w,"xview",show x]
xMoveTo_ w x = tclW_ w $ \w -> [w,"xview",show x]
xScroll_ w x = tclW_ w $ \w -> [w,"xview",showScrollUnit x]

yview_ :: Widget a b -> IO (Double,Double)
yviewSet_ :: Widget a b -> Int -> IO ()
yMoveTo_ :: Widget a b -> Double -> IO ()
yScroll_ :: Widget a b -> ScrollUnit -> IO ()

yview_ w = do
 s <- tclW w $ \w -> [w,"yview"]
 case words s of
  [a,b] -> return (read a,read b)
  _ -> fail $ "invalid return value for xview: " ++ s 
yviewSet_ w x = tclW_ w $ \w -> [w,"yview",show x]
yMoveTo_ w x = tclW_ w $ \w -> [w,"yview",show x]
yScroll_ w x = tclW_ w $ \w -> [w,"yview",showScrollUnit x]


-- Scrollbar -------------------------------------------
 
type Scrollbar = PWidget Scr
data Scr       = TkScr
 
scrollbar :: Window -> [Config] -> IO Scrollbar
scrollbar w cs = do
 nm <- mkChildOf w;scrollbar' nm cs (getTclGUIInfo w)

scrollbar' :: WPath -> [Config] -> TclGUI Scrollbar
scrollbar' nm cs = tcl_pwidget nm (cs) "scrollbar" TkScr

connectScr :: Bool -> WPath -> WPath -> TclGUI ()
connectScr isV scr wgt gui = do
 tcl_ gui [wgt, "configure -" ++ char ++ "scrollcommand {",scr,"set}"]
 tcl_ gui [scr,"configure -command {",wgt,char ++ "view}"]
   where
    char = if isV then "y" else "x"

vscroll' :: WPath -> PWidget w -> [Config] -> IO Scrollbar
vscroll' nm w cs =
     do v <- scrollbar' nm (cmd_c:cs) gui
	tcl_ gui [wnm, "configure -yscrollcommand {",nm,"set}"]
	return v
      where
	cmd_c = "command" %% ("{"++wnm++" yview}")
	wnm = wpath w
        gui = getTclGUIInfo w

hscroll' :: WPath -> PWidget w -> [Config] -> IO Scrollbar
hscroll' nm w cs =
     do h <- scrollbar' nm (ori_c:cmd_c:cs) gui
	tcl_ gui [wnm, "configure -xscrollcommand {",nm,"set}"]
	return h
      where
	ori_c = "orient" %% "horizontal"
	cmd_c = "command" %% ("{"++wnm++" xview}")
	wnm   = wpath w
	gui = getTclGUIInfo w

vscroll :: PWidget w -> [Config] -> IO Scrollbar
hscroll :: PWidget w -> [Config] -> IO Scrollbar
vscroll p cs = do nm <- mkSibling p;vscroll' nm p cs
hscroll p cs = do nm <- mkSibling p;hscroll' nm p cs
		  
 
-- Canvas ----------------------------------------------
 
type Canvas  = PWidget Can
data Can     = TkCan
 
canvas' :: WPath -> [Config] -> TclGUI Canvas
canvas' nm cs = tcl_pwidget nm (cs) "canvas" TkCan

canvas :: Window -> [Config] -> IO Canvas
canvas w cs = do nm <- mkChildOf w;canvas' nm cs (getTclGUIInfo w)

citem_canvas :: CWidget a -> IO Canvas
citem_canvas w@(TkWgt _ (TkC nm) _) = do
 totcl_pwidget nm TkCan (getTclGUIInfo w)

type Coord = (Int,Int)

mkCWidget :: String -> w -> Canvas -> [CCoord] -> [Config] -> IO (CWidget w)
mkCWidget s w c crds cs = tcl_cwidget c crds cs s w

-- CImage -------------------------------------------------------

type CImage = CWidget CIm
data CIm = TkCIm

type Image = String

cimage :: Canvas -> Coord -> [Config] -> IO CImage
cimage c crd = cimage' c (ccoord crd)

cimage' :: Canvas -> CCoord -> [Config] -> IO CImage
cimage' c crd = mkCWidget "image" TkCIm c [crd]

-- COval -------------------------------------------------------
 
type COval = CWidget COva
data COva  = TkCOva
 
coval :: Canvas -> Coord -> Coord -> [Config] -> IO COval
coval c crd1 crd2  = coval' c (ccoord crd1) (ccoord crd2)

coval' :: Canvas -> CCoord -> CCoord -> [Config] -> IO COval
coval' c crd1 crd2  = mkCWidget "oval" TkCOva c [crd1,crd2]

-- CLine ------------------------------------------------------
 
type CLine = CWidget CLin
data CLin  = TkCLin
   
cline :: Canvas -> [Coord] -> [Config] -> IO CLine
cline c cs cfgs = cline' c (map ccoord cs) cfgs

cline' :: Canvas -> [CCoord] -> [Config] -> IO CLine
cline' = mkCWidget "line" TkCLin

-- CRectangle --------------------------------------------
 
type CRectangle = CWidget CRec
data CRec       = TkCRec
 
crectangle :: Canvas -> Coord -> Coord -> [Config] -> IO CRectangle
crectangle c crd1 crd2 = crectangle' c (ccoord crd1) (ccoord crd2)

crectangle' :: Canvas -> CCoord -> CCoord -> [Config] -> IO CRectangle
crectangle' c crd1 crd2 = mkCWidget "rectangle" TkCRec c [crd1,crd2]

-- CArc --------------------------------------------------
 
type CArc = CWidget CAr
data CAr  = TkCAr
 
carc :: Canvas -> Coord -> Coord -> [Config] -> IO CArc
carc c crd1 crd2 = carc' c (ccoord crd1) (ccoord crd2)

carc' :: Canvas -> CCoord -> CCoord -> [Config] -> IO CArc
carc' c crd1 crd2 = mkCWidget "arc" TkCAr c [crd1,crd2]

 
-- CPoly -------------------------------------------------
 
type CPoly = CWidget CPol
data CPol  = TkCPol
 

cpoly :: Canvas -> [Coord] -> [Config] -> IO CPoly
cpoly c cs cfgs = cpoly' c (map ccoord cs) cfgs

cpoly' :: Canvas -> [CCoord] -> [Config] -> IO CPoly
cpoly'  = mkCWidget "polygon" TkCPol 

-- CText -----------------------------------------------------
 
type CText = CWidget CTex
data CTex  = TkCTex
 
ctext :: Canvas -> Coord -> [Config] -> IO CText
ctext c crd = ctext' c (ccoord crd) 

ctext' :: Canvas -> CCoord -> [Config] -> IO CText
ctext' c crd = mkCWidget "text" TkCTex c [crd]

-- CBitmap ---------------------------------------------------
 
type CBitmap = CWidget CBit
data CBit    = TkCBit
 
cbitmap :: Canvas -> Coord -> [Config] -> IO CBitmap
cbitmap c crd = cbitmap' c (ccoord crd)

cbitmap' :: Canvas -> CCoord -> [Config] -> IO CBitmap
cbitmap' c crd = mkCWidget "bitmap" TkCBit c [crd]


-- CWindow ---------------------------------------------------
 
type CWindow = CWidget CWin
data CWin = TkCWin
 

cwindow :: Canvas -> Coord -> PWidget w -> [Config] -> IO CWindow
cwindow c crd = cwindow' c (ccoord crd)

cwindow' :: Canvas -> CCoord -> PWidget w -> [Config] -> IO CWindow
cwindow' c crd w cs = mkCWidget "window" TkCWin c [crd] cs'
 where
   cs' = ("window" %% wpath w):cs

-- Entry -----------------------------------------------

type Entry = PWidget Ent
data Ent = TkEnt

entry' :: WPath -> [Config] -> TclGUI Entry
entry' nm cs = tcl_pwidget nm (cs) "entry" TkEnt

entry :: Window -> [Config] -> IO Entry
entry w cs = do nm <- mkChildOf w;entry' nm cs (getTclGUIInfo w)

getEntry :: Entry -> IO String
getEntry w = tclW w $ \w -> [w,"get"]

setEntry :: Entry -> String -> IO()
setEntry w s = 
 tclW_ w $ \w -> [w,"delete 0 end;", w,"insert 0",tcl_string s]

insertEntry :: Entry -> EIndex -> String -> IO ()
insertEntry e t s = 
 tclW_ e $ \w -> [w,"insert",showEIndex t,tcl_string s]

deleteEntry :: Entry -> EIndex -> EIndex -> IO ()
deleteEntry e t1 t2 = tclW_ e $ \w -> [w,"delete",showEIndex t1,showEIndex t2]

setICursor :: Entry -> EIndex -> IO ()
setICursor e t = tclW_ e $ \w -> [w,"icursor"]

data EIndex = EIndex Int | EIndexSelStart | EIndexSelEnd | EIndexAnchor 
	    | EIndexEnd | EIndexAt ! Int ! Int | EFree String

showEIndex :: EIndex -> String
showEIndex (EIndex n) = show n
showEIndex EIndexSelStart = "sel.first"
showEIndex EIndexSelEnd = "sel.last"
showEIndex EIndexAnchor = "anchor"
showEIndex EIndexEnd = "end"
showEIndex (EIndexAt x y) = "@" ++ show x ++ "," ++ show y

clearEntrySelection :: Entry -> IO ()
clearEntrySelection e = tclW_ e $ \w -> [w,"selection clear"]

setEntrySelectionAnchor :: Entry -> EIndex -> IO ()
setEntrySelectionAnchor e t = tclW_ e $ \w -> [w,"selection from",showEIndex t]

isEntrySelected :: Entry -> IO Bool
isEntrySelected e =  do
 b <- tclW e $ \w -> [w,"selection present"]
 case b of
  "1" -> return True
  _ -> return False

setEntrySelection :: Entry -> EIndex -> EIndex -> IO ()
setEntrySelection e t1 t2 = 
 tclW_ e $ \w -> [w,"selection range",showEIndex t1,showEIndex t2]

adjustEntrySelection :: Entry -> EIndex -> IO ()
adjustEntrySelection e t = tclW_ e $ \w -> [w,"selection adjust",showEIndex t]

setToEntrySelection :: Entry -> EIndex -> IO ()
setToEntrySelection e t = tclW_ e $ \w -> [w,"selection to",showEIndex t]

-- instance ScrollableX
  
tclToBool :: String -> Bool
tclToBool "1" = True
tclToBool _ = False

-- Edit ------------------------------------------------
 
type Edit = PWidget Edi
data Edi = Edi
 
isReadOnly :: PWidget a -> IO Bool
isReadOnly ed = do
 fmap get $ cget' ed ("state" %% "")
  where
    get "disabled" = True
    get "normal" = False
    get "" = False

doinsert :: PWidget a -> IO () -> IO ()
doinsert e act = do
 b <- isReadOnly e
 if b then do
   cset' e ["state" %% "normal"]
   act
   cset' e ["state" %% "disabled"]
  else
   act

edit' :: WPath -> [Config] -> TclGUI Edit
edit' nm cs = tcl_pwidget nm (cs) "text" Edi
 
edit :: Window -> [Config] -> IO Edit
edit w cs = do nm <- mkChildOf w;edit' nm cs (getTclGUIInfo w)

data TIndex = TIndex ! Int ! Int -- Line and Char
	    | TIndexAt ! Int ! Int
	    | TIndexEnd 	  
	    | TIndexMark ! MarkId
	    | TIndexTagFirst ! TagId
	    | TIndexTagLast ! TagId  
	    | TIndexEmbeddedWin ! WPath
	    | TFree String
            | TModMove TIndex ModMove
 deriving (Eq,Show)
	    
data ModMove = LineStart | LineEnd | WordStart | WordEnd 
             | ModChars Int | ModLines Int
 deriving (Eq,Show)

showTIndex' :: TIndex -> String
showTIndex' (TIndex x y) = show x ++ "." ++ show y
showTIndex' (TIndexAt x y) = "@" ++ show x ++ "," ++ show y
showTIndex' TIndexEnd = "end"
showTIndex' (TIndexMark s) = s
showTIndex' (TIndexTagFirst tg) = tg ++ ".first"
showTIndex' (TIndexTagLast tg) = tg ++ ".last"
showTIndex' (TIndexEmbeddedWin w) = w
showTIndex' (TModMove ti m) = showTIndex' ti ++ " " ++ showModMove m

fromIntS :: Int -> String
fromIntS n = if n < 0 then show n else "+ " ++ show n

showTIndex :: TIndex -> String
showTIndex ti = "{" ++ showTIndex' ti ++ "}"

showModMove :: ModMove -> String
showModMove LineStart = "linestart"
showModMove LineEnd = "lineend"
showModMove WordStart = "wordstart"
showModMove WordEnd = "wordend"
showModMove (ModChars n) = fromIntS n ++ " chars"
showModMove (ModLines n) = fromIntS n ++ " lines"

cmpIndex_ :: String -> Edit -> TIndex -> TIndex -> IO Bool
cmpIndex_ s e t1 t2 = 
  fmap tclToBool $ 
       (tclW e $ \w -> [w,"compare",s,showTIndex t1,showTIndex t2])

eqTIndex,ltTIndex,gtTIndex :: Edit -> TIndex -> TIndex 
                           -> IO Bool
eqTIndex = cmpIndex_ "=="
ltTIndex = cmpIndex_ "<"
gtTIndex = cmpIndex_ ">"

cmpTIndex :: Edit -> TIndex -> TIndex -> IO Ordering
cmpTIndex e t1 t2 = do
 eq <- eqTIndex e t1 t2
 if eq then return EQ
  else do
   lt <- ltTIndex e t1 t2
   return $ if lt then LT else GT
 
fromPlainIndex' :: String -> Maybe (Int,Int)
fromPlainIndex' s = 
  case splitWith (== '.') s of 
    [x1,x2] -> Just (parseInt x1,parseInt x2)
    _ -> Nothing

lineAreaEdit :: Edit -> TIndex -> IO (Int,Int,Int,Int,Int)
lineAreaEdit e t = do
 s <- tclW e $ \w -> [w,"dlineinfo",showTIndex t]
 case words s of
  [x,y,w,h,b] -> 
    return (parseInt x,parseInt y,parseInt w,parseInt h,parseInt b)
  _ -> fail $ "lineAreaEdit: invalid return value " ++ s

getEdit :: Edit -> IO String
getEdit e = getFromTo e (TIndex 0 0) TIndexEnd

getFromTo :: Edit -> TIndex -> TIndex -> IO String
getFromTo e t1 t2 = 
 tclW e $ \w -> [w,"get",showTIndex t1,showTIndex t2] 

fromTIndex :: Edit -> TIndex -> IO (Maybe (Int,Int))
fromTIndex e t1 = do
 s <- tclW e $ \w -> [w,"index",showTIndex t1]
 return $! fromPlainIndex' s

insertEdit :: Edit -> TIndex -> String -> IO ()
insertEdit e t s = doinsert e $  
 tclW_ e $ \w -> [w,"insert",showTIndex t,tcl_string s]


deleteEdit :: Edit -> TIndex -> TIndex -> IO ()   
deleteEdit e t1 t2 = doinsert e $
 tclW_ e $ \w -> [w,"delete",showTIndex t1,showTIndex t2]

resetEdit :: Edit -> String -> IO ()
resetEdit w s = doinsert w $ 
     do deleteEdit w (TIndex 1 0) (TIndexEnd)
	insertEdit w (TIndex 1 0) s
	tclW_ w $ \w -> [w,"mark set insert 1.0"]

searchEdit :: Edit 
	       -> Bool -- forward if True (back otherwise) 
	       -> Bool -- ignore case if True
	       -> Bool -- treat pattern as regexp
	       -> String -- pattern
	       -> TIndex -- start at
	       -> TIndex -- stop at
               -> IO (Maybe ((Int,Int),Int))
searchEdit e forward nocase isregexp pat start stop = 
 do v <- tclW e $ \w -> [w,"search",
		    if not forward then "-backwards" else "",
		    if nocase then "-nocase" else "",
		    if isregexp then "-regexp" else "",
		    "-count count" ++ wpath e,
		    "--",
		    pat,showTIndex start,showTIndex stop]
    case fromPlainIndex' v of
      Nothing -> return Nothing
      Just p -> do n <- tclW e $ const ["set count",wpath e]
		   return $ Just $ (p,parseInt n)
      
editMoveTo :: Edit -> TIndex -> IO ()
editMoveTo e t = tclW_ e $ \w -> [w,"see",showTIndex t] 

insertEditTagged :: Edit -> TIndex -> String -> [TagId] 
                 -> IO ()
insertEditTagged e t s tgs = doinsert e $ 
     do tclW_ e $ \w -> [w, "insert",showTIndex t,tcl_string s, prep tgs]
      where
	prep []       = "{}"
	prep (tg:tgs) = "{"++tg++foldr (\h t->' ':h++t) "}" tgs

clearSelEdit :: Edit -> IO ()
clearSelEdit e = doinsert e $ tclW_ e $ \w -> ["tk_textClear",w]

cutClipboard :: Edit -> IO ()
cutClipboard e = doinsert e $ tclW_ e $ \w -> ["tk_textCut",w]

copyClipboard :: Edit -> IO ()
copyClipboard e = doinsert e $ tclW_ e $ \w -> ["tk_textCopy",w]

pasteClipboard :: Edit -> IO ()
pasteClipboard e = doinsert e $ tclW_ e $ \w -> ["tk_textPaste",w]

-- Mark ------------------------------------------------

data Mark = Mark {markId :: ! MarkId,markEdit :: ! Edit}
type MarkId = String

mark' :: Edit -> MarkId -> TIndex -> IO Mark
mark' e s t = do 
 tclW_ e $ \w -> [w,"mark set",s,showTIndex t]
 return $! Mark s e

mark :: Edit -> TIndex -> IO Mark
mark e t = do nm <- tcl_newName (getTclGUIInfo e)
	      mark' e (show nm) t

getMarkPos :: Mark -> IO (Maybe (Int,Int))
getMarkPos m = fromTIndex (markEdit m) (TIndexMark (markId m))

setMark :: Mark -> TIndex -> IO ()
setMark (Mark s e) t = 
 tclW_ e $ \w -> [w,"mark set",s,showTIndex t]

removeMark :: Mark -> IO ()
removeMark (Mark s e) = 
 tclW_ e $ \w -> [w,"mark unset",s]

data Gravity = GLeft | GRight
 deriving (Eq,Show)

showGravity GLeft = "left"
showGravity GRight = "right"

setMarkGravity :: Mark -> Gravity -> IO ()
setMarkGravity (Mark s e) g = do 
 tclW_ e $ \w -> [w,"mark gravity",s,showGravity g]

getMarkGravity :: Mark -> IO Gravity
getMarkGravity (Mark s e) = do
 g <- tclW e $ \w -> [w,"mark gravity",s]
 case g of
  "left" -> return $! GLeft 
  _ -> return $! GRight

getAllMarks :: Edit -> IO [Mark]
getAllMarks e = do
 s <- tclW e $ \w -> [w,"mark names"]
 return $! map (\s -> Mark s e) $! words s

getMark :: Edit -> MarkId -> Mark
getMark e m = Mark m e

previousMark :: Edit -> TIndex -> IO Mark
previousMark e t = 
 fmap (flip Mark e) $
    tclW e $ \w -> [w,"mark previous",showTIndex t] 

nextMark :: Edit -> TIndex -> IO Mark
nextMark e t = 
 fmap (flip Mark e) $
    tclW e $ \w -> [w,"mark next",showTIndex t] 

insertionMark :: Edit -> Mark
insertionMark e = Mark "insert" e

currentMark :: Edit -> Mark
currentMark e = Mark "current" e

-- Embedded Text Windows -------------------------------

type Embedded = EWidget Ew
data Ew = Ew

embedded' :: Edit -> WPath -> TIndex -> [Config] -> IO Embedded
embedded' e pth indx cs = tcl_ewidget e pth (showTIndex indx) (("window" %% pth):cs) Ew 

embedded :: Edit -> PWidget a' -> TIndex -> [Config] -> IO Embedded
embedded e w indx cs = embedded' e (wpath w) indx cs

getAllEmbedded :: Edit -> IO [WPath]
getAllEmbedded e = fmap words $ tclW e $ \w -> [wpath e,"window names"]

-- Tag -------------------------------------------------
 
type Tag = TWidget Tg
data Tg  = TkTag

tag' :: Edit -> TagId -> [TIndex] -> [Config] -> IO Tag
tag' w ti indx cs = tcl_tgwidget w ti (fromTIndexes indx) (cs) TkTag

tag :: Edit -> [TIndex] -> [Config] -> IO Tag
tag w xs cs = do x <- tcl_newName (getTclGUIInfo w)
	         tg <- tag' w (show x) xs cs
	         setWithTags w (show x) xs
		 return tg

putPosTag :: Edit -> TIndex -> String -> [Config] -> IO Tag
putPosTag w p1 s cs = do
 t <- tag w [] cs
 insertEditTagged w p1 s [tagId t]
 return t


setWithTags :: Edit -> TagId -> [TIndex] -> IO ()
setWithTags e t [] = return ()
setWithTags e t xs = tclW_ e $ \w -> [w,"tag add",t,fromTIndexes xs]

fromTIndexes :: [TIndex] -> String
fromTIndexes xs = unwords $ map showTIndex xs

tagId :: Tag -> TagId 
tagId (TkWgt _ (TkT _ tg) _) = tg

tagEditWPath :: Tag -> WPath
tagEditWPath (TkWgt _ (TkT nm _) _) = nm

tagEdit :: Tag -> IO Edit
tagEdit w = totcl_pwidget (tagEditWPath w) Edi (getTclGUIInfo w)

lowerTag :: Tag -> Maybe TagId -> IO ()
lowerTag tg mb = tclW_ tg $ \w -> [w,"tag lower",tagId tg,maybe "" id mb]

raiseTag :: Tag -> Maybe TagId -> IO ()
raiseTag tg mb = tclW_ tg $ \w -> [w,"tag raise",tagId tg,maybe "" id mb]
 
getAllTags :: Edit -> IO [TagId]
getAllTags edi = fmap words $ tclW edi $ \w -> [w,"tag names"]

getTagsAt :: Edit -> TIndex -> IO [TagId]
getTagsAt edi t = 
 fmap words $ tclW edi $ \w -> [w,"tag names",showTIndex t]

tagRemove :: Tag -> [TIndex] -> IO ()
tagRemove tg ts = do
 tclW_ tg $ \w -> [w,"tag remove",tagId tg,fromTIndexes ts]

tagRemove' :: Edit -> TagId -> [TIndex] -> IO ()
tagRemove' e t ts = 
 tclW_ e $ \w -> [w,"tag remove",t,fromTIndexes ts]

setTag' :: Edit -> TagId -> [TIndex] -> IO ()
setTag' e t ts = 
 tclW_ e $ \w -> [w,"tag add",t,fromTIndexes ts]

setTag :: Tag -> [TIndex] -> IO ()
setTag tg ts = tclW_ tg $ \w -> [w,"tag add",tagId tg,fromTIndexes ts]

pairOff :: [a] -> [(a,a)]
pairOff (a:b:xs) = (a,b):pairOff xs
pairOff (a:xs) = []
pairOff [] = []

tagRanges :: Tag -> IO [((Int,Int),(Int,Int))]
tagRanges tg = do
 s <- tclW tg $ \w -> [w,"tag ranges"]
 return $ mapMaybe app $ pairOff $ words s
  where
    app (x1,x2) = case (fromPlainIndex' x1,fromPlainIndex' x2) of
                    (Just a,Just b) -> Just (a,b)
		    _ -> Nothing

tagNextRange :: Tag -> TIndex -> TIndex 
	     -> IO (Maybe ((Int,Int),(Int,Int)))   
tagNextRange = tagRange_ "nextrange"

tagPrevRange :: Tag -> TIndex -> TIndex 
	     -> IO (Maybe ((Int,Int),(Int,Int)))   
tagPrevRange = tagRange_ "prevrange"

tagRange_ :: String -> Tag -> TIndex -> TIndex 
	     -> IO (Maybe ((Int,Int),(Int,Int)))
tagRange_ op tg t1 t2 = do 
  s <- tclW tg $ \w -> [w,"tag",op,tagId tg,
	  	        showTIndex t1,showTIndex t2]
  case words s of
   [x,y] -> case fromPlainIndex' x of
	     (Just p1) ->
               case fromPlainIndex' y of
                (Just p2) ->
                  return $ Just (p1,p2) 
		_ -> return Nothing
	     _ -> return Nothing
   _ -> return Nothing

tagText :: Tag -> IO [String]
tagText t = do
 ts <- tagRanges t
 ed <- tagEdit t
 mapM (\((x,y),(x',y')) -> getFromTo ed (TIndex x y) (TIndex x' y')) ts


selectionTag :: Edit -> IO Tag
selectionTag w = do
 totcl_tgwidget w "sel" TkTag

