------------------------------------------------------------------------------
--- Library for GUI programming in Curry (based on Tcl/Tk).
---
--- This paper contains a description of the basic ideas
--- behind this library.
---
--- This library is an improved and updated version of the library Tk.
--- The latter might not be supported in the future.
---
--- @authors Michael Hanus, Bernd Brassel
--- @version January 2017
------------------------------------------------------------------------------
module GUI(GuiPort,Widget(..),Button,ConfigButton,
TextEditScroll,ListBoxScroll,CanvasScroll,EntryScroll,
ConfItem(..),ReconfigureItem(..),
Cmd,Command,
Event(..),ConfCollection(..),MenuItem(..),
CanvasItem(..),WidgetRef, Style(..), Color(..),
col,row,matrix,
runGUI,runGUIwithParams,runInitGUI,runInitGUIwithParams,
runPassiveGUI,
runControlledGUI,runConfigControlledGUI,runInitControlledGUI,
runHandlesControlledGUI,runInitHandlesControlledGUI,
exitGUI,getValue,setValue,updateValue,appendValue,
appendStyledValue,addRegionStyle,removeRegionStyle,
getCursorPosition,seeText,
focusInput,addCanvas,setConfig,
getOpenFile,getOpenFileWithTypes,getSaveFile,getSaveFileWithTypes,
chooseColor,popup_message,debugTcl,
cmd,command,button) where
import Char (isSpace, toUpper)
import IO
import IOExts (connectToCommand)
import Read
import System (system)
import Unsafe (trace)
-- If showTclTkErrors is true, all synchronization errors occuring in the
-- Tcl/Tk communication are shown (such errors should only occur on
-- slow machines in exceptional cases; they should be handled by this library
-- but might be interesting to see for debugging)
showTclTkErrors :: Bool
showTclTkErrors = False
-- If showTclTkCommunication is true, the all strings sent to and from
-- the Tcl/Tk GUI are shown in stdout:
showTclTkCommunication :: Bool
showTclTkCommunication = False
--- The port to a GUI is just the stream connection to a GUI
--- where Tcl/Tk communication is done.
data GuiPort = GuiPort Handle
handleOf :: GuiPort -> Handle
handleOf (GuiPort h) = h
------------------------------------------------------------------------
-- the basic data types for GUIs:
------------------------------------------------------------------------
--- The type of possible widgets in a GUI.
--- @cons PlainButton - a button in a GUI whose event handler is activated
--- if the user presses the button
--- @cons Canvas - a canvas to draw pictures containing CanvasItems
--- @cons CheckButton - a check button: it has value "0" if it is unchecked and
--- value "1" if it is checked
--- @cons Entry - an entry widget for entering single lines
--- @cons Label - a label for showing a text
--- @cons ListBox - a widget containing a list of items for selection
--- @cons Message - a message for showing simple string values
--- @cons MenuButton - a button with a pull-down menu
--- @cons Scale - a scale widget to input values by a slider
--- @cons ScrollH - a horizontal scroll bar
--- @cons ScrollV - a vertical scroll bar
--- @cons TextEdit - a text editor widget to show and manipulate larger
--- text paragraphs
--- @cons Row - a horizontal alignment of widgets
--- @cons Col - a vertical alignment of widgets
--- @cons Matrix - a 2-dimensional (matrix) alignment of widgets
data Widget = PlainButton [ConfItem]
| Canvas [ConfItem]
| CheckButton [ConfItem]
| Entry [ConfItem]
| Label [ConfItem]
| ListBox [ConfItem]
| Message [ConfItem]
| MenuButton [ConfItem]
| Scale Int Int [ConfItem]
| ScrollH WidgetRef [ConfItem]
| ScrollV WidgetRef [ConfItem]
| TextEdit [ConfItem]
| Row [ConfCollection] [Widget]
| Col [ConfCollection] [Widget]
| Matrix [ConfCollection] [[Widget]]
--
| RowC [ConfCollection] [ConfItem] [Widget]
| ColC [ConfCollection] [ConfItem] [Widget]
--- The data type for possible configurations of a widget.
--- @cons Active - define the active state for buttons, entries, etc.
--- @cons Anchor - alignment of information inside a widget where the
--- argument must be: n, ne, e, se, s, sw, w, nw, or center
--- @cons Background - the background color
--- @cons Foreground - the foreground color
--- @cons Handler - an event handler associated to a widget.
--- The event handler returns a list of widget
--- ref/configuration pairs that are applied after the handler
--- in order to configure GUI widgets
--- @cons Height - the height of a widget (chars for text, pixels for graphics)
--- @cons CheckInit - initial value for checkbuttons
--- @cons CanvasItems - list of items contained in a canvas
--- @cons List - list of values shown in a listbox
--- @cons Menu - the items of a menu button
--- @cons WRef - a reference to this widget
--- @cons Text - an initial text contents
--- @cons Width - the width of a widget (chars for text, pixels for graphics)
--- @cons Fill - fill widget in both directions
--- @cons FillX - fill widget in horizontal direction
--- @cons FillY - fill widget in vertical direction
--- @cons TclOption - further options in Tcl syntax (unsafe!)
data ConfItem =
Active Bool
| Anchor String
| Background String
| Foreground String
| Handler Event (GuiPort -> IO [ReconfigureItem])
| Height Int
| CheckInit String
| CanvasItems [CanvasItem]
| List [String]
| Menu [MenuItem]
| WRef WidgetRef
| Text String
| Width Int
| Fill | FillX | FillY
| TclOption String
| Display Bool
--- Data type for describing configurations that are applied
--- to a widget or GUI by some event handler.
--- @cons WidgetConf wref conf - reconfigure the widget referred by wref
--- with configuration item conf
--- @cons StreamHandler hdl handler - add a new handler to the GUI
--- that processes inputs on an input stream referred by hdl
--- @cons RemoveStreamHandler hdl - remove a handler for an input stream
--- referred by hdl from the GUI (usually used to remove handlers
--- for closed streams)
data ReconfigureItem =
WidgetConf WidgetRef ConfItem
| StreamHandler Handle (Handle -> GuiPort -> IO [ReconfigureItem])
| RemoveStreamHandler Handle
--- The data type of possible events on which handlers can react.
--- This list is still incomplete and might be extended or restructured
--- in future releases of this library.
--- @cons DefaultEvent - the default event of the widget
--- @cons MouseButton1 - left mouse button pressed
--- @cons MouseButton2 - middle mouse button pressed
--- @cons MouseButton3 - right mouse button pressed
--- @cons KeyPress - any key is pressed
--- @cons Return - return key is pressed
data Event = DefaultEvent
| MouseButton1
| MouseButton2
| MouseButton3
| KeyPress
| Return
-- translate event into corresponding Tcl string (except for DefaultEvent)
-- with a leading blank:
event2tcl :: Event -> String
event2tcl DefaultEvent = " default"
event2tcl MouseButton1 = " "
event2tcl MouseButton2 = " "
event2tcl MouseButton3 = " "
event2tcl KeyPress = " "
event2tcl Return = " "
--- The data type for possible configurations of widget collections
--- (e.g., columns, rows).
--- @cons CenterAlign - centered alignment
--- @cons LeftAlign - left alignment
--- @cons RightAlign - right alignment
--- @cons TopAlign - top alignment
--- @cons BottomAlign - bottom alignment
data ConfCollection =
CenterAlign | LeftAlign | RightAlign | TopAlign | BottomAlign
--- The data type for specifying items in a menu.
--- @cons MButton - a button with an associated command
--- and a label string
--- @cons MSeparator - a separator between menu entries
--- @cons MMenuButton - a submenu with a label string
data MenuItem =
MButton (GuiPort -> IO [ReconfigureItem]) String
| MSeparator
| MMenuButton String [MenuItem]
--- The data type of items in a canvas.
--- The last argument are further options in Tcl/Tk (for testing).
data CanvasItem = CLine [(Int,Int)] String
| CPolygon [(Int,Int)] String
| CRectangle (Int,Int) (Int,Int) String
| COval (Int,Int) (Int,Int) String
| CText (Int,Int) String String
--- The (hidden) data type of references to a widget in a GUI window.
--- Note that the constructor WRefLabel will not be exported so that values
--- can only be created inside this module.
--- @cons WRefLabel wp label type - here "wp" is the GUI port related
--- to the widget, "label" is the (globally unique) identifier of
--- this widget used in Tk, and "type" is one of
--- button / canvas / checkbutton / entry / label / listbox /
--- message / scale / scrollbar / textedit
data WidgetRef = WRefLabel GuiPort String String
wRef2Label (WRefLabel _ var _) = wRefname2Label var
wRef2Wtype (WRefLabel _ _ wtype) = wtype
--- The data type of possible text styles.
--- @cons Bold - text in bold font
--- @cons Italic - text in italic font
--- @cons Underline - underline text
--- @cons Fg - foreground color, i.e., color of the text font
--- @cons Bg - background color of the text
data Style = Bold | Italic | Underline | Fg Color | Bg Color
--- The data type of possible colors.
data Color
= Black | Blue | Brown | Cyan | Gold | Gray | Green | Magenta | Navy | Orange
| Pink | Purple | Red | Tomato| Turquoise | Violet | White | Yellow
--- Converts a style value into its textual representation.
showStyle :: Style -> String
showStyle Bold = "bold"
showStyle Italic = "italic"
showStyle Underline = "underline"
showStyle (Fg fg) = dropSpaces $ showColor fg
showStyle (Bg bg) = camelCase $ showColor bg
dropSpaces :: String -> String
dropSpaces = filter (not . isSpace)
camelCase :: String -> String
camelCase [] = []
camelCase (c:cs) = toUpper c : cc cs
where
cc "" = ""
cc [x] = [x]
cc (x:y:xs)
| isSpace x = toUpper y : cc xs
| otherwise = x : cc (y:xs)
--- Converts a color value into its textual representation.
showColor :: Color -> String
showColor Black = "black"
showColor Blue = "blue"
showColor Brown = "brown"
showColor Cyan = "cyan"
showColor Gold = "gold"
showColor Gray = "gray"
showColor Green = "forest green"
showColor Magenta = "magenta"
showColor Navy = "navy"
showColor Orange = "orange"
showColor Pink = "pink"
showColor Purple = "purple"
showColor Red = "red"
showColor Tomato = "tomato"
showColor Turquoise = "turquoise"
showColor Violet = "violet"
showColor White = "white"
showColor Yellow = "yellow"
------------------------------------------------------------------------
-- Some useful abbreviations:
------------------------------------------------------------------------
--- Horizontal alignment of widgets.
row :: [Widget] -> Widget
row = Row []
--- Vertical alignment of widgets.
col :: [Widget] -> Widget
col = Col []
--- Matrix alignment of widgets.
matrix :: [[Widget]] -> Widget
matrix = Matrix []
------------------------------------------------------------------------
-- internal translation functions from GUI terms into Tcl:
------------------------------------------------------------------------
-- An event handler specification consists of an identifying string of
-- the widget for which this handler is repsonsible, an event type
-- to which the handler should react, and a handler:
type EventHandler = (String,Event,GuiPort -> IO [ReconfigureItem])
-- translate a widget into a pair of Tcl command string / event list
-- argument 1: port for the GUI
-- argument 2: current label prefix
-- argument 3: the widget to translate
-- result: pair of (Tcl command string,
-- list of (eventname, eventtype, eventhandler))
widget2tcl :: GuiPort -> String -> Widget -> (String,[EventHandler])
widget2tcl wp label (PlainButton confs) =
("button "++label++"\n" ++
label++" configure -textvariable "++refname++"\n" ++
"proc getvar"++refname++" {} { global "++refname++" ; return $"
++refname++" }\n" ++
"proc setvar"++refname++" {s} { global "++refname++" ; set "
++refname++" $s}\n" ++
conf_tcl , conf_evs)
where refname = wLabel2Refname label
(conf_tcl,conf_evs) = configs2tcl "button" wp label confs
widget2tcl wp label (Canvas confs) =
("canvas "++label++"\n"
++"set "++refname++"_scrollx 100\n"
++"set "++refname++"_scrolly 100\n"
++"proc set"++refname++"_scrollx {x}"
++" { global "++refname++"_scrollx ; global "++refname++"_scrolly ;\n"
++" if {$"++refname++"_scrollx < $x} {set "++refname++"_scrollx $x ;\n"
++" "++label++" configure -scrollregion [list 0 0 $"
++refname++"_scrollx $"++refname++"_scrolly]}}\n"
++"proc set"++refname++"_scrolly {y}"
++" { global "++refname++"_scrollx ; global "++refname++"_scrolly ;\n"
++" if {$"++refname++"_scrolly < $y} {set "++refname++"_scrolly $y ;\n"
++" "++label++" configure -scrollregion [list 0 0 $"
++refname++"_scrollx $"++refname++"_scrolly]}}\n"
++ conf_tcl , conf_evs)
where refname = wLabel2Refname label
(conf_tcl,conf_evs) = configs2tcl "canvas" wp label confs
widget2tcl wp label (CheckButton confs) =
("checkbutton "++label++"\n" ++
label++" configure -variable "++refname++"\n" ++
"proc getvar"++refname++" {} { global "++refname++" ; return $"
++refname++" }\n" ++
"proc setvar"++refname++" {s} { global "++refname++" ; set "
++refname++" $s}\n" ++
conf_tcl , conf_evs)
where refname = wLabel2Refname label
(conf_tcl,conf_evs) = configs2tcl "checkbutton" wp label confs
widget2tcl wp label (Entry confs) =
("entry "++label++"\n" ++
label++" configure -textvariable "++refname++"\n" ++
"proc getvar"++refname++" {} { global "++refname++" ; return $"
++refname++" }\n" ++
"proc setvar"++refname++" {s} { global "++refname++" ; set "
++refname++" $s}\n" ++
conf_tcl , conf_evs)
where refname = wLabel2Refname label
(conf_tcl,conf_evs) = configs2tcl "entry" wp label confs
widget2tcl wp label (Label confs) =
("label "++label++"\n" ++
label++" configure -textvariable "++refname++"\n" ++
"proc getvar"++refname++" {} { global "++refname++" ; return $"
++refname++" }\n" ++
"proc setvar"++refname++" {s} { global "++refname++" ; set "
++refname++" $s}\n" ++
conf_tcl , conf_evs)
where refname = wLabel2Refname label
(conf_tcl,conf_evs) = configs2tcl "label" wp label confs
widget2tcl wp label (ListBox confs) =
("listbox "++label++" -exportselection false\n" ++
"proc getvar"++refname++" {} { return ["++label++" curselection]}\n" ++
"proc setvar"++refname++" {s} { "++label++" selection clear 0 end ; "
++label++" selection set $s ; "++label++" see $s}\n" ++
conf_tcl , conf_evs)
where refname = wLabel2Refname label
(conf_tcl,conf_evs) = configs2tcl "listbox" wp label confs
widget2tcl wp label (Message confs) =
("message "++label++"\n" ++
label++" configure -textvariable "++refname++"\n" ++
"proc getvar"++refname++" {} { global "++refname++" ; return $"
++refname++" }\n" ++
"proc setvar"++refname++" {s} { global "++refname++" ; set "
++refname++" $s}\n" ++
conf_tcl , conf_evs)
where refname = wLabel2Refname label
(conf_tcl,conf_evs) = configs2tcl "message" wp label confs
widget2tcl wp label (MenuButton confs) =
("menubutton "++label++"\n" ++
label++" configure -textvariable "++refname++"\n" ++
"proc getvar"++refname++" {} { global "++refname++" ; return $"
++refname++" }\n" ++
"proc setvar"++refname++" {s} { global "++refname++" ; set "
++refname++" $s}\n" ++
conf_tcl , conf_evs)
where refname = wLabel2Refname label
(conf_tcl,conf_evs) = configs2tcl "menubutton" wp label confs
widget2tcl wp label (Scale from to confs) =
("scale "++label++" -from "++show from++" -to "++show to++
" -orient horizontal -length 200\n" ++
"variable "++refname++" "++show from++"\n"++ -- initialize scale variable
label++" configure -variable "++refname++"\n" ++
"proc getvar"++refname++" {} { global "++refname++" ; return $"
++refname++" }\n" ++
"proc setvar"++refname++" {s} { global "++refname++" ; set "
++refname++" $s}\n" ++
conf_tcl , conf_evs)
where refname = wLabel2Refname label
(conf_tcl,conf_evs) = configs2tcl "scale" wp label confs
widget2tcl wp label (ScrollH widget confs) =
("scrollbar "++label++" -orient horizontal -command {"++
wRef2Label widget++" xview}\n" ++
wRef2Label widget++" configure -xscrollcommand {"++label++" set}\n" ++
wRef2Label widget++" configure -wrap none\n" ++ -- no line wrap
conf_tcl , conf_evs)
where (conf_tcl,conf_evs) = configs2tcl "scrollbar" wp label confs
widget2tcl wp label (ScrollV widget confs) =
("scrollbar "++label++" -command {"++wRef2Label widget++" yview}\n" ++
wRef2Label widget++" configure -yscrollcommand {"++label++" set}\n" ++
conf_tcl , conf_evs)
where (conf_tcl,conf_evs) = configs2tcl "scrollbar" wp label confs
widget2tcl wp label (TextEdit confs) =
("text "++label++"\n"++ --" -height 15\n" ++
"proc getvar"++refname++" {} { "++label++" get 1.0 {end -1 chars}}\n" ++
"proc setvar"++refname++" {s} { "++label++" delete 1.0 end ; "
++label++" insert 1.0 $s}\n" ++
conf_tcl ++
enableFont "italic" "-slant italic" ++
enableFont "underline" "-underline on" ++
enableFont "bold" "-weight bold" ++
unlines (map enableForeground colors) ++
unlines (map enableBackground colors)
, conf_evs)
where refname = wLabel2Refname label
(conf_tcl,conf_evs) = configs2tcl "textedit" wp label confs
enableFont tag style
= label ++ " tag configure " ++ tag ++ " -font \"[font actual [" ++
label ++ " cget -font]] " ++ style ++ "\"\n"
colors = map showColor
[Black,Blue,Brown,Cyan,Gold,Gray,Green,Magenta,Navy,Orange,Pink
,Purple,Red,Tomato,Turquoise,Violet,White,Yellow]
enableForeground color
= label ++ " tag configure " ++ dropSpaces color ++
" -foreground \"" ++ color ++ "\""
enableBackground color
= label++" tag configure "++ camelCase color ++
" -background \"" ++ color ++ "\""
widget2tcl wp label (Row confs ws) =
((if label=="" then "wm resizable . " ++ resizeBehavior wsGridInfo++"\n"
else "frame "++label++"\n") ++
wstcl ++
(snd $ foldl (\ (n,g) l->(n+1,g++"grid "++label ++ labelIndex2string (96+n)
++" -row 1 -column "++show n++" "
++confCollection2tcl confs
++gridInfo2tcl n label "col" l ++ "\n"))
(1,"")
wsGridInfo),
wsevs)
where (wstcl,wsevs) = widgets2tcl wp label 97 ws
wsGridInfo = widgets2gridinfo ws
widget2tcl wp label (Col confs ws) =
((if label=="" then "wm resizable . " ++ resizeBehavior wsGridInfo++"\n"
else "frame "++label++"\n") ++
wstcl ++
(snd $ foldl (\ (n,g) l->(n+1,g++"grid "++label ++ labelIndex2string (96+n)
++" -column 1 -row "++show n++" "
++confCollection2tcl confs
++gridInfo2tcl n label "row" l ++ "\n"))
(1,"")
(widgets2gridinfo ws)),
wsevs)
where (wstcl,wsevs) = widgets2tcl wp label 97 ws
wsGridInfo = widgets2gridinfo ws
widget2tcl wp label (Matrix confs ws) =
((if label == "" then "wm resizable . " ++ resizeBehavior wsGridInfo++"\n"
else "frame "++label++"\n") ++ wstcl,wsevs)
where
(wstcl,wsevs) = matrix2tcl 97 1 wp label confs ws
wsGridInfo = concatMap widgets2gridinfo ws
--
widget2tcl wp label (RowC confs confitems ws) =
((if label=="" then "wm resizable . " ++ resizeBehavior wsGridInfo++"\n"
else "frame "++label++"\n" ++ conf_tcl ++ "\n") ++
wstcl ++
(snd $ foldl (\ (n,g) l->(n+1,g++"grid "++label ++ labelIndex2string (96+n)
++" -row 1 -column "++show n++" "
++confCollection2tcl confs
++gridInfo2tcl n label "col" l ++ "\n"))
(1,"")
wsGridInfo),
conf_evs ++ wsevs)
where (wstcl,wsevs) = widgets2tcl wp label 97 ws
(conf_tcl,conf_evs) = configs2tcl "row" wp label confitems
wsGridInfo = widgets2gridinfo ws
widget2tcl wp label (ColC confs confitems ws) =
((if label=="" then "wm resizable . " ++ resizeBehavior wsGridInfo++"\n"
else "frame "++label++"\n" ++ conf_tcl ++ "\n") ++
wstcl ++
(snd $ foldl (\ (n,g) l->(n+1,g++"grid "++label ++ labelIndex2string (96+n)
++" -column 1 -row "++show n++" "
++confCollection2tcl confs
++gridInfo2tcl n label "row" l ++ "\n"))
(1,"")
(widgets2gridinfo ws)),
conf_evs ++ wsevs)
where (wstcl,wsevs) = widgets2tcl wp label 97 ws
(conf_tcl,conf_evs) = configs2tcl "col" wp label confitems
wsGridInfo = widgets2gridinfo ws
-- actual translation function of the list of lists of widgets in a matrix
matrix2tcl :: Int -> Int -> GuiPort -> String -> [ConfCollection]
-> [[Widget]] -> (String,[EventHandler])
matrix2tcl _ _ _ _ _ [] = ("",[])
matrix2tcl nextLabel n wp label confs (ws:wss) =
(wstcl ++
(snd $ foldl (\ (m,g) l->(m+1,g++"grid "++label ++ labelIndex2string (nextLabel+m-1)
++" -row "++show n ++" -column "++show m++" "
++confCollection2tcl confs
++gridInfo2tcl m label "col" l ++ "\n"))
(1,"")
wsGridInfo) ++ wsstcl, wsevs++wssevs)
where (wsstcl,wssevs) = matrix2tcl (nextLabel+length ws) (n+1) wp label confs wss
(wstcl,wsevs) = widgets2tcl wp label nextLabel ws
wsGridInfo = widgets2gridinfo ws
-- compute the required resize behavior of the top window
resizeBehavior :: [[ConfItem]] -> String
resizeBehavior ws = if any (elem Fill) ws then "1 1" else
if any (elem FillX) ws then "1 0" else
if any (elem FillY) ws then "0 1" else "0 0"
-- list of labels of the widgets
widgets2gridinfo [] = []
widgets2gridinfo (w:ws) =
(tclfill ++ getConfs w): widgets2gridinfo ws
where
fillx = hasFillX w
filly = hasFillY w
flexible = hasFill w
tclfill = if flexible || (fillx && filly) then [Fill] else
if fillx then [FillX] else
if filly then [FillY] else []
hasFillX w = any isFillXConf (propagateFillInfo w)
isFillXConf conf = case conf of
FillX -> True
_ -> False
hasFillY w = any isFillYConf (propagateFillInfo w)
isFillYConf conf = case conf of
FillY -> True
_ -> False
hasFill w = any isFillConf (propagateFillInfo w)
isFillConf conf = case conf of
Fill -> True
_ -> False
isFillInfo conf = case conf of
FillX -> True
FillY -> True
Fill -> True
_ -> False
-- propagate FillInfo for those kinds of widgets which are resizable on their on
propagateFillInfo (PlainButton _) = []
propagateFillInfo (Canvas confs) = filter isFillInfo confs
propagateFillInfo (CheckButton _) = []
propagateFillInfo (Entry confs) = filter isFillInfo confs
propagateFillInfo (Label confs) = filter isFillInfo confs
propagateFillInfo (ListBox confs) = filter isFillInfo confs
propagateFillInfo (Message confs) = filter isFillInfo confs
propagateFillInfo (MenuButton _) = []
propagateFillInfo (Scale _ _ confs) = filter isFillInfo confs
propagateFillInfo (ScrollV _ _) = []
propagateFillInfo (ScrollH _ _) = []
propagateFillInfo (TextEdit confs) = filter isFillInfo confs
propagateFillInfo (Row _ ws) = concatMap propagateFillInfo ws
propagateFillInfo (Col _ ws) = concatMap propagateFillInfo ws
propagateFillInfo (Matrix _ wss) = concatMap (concatMap propagateFillInfo) wss
propagateFillInfo (RowC _ _ ws) = concatMap propagateFillInfo ws
propagateFillInfo (ColC _ _ ws) = concatMap propagateFillInfo ws
-- get the configurations of a widget
getConfs (PlainButton confs) = confs
getConfs (Canvas confs) = filter isFillInfo confs
getConfs (CheckButton confs) = confs
getConfs (Entry confs) = filter isFillInfo confs
getConfs (Label confs) = filter isFillInfo confs
getConfs (ListBox confs) = filter isFillInfo confs
getConfs (Message confs) = filter isFillInfo confs
getConfs (MenuButton confs) = confs
getConfs (Scale _ _ confs) = filter isFillInfo confs
getConfs (ScrollV _ confs) = confs
getConfs (ScrollH _ confs) = confs
getConfs (TextEdit confs) = filter isFillInfo confs
getConfs (Row _ _) = []
getConfs (Col _ _) = []
getConfs (Matrix _ _) = []
getConfs (RowC _ confs _) = confs
getConfs (ColC _ confs _) = confs
-- translate configuration options for collections (rows or columns)
-- into parameters for the Tcl/Tk command "grid":
confCollection2tcl [] = ""
confCollection2tcl (CenterAlign : confs) = confCollection2tcl confs
confCollection2tcl (LeftAlign : confs) = "-sticky w " ++ confCollection2tcl confs
confCollection2tcl (RightAlign : confs) = "-sticky e " ++ confCollection2tcl confs
confCollection2tcl (TopAlign : confs) = "-sticky n " ++ confCollection2tcl confs
confCollection2tcl (BottomAlign : confs) = "-sticky s " ++ confCollection2tcl confs
-- translate the Fill - options to sticky options and grid configures
gridInfo2tcl :: Int -> String -> String -> [ConfItem] -> String
gridInfo2tcl n label "col" confs
| elem Fill confs || (elem FillX confs && elem FillY confs)
= "-sticky nsew \ngrid columnconfigure "++lab++" "++show n++
" -weight 1\ngrid rowconfigure "++lab++" 1 -weight 1"
| elem FillX confs = "-sticky we \ngrid columnconfigure "++lab++
" "++show n++" -weight 1"
| elem FillY confs = "-sticky ns \ngrid rowconfigure "++lab++
" 1 -weight 1"
| otherwise = ""
where
lab = if label=="" then "." else label
gridInfo2tcl n label "row" confs
| elem Fill confs || (elem FillX confs && elem FillY confs)
= "-sticky nsew \ngrid columnconfigure "++lab++
" 1 -weight 1\ngrid rowconfigure "++lab++" "++show n++" -weight 1"
| elem FillX confs = "-sticky we \ngrid columnconfigure "++lab++
" 1 -weight 1"
| elem FillY confs = "-sticky ns \ngrid rowconfigure "++lab++
" "++show n++" -weight 1"
| otherwise = ""
where
lab = if label=="" then "." else label
-- translate a single configuration option into Tcl/Tk commands
-- to configure the widget:
-- the first argument specifies the type of the widget
-- (button/canvas/checkbutton/entry/label/listbox/message/scale/scrollbar/
-- textedit)
-- and the third argument is the widget label
config2tcl :: String -> GuiPort -> String -> ConfItem -> String
-- is the state of the widget active ("normal" in Tcl/Tk) or
-- inactive ("disabled" in Tcl/Tk)?
-- (inactive widgets do not accept any events)
config2tcl wtype _ label (Active active) =
if wtype=="button" || wtype=="checkbutton" || wtype=="entry" ||
wtype=="menubutton" || wtype=="scale" || wtype=="textedit"
then if active
then label++" configure -state normal\n"
else label++" configure -state disabled\n"
else trace ("WARNING: GUI.Active ignored for widget type \""++wtype++"\"\n") ""
-- alignment of information inside a widget
-- argument must be: n, ne, e, se, s, sw, w, nw, or center
config2tcl wtype _ label (Anchor align) =
if wtype=="button" || wtype=="checkbutton" || wtype=="label" ||
wtype=="menubutton" || wtype=="message"
then label++" configure -anchor "++align++"\n"
else trace ("WARNING: GUI.Anchor ignored for widget type \""++wtype++"\"\n") ""
-- background color:
config2tcl _ _ label (Background color)
= label++" configure -background \""++color++"\"\n"
-- foreground color:
config2tcl _ _ label (Foreground color)
= label++" configure -foreground \""++color++"\"\n"
-- command associated to various widgets:
config2tcl wtype _ label (Handler evtype _)
| evtype == DefaultEvent
= if wtype=="button"
then label++" configure -command"++writeEvent else
if wtype=="checkbutton"
then label++" configure -command"++writeEvent else
if wtype=="entry"
then "bind "++label++" "++writeEvent else
if wtype=="scale"
then label++" configure -command { putlabel \""++label++event2tcl evtype++"\"}\n" else
if wtype=="listbox"
then "bind "++label++" "++writeEvent else
if wtype=="textedit"
then "bind "++label++" "++writeEvent
else
trace ("WARNING: GUI.Handler with DefaultEvent ignored for widget type \""++
wtype++"\"\n") ""
| otherwise
= "bind "++label++event2tcl evtype++writeEvent
where
writeEvent = " { writeevent \""++label++event2tcl evtype++"\" }\n"
-- height of a widget (not defined for all widget types):
config2tcl wtype _ label (Height h)
| wtype=="entry" || wtype=="message" || wtype=="menubutton" ||
wtype=="scale"
= trace ("WARNING: GUI.Height ignored for widget type \""++wtype++"\"\n") ""
| wtype=="canvas"
= label++" configure -height "++show h++"\n"++
"set"++wLabel2Refname label++"_scrolly "++show h++"\n"
| otherwise
= label++" configure -height "++show h++"\n"
-- show/hide widget
config2tcl _ _ label (Display b)
= if b then "grid " ++ label ++ "\n"
else "grid remove " ++ label ++ "\n"
-- value of checkbuttons:
config2tcl wtype _ label (CheckInit s)
| wtype=="checkbutton"
= "setvar"++wLabel2Refname label++" \""++s++"\"\n"
| wtype=="listbox"
= label ++ " selection set " ++ " \""++s++"\"\n"
| otherwise
= trace ("WARNING: GUI.CheckInit ignored for widget type \""++wtype++"\"\n") ""
-- items in a canvas:
config2tcl wtype _ label (CanvasItems items)
| wtype=="canvas" = canvasItems2tcl label items
| otherwise
= trace ("WARNING: GUI.CanvasItems ignored for widget type \""++wtype++"\"\n") ""
-- value lists for listboxes:
config2tcl wtype _ label (List l)
| wtype=="listbox"
= label++" delete 0 end\n" ++ setlistelems (ensureSpine l)
| otherwise
= trace ("WARNING: GUI.List ignored for widget type \""++wtype++"\"\n") ""
where
setlistelems [] = ""
setlistelems (e:es) = label++" insert end \""++escape_tcl e++"\"\n"++
setlistelems es
-- items in a menu button:
config2tcl wtype _ label (Menu l)
| wtype=="menubutton"
= label++" configure -menu "++label++".a\n" ++
menu2tcl (label++".a") l
| otherwise
= trace ("WARNING: GUI.Menu ignored for widget type \""++wtype++"\"\n") ""
-- references to widgets are bound to actual widget labels:
config2tcl wtype wp label (WRef r)
| r =:= WRefLabel wp (wLabel2Refname label) wtype = ""
-- initial text value of widgets:
config2tcl wtype _ label (Text s)
| wtype=="canvas"
= trace "WARNING: GUI.Text ignored for Canvas\n" ""
| wtype=="checkbutton"
= label++" configure -text \""++escape_tcl s++"\"\n"
| otherwise
= "setvar"++wLabel2Refname label++" \""++escape_tcl s++"\"\n"
-- width of a widget:
config2tcl wtype _ label (Width w)
| wtype=="canvas"
= label++" configure -width "++show w++"\n"++
"set"++wLabel2Refname label++"_scrollx "++show w++"\n"
| otherwise = label++" configure -width "++show w++"\n"
-- configuration options for widget composition are ignored here
-- since they are used during geometry management
config2tcl _ _ _ Fill = ""
config2tcl _ _ _ FillX = ""
config2tcl _ _ _ FillY = ""
-- for testing, put arbitrary Tk options for this widget:
config2tcl _ _ label (TclOption tcloptions)
= label++" configure "++tcloptions++"\n"
-- translation of a menu with a given label:
menu2tcl label menu =
"menu "++label++" -tearoff false\n" ++
label++" delete 0 end\n" ++
setmenuelems menu 0
where setmenuelems [] _ = ""
setmenuelems (MButton _ text : es) i =
label++" add command -label \""++escape_tcl text++
"\" -command { writeevent \""++label++"."++show i++
event2tcl DefaultEvent++"\" }\n"++
setmenuelems es (i+1)
setmenuelems (MSeparator : es) i =
label++" add separator\n"++ setmenuelems es (i+1)
setmenuelems (MMenuButton text l : es) i =
label++" add cascade -label \""++escape_tcl text++
"\" -menu "++label++labelIndex2string (i+97)++"\n"++
menu2tcl (label++labelIndex2string (i+97)) l ++
setmenuelems es (i+1)
-- get the event handlers in a list of configuration options:
-- and bind widget references:
configs2handler :: String -> [ConfItem] -> [EventHandler]
configs2handler _ [] = []
configs2handler label (confitem : cs) = case confitem of
Handler evtype handler -> (label,evtype,handler) : configs2handler label cs
Menu m -> menu2handler (label++".a") m 0 ++ configs2handler label cs
_ -> configs2handler label cs
menu2handler _ [] _ = []
menu2handler label (MButton handler _ : ms) i =
(label++"."++show i, DefaultEvent, handler) : menu2handler label ms (i+1)
menu2handler label (MSeparator : ms) i = menu2handler label ms (i+1)
menu2handler label (MMenuButton _ menu : ms) i =
menu2handler (label++labelIndex2string (i+97)) menu 0 ++
menu2handler label ms (i+1)
-- translate configuration options into Tcl/Tk commands and event handler map:
configs2tcl :: String -> GuiPort -> String -> [ConfItem]
-> (String,[EventHandler])
configs2tcl wtype wp label confs =
(concatMap (config2tcl wtype wp label) confs,
configs2handler label confs)
-- translate a list of canvas items into a Tcl string:
canvasItems2tcl _ [] = ""
canvasItems2tcl label (i:is) =
canvasItem2tcl label i ++ canvasItems2tcl label is
canvasItem2tcl label (CLine coords opts) =
label++ " create line "++showCoords coords++" "++opts++"\n"++
concatMap (\(x,_)->"set"++refname++"_scrollx "++show x++"\n") coords ++
concatMap (\(_,y)->"set"++refname++"_scrolly "++show y++"\n") coords
where refname = wLabel2Refname label
canvasItem2tcl label (CPolygon coords opts) =
label++ " create polygon "++showCoords coords++" "++opts++"\n"++
concatMap (\(x,_)->"set"++refname++"_scrollx "++show x++"\n") coords ++
concatMap (\(_,y)->"set"++refname++"_scrolly "++show y++"\n") coords
where refname = wLabel2Refname label
canvasItem2tcl label (CRectangle (x1,y1) (x2,y2) opts) =
label++ " create rectangle "++showCoords [(x1,y1),(x2,y2)]++" "++opts++"\n"++
concatMap (\x->"set"++refname++"_scrollx "++show x++"\n") [x1,x2] ++
concatMap (\y->"set"++refname++"_scrolly "++show y++"\n") [y1,y2]
where refname = wLabel2Refname label
canvasItem2tcl label (COval (x1,y1) (x2,y2) opts) =
label++ " create oval "++showCoords [(x1,y1),(x2,y2)]++" "++opts++"\n"++
concatMap (\x->"set"++refname++"_scrollx "++show x++"\n") [x1,x2] ++
concatMap (\y->"set"++refname++"_scrolly "++show y++"\n") [y1,y2]
where refname = wLabel2Refname label
canvasItem2tcl label (CText (x,y) text opts) =
label++ " create text "++show x++" "++show y++
" -text \""++escape_tcl text++"\" "++opts++"\n"++
"set"++refname++"_scrollx "++show (x+5*(length text))++"\n"++
"set"++refname++"_scrolly "++show y++"\n"
where refname = wLabel2Refname label
showCoords [] = ""
showCoords ((x,y):cs) = show x++" "++show y++" "++showCoords cs
-- translate a widget label into a name (replacing dots by underscores)
wLabel2Refname l = map (\c -> if c=='.' then '_' else c) l
-- translate a name into a widget label (replacing underscores by dots)
wRefname2Label l = map (\c -> if c=='_' then '.' else c) l
-- translate a list of widgets into pair Tcl string / event list:
widgets2tcl _ _ _ [] = ("",[])
widgets2tcl wp lab nr (w:ws) = (wtcl ++ wstcl, wevs ++ wsevs)
where (wtcl,wevs) = widget2tcl wp (lab++labelIndex2string nr) w
(wstcl,wsevs) = widgets2tcl wp lab (nr+1) ws
-- translate a label index into a textual label
-- (e.g., 97->".a" or 123->".z1"):
labelIndex2string :: Int -> String
labelIndex2string li = if li<123 then ['.',chr li]
else ['.','z'] ++ show (li-122)
-- translate main widget:
mainWidget2tcl :: GuiPort -> Widget -> (String,[EventHandler])
mainWidget2tcl wp widget =
("proc writeevent {l} { puts \":EVT$l\" }\n" ++
"proc putlabel {l v} { writeevent $l }\n" ++
"proc putvar {var value} { puts \":VAR$var%[string length $value]*$value\"}\n" ++
widgettcl, evs)
where (widgettcl,evs) = widget2tcl wp "" widget
--- Prints the generated Tcl commands of a main widget (useful for debugging).
debugTcl :: Widget -> IO ()
debugTcl widget = putStrLn (fst (mainWidget2tcl wp widget)) where wp free
------------------------------------------------------------------------
-- Operations to communicate with Tcl/Tk:
------------------------------------------------------------------------
reportTclTk s =
if showTclTkCommunication then hPutStrLn stdout s else done
reportTclTkError s =
if showTclTkErrors then hPutStrLn stderr s else done
-- Open a GUI port by connecting to new "wish" process.
-- The first argument are parameters passed to the wish command.
openGuiPort :: String -> IO GuiPort
openGuiPort wishparams = do
exwish <- system "which wish"
when (exwish>0) $
error "Windowing shell `wish' not found. Please install package `tk'!"
reportTclTk ("OPEN CONNECTION TO WISH WITH PARAMS: "++wishparams)
tclhdl <- connectToCommand ("wish "++wishparams)
return (GuiPort tclhdl)
-- Send a string (Tcl/Tk command) to GUI port:
send2tk :: String -> GuiPort -> IO ()
send2tk s (GuiPort tclhdl) = do
reportTclTk ("GUI SEND: "++s)
hPutStrLn tclhdl s
hFlush tclhdl
-- Receive an output line from the wish process:
receiveFromTk :: GuiPort -> IO String
receiveFromTk (GuiPort tclhdl) = do
s <- hGetLine tclhdl
reportTclTk ("GUI RECEIVED: "++s)
return s
-- Choice over the output of the wish process and a stream of external messages
choiceOverHandlesMsgs :: [Handle] -> [msg] -> IO (Either (Int,Handle) [msg])
choiceOverHandlesMsgs hdls msgs = do
iormsgs <- hWaitForInputsOrMsg hdls msgs
return (either (\i -> Left (i,hdls!!i)) Right iormsgs)
-- Choice over the output of the wish process and handles to input streams:
choiceOverHandles :: [Handle] -> IO (Int,Handle)
choiceOverHandles hdls = do
i <- hWaitForInputs hdls (-1)
return (i,hdls!!i)
-- Close connection to wish process:
closeGuiPort :: GuiPort -> IO ()
closeGuiPort (GuiPort tclhdl) = do
reportTclTk "CLOSE CONNECTION TO WISH"
hClose tclhdl
------------------------------------------------------------------------
-- functions for running a GUI:
------------------------------------------------------------------------
--- Creates a new GUI window with a "title" for the top-level window
--- (but unspecified contents). A GUI port is returned that can be
--- used to start a GUI specification on this port.
--- @param title - the title of the top-level window
--- @param params - parameter string passed to the initial wish command
openWish :: String -> String -> IO GuiPort
openWish title params = do
gport <- openGuiPort params
send2tk ("wm title . \""++title++"\"\n") gport
return gport
--- IO action to show a Widget in a new GUI window in passive mode,
--- i.e., ignore all GUI events.
--- @param title - the title of the main window containing the widget
--- @param widget - the widget shown in the new window
runPassiveGUI :: String -> Widget -> IO GuiPort
runPassiveGUI title widget = do
gport <- openWish (escape_tcl title) ""
send2tk (fst (mainWidget2tcl gport widget)) gport
return gport
--- IO action to run a Widget in a new window.
--- @param title - the title of the main window containing the widget
--- @param widget - the widget shown in the new window
runGUI :: String -> Widget -> IO ()
runGUI title widget = runInitGUIwithParams title "" widget (const done)
--- IO action to run a Widget in a new window.
--- @param title - the title of the main window containing the widget
--- @param params - parameter string passed to the initial wish command
--- @param widget - the widget shown in the new window
runGUIwithParams :: String -> String -> Widget -> IO ()
runGUIwithParams title params widget =
runInitGUIwithParams title params widget (const done)
--- IO action to run a Widget in a new window. The GUI events
--- are processed after executing an initial action on the GUI.
--- @param title - the title of the main GUI window
--- @param widget - the widget shown in the new GUI window
--- @param initcmd - the initial command executed before activating the GUI
runInitGUI :: String -> Widget -> (GuiPort -> IO ()) -> IO ()
runInitGUI title widget initcmd = do
gport <- openWish (escape_tcl title) ""
initSchedule widget gport [] [] initcmd
--- IO action to run a Widget in a new window. The GUI events
--- are processed after executing an initial action on the GUI.
--- @param title - the title of the main GUI window
--- @param params - parameter string passed to the initial wish command
--- @param widget - the widget shown in the new GUI window
--- @param initcmd - the initial command executed before activating the GUI
runInitGUIwithParams :: String -> String -> Widget -> (GuiPort -> IO ()) -> IO ()
runInitGUIwithParams title params widget initcmd = do
gport <- openWish (escape_tcl title) params
initSchedule widget gport [] [] initcmd
--- Runs a Widget in a new GUI window and process GUI events.
--- In addition, an event handler is provided that process
--- messages received from an external message stream.
--- This operation is useful to run a GUI that should react on
--- user events as well as messages sent to an external port.
--- @param title - the title of the main window containing the widget
--- @param th - a pair (widget,exth) where widget is the widget shown in the
--- new window and exth is the event handler for external messages
--- @param msgs - the stream of external messages (usually coming from
--- an external port)
runControlledGUI :: String -> (Widget, msg -> GuiPort -> IO ()) -> [msg] -> IO ()
runControlledGUI title (widget,exth) msgs =
runInitControlledGUI title (widget,exth) (const done) msgs
--- Runs a Widget in a new GUI window and process GUI events.
--- In addition, an event handler is provided that process
--- messages received from an external message stream.
--- This operation is useful to run a GUI that should react on
--- user events as well as messages sent to an external port.
--- @param title - the title of the main window containing the widget
--- @param th - a pair (widget,exth) where widget is the widget shown in the
--- new window and exth is the event handler for external messages
--- that returns a list of widget reference/configuration pairs
--- which is applied after the handler in order to configure
--- some GUI widgets
--- @param msgs - the stream of external messages (usually coming from
--- an external port)
runConfigControlledGUI :: String ->
(Widget, msg -> GuiPort -> IO [ReconfigureItem]) -> [msg] -> IO ()
runConfigControlledGUI title (widget,exth) msgs = do
gport <- openWish (escape_tcl title) ""
initSchedule widget gport [PortMsgHandler exth] msgs (\_->done)
--- Runs a Widget in a new GUI window and process GUI events
--- after executing an initial action on the GUI window.
--- In addition, an event handler is provided that process
--- messages received from an external message stream.
--- This operation is useful to run a GUI that should react on
--- user events as well as messages sent to an external port.
--- @param title - the title of the main window containing the widget
--- @param th - a pair (widget,exth) where widget is the widget shown in the
--- new window and exth is the event handler for external messages
--- @param initcmd - the initial command executed before starting the GUI
--- @param msgs - the stream of external messages (usually coming from
--- an external port)
runInitControlledGUI :: String -> (Widget, msg -> GuiPort -> IO ()) ->
(GuiPort -> IO ()) -> [msg] -> IO ()
runInitControlledGUI title (widget,exth) initcmd msgs = do
gport <- openWish (escape_tcl title) ""
initSchedule widget gport
[PortMsgHandler (\msg wp -> exth msg wp >> return [])]
msgs initcmd
--- Runs a Widget in a new GUI window and process GUI events.
--- In addition, a list of event handlers is provided that process
--- inputs received from a corresponding list of handles to input streams.
--- Thus, if the i-th handle has some data available, the i-th event handler
--- is executed with the i-th handle as a parameter.
--- This operation is useful to run a GUI that should react on
--- inputs provided by other processes, e.g., via sockets.
--- @param title - the title of the main window containing the widget
--- @param th - a pair (widget,handlers) where widget is the widget shown in the
--- new window and handlers is a list of event handler for external inputs
--- @param handles - a list of handles to the external input streams for the
--- corresponding event handlers
runHandlesControlledGUI :: String -> (Widget,[Handle -> GuiPort -> IO ()])
-> [Handle] -> IO ()
runHandlesControlledGUI title widgethandlers handles =
runInitHandlesControlledGUI title widgethandlers (const done) handles
--- Runs a Widget in a new GUI window and process GUI events
--- after executing an initial action on the GUI window.
--- In addition, a list of event handlers is provided that process
--- inputs received from a corresponding list of handles to input streams.
--- Thus, if the i-th handle has some data available, the i-th event handler
--- is executed with the i-th handle as a parameter.
--- This operation is useful to run a GUI that should react on
--- inputs provided by other processes, e.g., via sockets.
--- @param title - the title of the main window containing the widget
--- @param th - a pair (widget,handlers) where widget is the widget shown in the
--- new window and handlers is a list of event handler for external inputs
--- @param initcmd - the initial command executed before starting the GUI
--- @param handles - a list of handles to the external input streams for the
--- corresponding event handlers
runInitHandlesControlledGUI :: String -> (Widget,[Handle -> GuiPort -> IO ()])
-> (GuiPort -> IO ()) -> [Handle] -> IO ()
runInitHandlesControlledGUI title (widget,handlers) initcmd handles =
do gport <- openWish (escape_tcl title) ""
initSchedule widget gport
(map IOHandler (zip handles (map toIOHandler handlers)))
[] initcmd
-- The type of external event handlers currently supported.
-- It is either a handler processing messages from an external port
-- or a handler processing input from various IO streams
data ExternalHandler msg =
PortMsgHandler (msg -> GuiPort -> IO [ReconfigureItem])
| IOHandler (Handle,
[EventHandler] -> Handle -> GuiPort -> IO (Maybe [ReconfigureItem]))
-- start the scheduler (see below) with a given Widget on a wish port
-- and an initial command:
initSchedule :: Widget -> GuiPort -> [ExternalHandler msg] ->
[msg] -> (GuiPort -> IO ()) -> IO ()
initSchedule widget gport exths msgs initcmd = do
send2tk (defaultBgColor ++ tcl) gport
initcmd gport
-- add handler on wish connection as first handler:
scheduleTkEvents evs gport
(IOHandler (handleOf gport,processTkEvent) : exths) msgs
where
(tcl,evs) = mainWidget2tcl gport widget
defaultBgColor =
"label .foo\n" ++
"set defaultBgColor [.foo cget -background]\n" ++
"destroy .foo \n"
-- Scheduler for Tcl/Tk events:
--
-- Meaning of arguments:
-- evs: list of EventHandlers
-- gport: port to a wish
-- exth: handler for external messages
-- msgs: list of external messages
scheduleTkEvents :: [EventHandler] -> GuiPort -> [ExternalHandler msg]
-> [msg] -> IO ()
-- schedule GUI with handler for external port:
scheduleTkEvents evs gport exthds msgs = do
(newmsgs,newconfigs) <- processEvent evs gport (splitHandlers exthds) msgs
configAndProceedScheduler evs gport exthds newmsgs newconfigs
where
-- split ExternalHandlers into list of PortMsgHandler and list of IOHandler:
splitHandlers [] = ([],[])
splitHandlers (PortMsgHandler ph : exths) =
let (phs,iohs) = splitHandlers exths in (ph : phs, iohs)
splitHandlers (IOHandler ioh : exths) =
let (phs,iohs) = splitHandlers exths in (phs, ioh : iohs)
processEvent evs gport (portmsghandler:_,iohandlers) msgs = do
-- for implementation reasons, we take only the first PortMsgHandler
-- (more than one should not occur)
answer <- choiceOverHandlesMsgs (map fst iohandlers) msgs
either (\ (i,hdl) -> do confs <- (snd (iohandlers!!i)) evs hdl gport
return (msgs,confs))
(\ (newmsg:newmsgs) -> do configs <- portmsghandler newmsg gport
return (newmsgs, Just configs) )
answer
-- schedule GUI with handlers for external streams:
processEvent evs gport ([],iohandlers) msgs = do
(i,hdl) <- choiceOverHandles (map fst iohandlers)
mbconfigs <- (snd (iohandlers!!i)) evs hdl gport
return (msgs,mbconfigs)
-- process an event from the wish and return the new configuration items:
processTkEvent :: [EventHandler] -> Handle -> GuiPort
-> IO (Maybe [ReconfigureItem])
processTkEvent evs str gport =
hIsEOF str >>= \eof ->
if eof then return Nothing -- connection closed (by wish)
else
hGetLine str >>= \ans ->
reportTclTk ("GUI RECEIVED: "++ans) >>
if (take 4 ans)==":EVT"
then let (evwidget,evtype) = break (==' ') (drop 4 ans) in
selectEvent evwidget evtype evs gport >>= \configs ->
return (Just configs)
else do reportTclTkError("ERROR in scheduleTkEvents: Received: "++ans++"\n")
-- ignore other outputs:
return (Just [])
-- Reconfigure scheduler with new configurations and proceed.
-- If the configs are Nothing, then terminate the scheduler
-- (this case occurs of the connection is closed by wish)
configAndProceedScheduler _ gport _ _ Nothing = closeGuiPort gport
configAndProceedScheduler evs gport exths msgs (Just configs) = do
mapIO_ reconfigureGUI configs
scheduleTkEvents (configEventHandlers evs configs) gport
(configStreamHandlers exths configs)
msgs
where
reconfigureGUI (WidgetConf r ci) = setConfig r ci gport
reconfigureGUI (StreamHandler _ _) = done
reconfigureGUI (RemoveStreamHandler _) = done
configEventHandlers evs [] = evs
configEventHandlers evs (WidgetConf ref confitem : confitems) =
let label = wRef2Label ref in
case confitem of
Handler evtype handler ->
configEventHandlers ((label,evtype,handler) :
(filter (\ (l,t,_)->l/=label || t/=evtype) evs))
confitems
_ -> configEventHandlers evs confitems
configEventHandlers evs (StreamHandler _ _ : confitems) =
configEventHandlers evs confitems
configEventHandlers evs (RemoveStreamHandler _ : confitems) =
configEventHandlers evs confitems
-- reconfigure external stream handlers:
configStreamHandlers exths [] = exths
configStreamHandlers exths (WidgetConf _ _ : confitems) =
configStreamHandlers exths confitems
configStreamHandlers exths (StreamHandler handle handler : confitems) =
configStreamHandlers
(exths++[IOHandler (handle,\_ hdl gp -> handler hdl gp >>= return . Just)])
confitems
configStreamHandlers exths (RemoveStreamHandler handle : confitems) =
configStreamHandlers (removeHandler handle exths) confitems
where
removeHandler _ [] = []
removeHandler h (PortMsgHandler hr : ehs) =
PortMsgHandler hr : removeHandler h ehs
removeHandler h (IOHandler (h',hr) : ehs) =
if h==h' then removeHandler h ehs
else IOHandler (h',hr) : removeHandler h ehs
-- transform external handler into an IO Handler used in the scheduler
-- which alwaus returns empty configurations:
toIOHandler handler _ handle gport = handler handle gport >> return (Just [])
--- Changes the current configuration of a widget
--- (deprecated operation, only included for backward compatibility).
--- Warning: does not work for Command options!
setConfig :: WidgetRef -> ConfItem -> GuiPort -> IO ()
setConfig (WRefLabel wpv var wtype) confitem gport = do
checkWishConsistency wpv gport
send2tk (config2tcl wtype wpv (wRefname2Label var) confitem) gport
selectEvent evwidget evtype [] _ =
trace ("Internal error in GUI.curry: no handler for event: "++evwidget++evtype++"\n")
(return [])
selectEvent evwidget evtype ((ev,hevtype,handler):evs) gport =
if evwidget==ev && event2tcl hevtype == evtype
then handler gport
else selectEvent evwidget evtype evs gport
-- get the current value of a widget " by
-- 1. executing the Tcl procedure "putvar [getvar_]"
-- 2. reading the message ":VAR%*
-- (where is the length of which can be more than one line)
getWidgetVar :: String -> GuiPort -> IO String
getWidgetVar var gport = do
send2tk ("putvar "++var++" [getvar"++var++"]") gport
getWidgetVarMsg var gport
getWidgetVarMsg var gport =
receiveFromTk gport >>= \varmsg ->
if takeWhile (/='%') varmsg == ":VAR"++var
then let (len,value) = break (=='*') (tail (dropWhile (/='%') varmsg))
in getWidgetVarValue (readNat len) (tail value) gport
else do reportTclTkError ("ERROR in getWidgetVar \""++var++"\": Received: "
++varmsg++"\n")
getWidgetVarMsg var gport -- ignore other messages and try again
getWidgetVarValue len valmsg gport =
if length valmsg < len
then do remvalmsg <- getWidgetVarRemValue (len - (length valmsg + 1)) gport
return (valmsg++"\n"++remvalmsg)
else do if length valmsg > len
then reportTclTkError ("ERROR in getWidgetVar: answer too short\n")
else done
return valmsg
getWidgetVarRemValue len gport =
receiveFromTk gport >>= \valmsg ->
if length valmsg < len
then getWidgetVarRemValue (len - (length valmsg + 1)) gport >>= \remvalmsg ->
return (valmsg++"\n"++remvalmsg)
else do if length valmsg > len
then reportTclTkError ("ERROR in getWidgetVar: answer too short\n")
else done
return valmsg
-- Check consistency of access to widget variables via GUI ports, i.e.,
-- check whether the accessed variable really belongs to the GUI referenced
-- by the GUI port.
checkWishConsistency wp1 wp2 =
if wp1==wp2 then done else
trace "Inconsistent use of Tk ports during access to Tk variables\n" failed
-- escape some Tcl special characters (brackets, dollars):
escape_tcl [] = []
escape_tcl (c:s) = if c=='[' || c==']' || c=='$' || c=='"' || c=='\\'
then '\\':c:escape_tcl s
else c:escape_tcl s
----------------------------------------------------------------------------
-- Some useful IO actions for implementing event handlers...
----------------------------------------------------------------------------
--- An event handler for terminating the GUI.
exitGUI :: GuiPort -> IO ()
exitGUI gport = send2tk "exit" gport -- this also terminates the scheduler
-- due to EOF on the gport handle
--- Gets the (String) value of a variable in a GUI.
getValue :: WidgetRef -> GuiPort -> IO String
getValue (WRefLabel wpv var _) gport = do
checkWishConsistency wpv gport
getWidgetVar var gport
--- Sets the (String) value of a variable in a GUI.
setValue :: WidgetRef -> String -> GuiPort -> IO ()
setValue (WRefLabel wpv var _) val gport = do
checkWishConsistency wpv gport
send2tk ("setvar"++var++" \""++escape_tcl val++"\"") gport
--- Updates the (String) value of a variable w.r.t. to an update function.
updateValue :: (String->String) -> WidgetRef -> GuiPort -> IO ()
updateValue upd wref gport = do
val <- getValue wref gport
setValue wref (upd val) gport
--- Appends a String value to the contents of a TextEdit widget and
--- adjust the view to the end of the TextEdit widget.
appendValue :: WidgetRef -> String -> GuiPort -> IO ()
appendValue (WRefLabel wpv var wtype) val gport =
if wtype/="textedit"
then trace ("WARNING: GUI.appendValue ignored for widget type \""++wtype++"\"\n") done
else checkWishConsistency wpv gport >>
send2tk (wRefname2Label var++" insert end \""++escape_tcl val++"\"") gport >>
send2tk (wRefname2Label var++" see end") gport
--- Appends a String value with style tags to the contents of a TextEdit widget
--- and adjust the view to the end of the TextEdit widget.
--- Different styles can be combined, e.g., to get bold blue text on a
--- red background. If Bold
, Italic
and
--- Underline
are combined, currently all but one of these are
--- ignored.
--- This is an experimental function and might be changed in the future.
appendStyledValue :: WidgetRef -> String -> [Style] -> GuiPort -> IO ()
appendStyledValue (WRefLabel wpv var wtype) val styles gport =
if wtype/="textedit"
then trace ("WARNING: GUI.appendStyledValue ignored for widget type \""++wtype++"\"\n") done
else checkWishConsistency wpv gport >>
send2tk (wRefname2Label var++" insert end \""++escape_tcl val++"\""
++" \""++showStyles styles++"\"") gport >>
send2tk (wRefname2Label var++" see end") gport
where
showStyles = foldr (\st s -> showStyle st ++ " " ++ s) ""
--- Adds a style value in a region of a TextEdit widget.
--- The region is specified a start and end position similarly
--- to getCursorPosition
.
--- Different styles can be combined, e.g., to get bold blue text on a
--- red background. If Bold
, Italic
and
--- Underline
are combined, currently all but one of these are
--- ignored.
--- This is an experimental function and might be changed in the future.
addRegionStyle :: WidgetRef -> (Int,Int) -> (Int,Int) -> Style -> GuiPort
-> IO ()
addRegionStyle (WRefLabel wpv var wtype) (l1,c1) (l2,c2) style gport =
if wtype/="textedit"
then trace ("WARNING: GUI.setRegionStyle ignored for widget type \""++wtype++"\"\n") done
else checkWishConsistency wpv gport >>
send2tk (wRefname2Label var++" tag add "++showStyle style++" "++
show l1++"."++show c1++" "++show l2++"."++show c2) gport
--- Removes a style value in a region of a TextEdit widget.
--- The region is specified a start and end position similarly
--- to getCursorPosition
.
--- This is an experimental function and might be changed in the future.
removeRegionStyle :: WidgetRef -> (Int,Int) -> (Int,Int) -> Style -> GuiPort
-> IO ()
removeRegionStyle (WRefLabel wpv var wtype) (l1,c1) (l2,c2) style gport =
if wtype/="textedit"
then trace ("WARNING: GUI.setRegionStyle ignored for widget type \""++wtype++"\"\n") done
else checkWishConsistency wpv gport >>
send2tk (wRefname2Label var++" tag remove "++showStyle style++" "++
show l1++"."++show c1++" "++show l2++"."++show c2) gport
--- Get the position (line,column) of the insertion cursor in a TextEdit
--- widget. Lines are numbered from 1 and columns are numbered from 0.
getCursorPosition :: WidgetRef -> GuiPort -> IO (Int,Int)
getCursorPosition (WRefLabel wpv var wtype) gport =
if wtype/="textedit"
then error ("GUI.getCursorPosition not applicable to widget type \""++
wtype++"\"")
else do checkWishConsistency wpv gport
send2tk ("puts [ "++wRefname2Label var++" index insert ]") gport
line <- receiveFromTk gport
let (ls,ps) = break (=='.') line
return (if null ps then (0,0) else (readNat ls, readNat (tail ps)))
--- Adjust the view of a TextEdit widget so that the specified line/column
--- character is visible.
--- Lines are numbered from 1 and columns are numbered from 0.
seeText :: WidgetRef -> (Int,Int) -> GuiPort -> IO ()
seeText (WRefLabel wpv var wtype) (line,column) gport =
if wtype/="textedit"
then trace ("WARNING: GUI.seeText ignored for widget type \""++wtype++"\"\n") done
else checkWishConsistency wpv gport >>
send2tk (wRefname2Label var++" see "++show line++"."++show column) gport
--- Sets the input focus of this GUI to the widget referred by the first
--- argument.
--- This is useful for automatically selecting input entries in an application.
focusInput :: WidgetRef -> GuiPort -> IO ()
focusInput (WRefLabel wpv var _) gport = do
checkWishConsistency wpv gport
send2tk ("focus "++wRefname2Label var) gport
--- Adds a list of canvas items to a canvas referred by the first argument.
addCanvas :: WidgetRef -> [CanvasItem] -> GuiPort -> IO ()
addCanvas (WRefLabel wpv var wtype) items gport = do
checkWishConsistency wpv gport
send2tk (config2tcl wtype wpv (wRefname2Label var) (CanvasItems items)) gport
----------------------------------------------------------------------------
-- Example GUIs:
----------------------------------------------------------------------------
--- A simple popup message.
popup_message :: String -> IO ()
popup_message s = runGUI "" (col [Label [Text s],
Button exitGUI [Text "Dismiss"]])
--- A simple event handler that can be associated to a widget.
--- The event handler takes a GUI port as parameter in order to
--- read or write values from/into the GUI.
Cmd :: (GuiPort -> IO ()) -> ConfItem
Cmd cmmd = Command (\gport -> cmmd gport >> return [])
--- An event handler that can be associated to a widget.
--- The event handler takes a GUI port as parameter (in order to
--- read or write values from/into the GUI) and returns a list
--- of widget reference/configuration pairs
--- which is applied after the handler in order to configure some GUI widgets.
Command :: (GuiPort -> IO [ReconfigureItem]) -> ConfItem
Command cmmd = Handler DefaultEvent cmmd
--- A button with an associated event handler which is activated
--- if the button is pressed.
Button :: (GuiPort -> IO ()) -> [ConfItem] -> Widget
Button cmmd confs = PlainButton (Cmd cmmd : confs)
cmd = Cmd
command = Command
button = Button
--- A button with an associated event handler which is activated
--- if the button is pressed. The event handler is a configuration handler
--- (see Command) that allows the configuration of some widgets.
ConfigButton :: (GuiPort -> IO [ReconfigureItem]) -> [ConfItem] -> Widget
ConfigButton cmmd confs = PlainButton (Command cmmd : confs)
--- A text edit widget with vertical and horizontal scrollbars.
--- The argument contains the configuration options for the text edit widget.
TextEditScroll :: [ConfItem] -> Widget
TextEditScroll confs =
matrix [[TextEdit ([WRef txtref, Fill]++confs),
ScrollV txtref [FillY]],
[ScrollH txtref [FillX]]] where txtref free
--- A list box widget with vertical and horizontal scrollbars.
--- The argument contains the configuration options for the list box widget.
ListBoxScroll :: [ConfItem] -> Widget
ListBoxScroll confs =
matrix [[ListBox ([WRef lbref, Fill]++confs),
ScrollV lbref [FillY]],
[ScrollH lbref [FillX]]] where lbref free
--- A canvas widget with vertical and horizontal scrollbars.
--- The argument contains the configuration options for the text edit widget.
CanvasScroll :: [ConfItem] -> Widget
CanvasScroll confs =
col
[row [Canvas ([WRef cref, Fill]++confs),
ScrollV cref [FillY]],
ScrollH cref [FillX]] where cref free
--- An entry widget with a horizontal scrollbar.
--- The argument contains the configuration options for the entry widget.
EntryScroll :: [ConfItem] -> Widget
EntryScroll confs =
col
[Entry ([WRef entryref, FillX]++confs),
ScrollH entryref [Width 10, FillX]]
where entryref free
--- Pops up a GUI for selecting an existing file.
--- The file with its full path name will be returned (or "" if the user
--- cancels the selection).
getOpenFile :: IO String
getOpenFile = getOpenFileWithTypes []
--- Pops up a GUI for selecting an existing file. The parameter is
--- a list of pairs of file types that could be selected.
--- A file type pair consists of a name and an extension for that file type.
--- The file with its full path name will be returned (or "" if the user
--- cancels the selection).
getOpenFileWithTypes :: [(String,String)] -> IO String
getOpenFileWithTypes filetypes = do
gport <- openWish "" ""
send2tk ("wm withdraw .\nputs [tk_getOpenFile" ++
(if null filetypes then "" else
" -filetypes {"++
concatMap (\(x,y)->"{{"++x++"} {"++y++"}} ") filetypes ++"}") ++
"]\n") gport
filename <- receiveFromTk gport
exitGUI gport
return filename
--- Pops up a GUI for choosing a file to save some data.
--- If the user chooses an existing file, she/he will asked to confirm
--- to overwrite it.
--- The file with its full path name will be returned (or "" if the user
--- cancels the selection).
getSaveFile :: IO String
getSaveFile = getSaveFileWithTypes []
--- Pops up a GUI for choosing a file to save some data. The parameter is
--- a list of pairs of file types that could be selected.
--- A file type pair consists of a name and an extension for that file type.
--- If the user chooses an existing file, she/he will asked to confirm
--- to overwrite it.
--- The file with its full path name will be returned (or "" if the user
--- cancels the selection).
getSaveFileWithTypes :: [(String,String)] -> IO String
getSaveFileWithTypes filetypes = do
gport <- openWish "" ""
send2tk ("wm withdraw .\nputs [tk_getSaveFile" ++
(if null filetypes then "" else
" -filetypes {"++
concatMap (\(x,y)->"{{"++x++"} {"++y++"}} ") filetypes ++"}") ++
"]\n") gport
filename <- receiveFromTk gport
exitGUI gport
return filename
--- Pops up a GUI dialog box to select a color.
--- The name of the color will be returned (or "" if the user
--- cancels the selection).
chooseColor :: IO String
chooseColor = do
gport <- openWish "" ""
send2tk "wm withdraw .\nputs [tk_chooseColor]" gport
color <- receiveFromTk gport
exitGUI gport
return color
-- end of GUI library