module Main (main)
where
import List (isPrefixOf, (\\))
import Maybe (isNothing, isJust, fromJust)
import Monad (when, liftM)
import IO (ioeGetFileName, ioeGetErrorString)
import Directory (getCurrentDirectory)
import System (ExitCode(..), getArgs, getProgName)
import IOExts (IORef, newIORef, readIORef, writeIORef, modifyIORef)
import Gtk hiding (init, main)
import qualified
Gtk (init, main)
suffix :: String
suffix = ".bed"
type GUIState = [WindowState]
type GUIStateHandle = IORef GUIState
data WindowElems = WindowElems {
windowWE :: Window,
labelWE :: Label,
buttonWE :: ToggleButton,
toggledWE :: Connection,
menuSaveWE :: MenuItem
}
instance Eq WindowElems where
we1 == we2 = windowWE we1 == windowWE we2
data WindowState = WindowState {
windowsWS :: [WindowElems],
fnameWS :: Maybe FilePath,
valueWS :: Maybe Bool,
dirtyWS :: Bool
}
windowWidgets :: WindowState -> [Window]
windowWidgets = map windowWE . windowsWS
windowElems :: WindowState -> Window -> WindowElems
windowElems ws win = head . filter ((win ==) . windowWE) $ windowsWS ws
allWindows :: GUIStateHandle -> IO [Window]
allWindows guis = liftM (concat . map windowWidgets) $ readIORef guis
addWindow :: GUIStateHandle -> WindowState -> IO ()
addWindow guis ws = modifyIORef guis updateWSs
where
fname = fnameWS ws
updateWSs [] = [ws]
updateWSs (ws':wss)
| isJust fname && fnameWS ws' == fname = merge ws' : wss
| otherwise = ws' : updateWSs wss
merge ws' = ws' {windowsWS = windowsWS ws ++ windowsWS ws'}
removeWindow :: GUIStateHandle -> Window -> IO ()
removeWindow guis win = do
modifyIORef guis $ \wss ->
[ws {windowsWS = wins} | ws <- wss,
let wins = windowsWS ws \\ [winElems],
(not . null) wins]
where
winElems = WindowElems {windowWE = win}
updateWindowElems :: GUIStateHandle -> WindowElems -> IO ()
updateWindowElems guis we = do
modifyIORef guis $ \wss ->
[ws {windowsWS = [if we == we' then we else we'| we' <- windowsWS ws]}
| ws <- wss]
updateWindow :: GUIStateHandle -> Window -> Maybe FilePath -> Bool -> Bool
-> IO ()
updateWindow guis win ofname value dirty = do
modifyIORef guis $ map $ \ws ->
if win `notElem` windowWidgets ws
then ws
else ws {fnameWS = ofname,
valueWS = Just value,
dirtyWS = dirty}
currentState :: GUIStateHandle -> Window -> IO WindowState
currentState guis win = do
wss <- readIORef guis
return $ (head . filter ((win `elem`) . windowWidgets)) wss
about :: IO ()
about = do
win <- windowNew WindowDialog
windowSetTitle win "About BoolEd"
containerSetBorderWidth win 10
frame <- frameNew "The Boolean Editor"
frameSetShadowType frame ShadowEtchedOut
label <- labelNew "The Gtk+HS Boolean Editor is an example application that \
\demonstrates the use of Gtk+HS for implementing GUI \
\applications with Haskell.\n\n\
\Manuel M T Chakravarty, 2001"
labelSetLineWrap label True
labelSetJustify label JustifyFill
containerAdd frame label
but <- buttonNewWithLabel "Ok"
buttonSignalConnectObject but (ButtonClickedHandler widgetDestroy) win
box <- vBoxNew False 0
containerAdd win box
boxPackStart box frame True True 10
boxPackStart box but True False 10
widgetShowAll win
alert :: Maybe Window -> String -> IO ()
alert parent msg = do
win <- windowNew WindowDialog
windowSetTitle win "Error!"
maybeDo (windowSetTransientFor win) parent
containerSetBorderWidth win 10
lbl <- labelNew msg
but <- buttonNewWithLabel "Ok"
buttonSignalConnectObject but (ButtonClickedHandler widgetDestroy) win
box <- vBoxNew False 0
containerAdd win box
boxPackStart box lbl True True 10
boxPackStart box but True False 10
mapM_ widgetShow [widget lbl, widget but, widget box, widget win]
alertIOError :: Maybe Window -> IOError -> IO ()
alertIOError parent ioe =
let
optFName = maybe "" (++ ": ") (ioeGetFileName ioe)
in
alert parent $ optFName ++ ioeGetErrorString ioe
type MenuBarSpec = [(String,
[(String, IO ())])
]
type MenuBarItems = [[MenuItem]]
createMenuBar :: MenuBarSpec -> IO (MenuBar, MenuBarItems)
createMenuBar items = do
mbar <- menuBarNew
itemWidgets <- mapM (createMenuBarItem mbar) items
return (mbar, itemWidgets)
where
createMenuBarItem mbar (label, menuItems) = do
mbarItem <- menuItemNewWithLabel label
menuBarAppend mbar mbarItem
menu <- menuNew
menuItemSetSubmenu mbarItem menu
items <- mapM (createMenuItem menu) menuItems
widgetShow mbarItem
return items
createMenuItem menu (label, action) = do
item <- menuItemNewWithLabel label
menuAppend menu item
menuItemSignalConnect item (MenuItemActivateHandler (const action))
widgetShow item
return item
addWindowWidgets :: GUIStateHandle -> Window -> IO ()
addWindowWidgets guis win = do
ws <- currentState guis win
windowSetTitle win (windowTitle (fnameWS ws))
objectSignalConnect win (ObjectDestroyHandler closeFile)
let initValue = fromJust (valueWS ws)
but <- toggleButtonNewWithLabel " False/True "
lbl <- labelNew (show initValue)
toggleButtonSetActive but initValue
conn <- toggleButtonSignalConnect but
(ToggleButtonToggledHandler buttonToggled)
(mbar, items) <- createMenuBar [("File", [
("Open..." , openFile guis),
("Save" , saveFile guis win),
("Save as...", saveAsFile guis win),
("Close" , closeFile win),
("Exit" , exitFile)
]),
("Help", [
("About..." , about)
])
]
let saveItem = items!!0!!1
when (isNothing (fnameWS ws)) $
widgetSetSensitive saveItem False
box <- vBoxNew False 0
containerAdd win box
boxPackStart box mbar False False 0
boxPackStart box lbl True True 10
boxPackStart box but True True 10
mapM_ widgetShow [widget mbar, widget box, widget but, widget lbl]
updateWindowElems guis (WindowElems win lbl but conn saveItem)
where
closeFile win = do
widgetDestroy win
removeWindow guis win
wins <- allWindows guis
when (null wins) $
mainQuit
exitFile = do
wins <- allWindows guis
mapM_ closeFile wins
buttonToggled but = do
onOff <- toggleButtonQueryState but
setBooleanValue guis win onOff
setBooleanValue :: GUIStateHandle -> Window -> Bool -> IO ()
setBooleanValue guis win value = do
ws <- currentState guis win
updateWindow guis win (fnameWS ws) value True
mapM_ displayValue (windowsWS ws)
where
displayValue we = do
signalHandlerBlock (buttonWE we) (toggledWE we)
toggleButtonSetActive (buttonWE we) value
signalHandlerUnblock (buttonWE we) (toggledWE we)
labelSetText (labelWE we) (show value)
openFile :: GUIStateHandle -> IO ()
openFile guis = do
fsel <- fileSelectionNew "Open file..."
(ok, cancel) <- fileSelectionQueryButtons fsel
buttonSignalConnect ok (ButtonClickedHandler (const (selection fsel)))
buttonSignalConnect cancel (ButtonClickedHandler
(const (widgetDestroy fsel)))
widgetShow fsel
where
selection fsel = do
fname <- fileSelectionGetFilename fsel
widgetDestroy fsel
newWindow guis (Just fname)
saveFile :: GUIStateHandle -> Window -> IO ()
saveFile guis win = do
ws <- currentState guis win
writeBedFile win (fromJust (fnameWS ws)) (fromJust (valueWS ws))
updateWindow guis win (fnameWS ws) (fromJust (valueWS ws)) False
saveAsFile :: GUIStateHandle -> Window -> IO ()
saveAsFile guis win = do
fsel <- fileSelectionNew "Save file..."
(ok, cancel) <- fileSelectionQueryButtons fsel
buttonSignalConnect ok (ButtonClickedHandler (const (selection fsel)))
buttonSignalConnect cancel (ButtonClickedHandler
(const (widgetDestroy fsel)))
widgetShow fsel
where
selection fsel = do
ws <- currentState guis win
fname <- fileSelectionGetFilename fsel
fname' <- normaliseFName fname
widgetDestroy fsel
updateWindow guis win (Just fname') (fromJust (valueWS ws)) True
widgetSetSensitive (menuSaveWE (windowElems ws win)) True
windowSetTitle win (windowTitle (Just fname'))
saveFile guis win
readBedFile :: FilePath -> IO (Maybe Bool)
readBedFile fname =
do
contents <- readFile fname
case contents of
"True" -> return $ Just True
"False" -> return $ Just False
_ -> fail $ fname ++ ": Not a Boolean file"
`catch` \err -> do
alertIOError Nothing err
return Nothing
writeBedFile :: Window -> FilePath -> Bool -> IO ()
writeBedFile win fname b =
writeFile (bedFName fname) (show b)
`catch` alertIOError (Just win)
windowTitle :: Maybe FilePath -> String
windowTitle Nothing = "booled: <unnamed>"
windowTitle (Just fname) = "booled: " ++ fname
newWindowState :: GUIState -> Maybe FilePath -> IO WindowState
newWindowState wss ofname = do
ofname' <- case ofname of
Nothing -> return Nothing
Just fname -> liftM Just $ normaliseFName fname
win <- windowNew WindowToplevel
ovalue <- case ofname' of
Nothing -> return Nothing
Just fname
| null buddyValues -> readBedFile fname
| otherwise -> return $ head buddyValues
where
buddyValues = [valueWS ws | ws <- wss, fnameWS ws == ofname']
return $ WindowState [WindowElems {windowWE = win}] ofname' ovalue False
newWindow :: GUIStateHandle -> Maybe FilePath -> IO ()
newWindow guis ofname = do
wss <- readIORef guis
ws <- newWindowState wss ofname
let ws' = if isNothing (valueWS ws) then ws {valueWS = Just False} else ws
win = head (windowWidgets ws)
addWindow guis ws'
addWindowWidgets guis win
widgetShow win
usageAndExit :: IO ()
usageAndExit = do
prgm <- getProgName
putStrLn $ "Usage: " ++ prgm ++ " [FILE]"
putStrLn $ " FILE Boolean file to load"
putStrLn $ "Boolean files have the suffix `.bed'"
exit $ ExitFailure 1
main :: IO ()
main = do
(_, args) <- Gtk.init Nothing
when (length args > 1) $
usageAndExit
let ofname = if null args then Nothing else Just (head args)
guis <- newIORef []
newWindow guis ofname
Gtk.main
exit ExitSuccess
maybeDo :: (a -> IO b) -> Maybe a -> IO (Maybe b)
maybeDo m Nothing = return Nothing
maybeDo m (Just v) = liftM Just $ m v
bedFName :: FilePath -> FilePath
bedFName fname | hasSuffix = fname
| otherwise = fname ++ suffix
where
fnameLen = length fname
suffixLen = length suffix
hasSuffix = fnameLen >= suffixLen
&& drop (fnameLen - suffixLen) fname == suffix
normaliseFName :: FilePath -> IO FilePath
normaliseFName fname = do
wd <- getCurrentDirectory
let fname' = bedFName fname
prefixLen = length wd + if last wd == '/' then 0 else 1
return $ if wd `isPrefixOf` fname'
then drop prefixLen fname'
else fname'